Files
dev/third_party/python/rpycdec/src/renpy/ast.py

1351 lines
36 KiB
Python

import logging
from typing import Any, Callable, ClassVar, Literal
from renpy import config
from renpy.atl import RawBlock
from renpy.lexer import SubParse
from . import translation, util
def parse_store_name(name: str) -> str:
if not name:
return ""
if name == "store":
return ""
return name.lstrip("store.")
def align_imspec(imspec):
"""
Align imspec to a tuple of 7 elements.
"""
if len(imspec) == 7:
return imspec
elif len(imspec) == 6:
return imspec + (None,)
elif len(imspec) == 3:
return imspec + (None, None, None, None)
else:
raise ValueError(f"Invalid imspec length: {len(imspec)}")
def get_imspec_name(imspec) -> str:
name, expression, tag, at_list, layer, zorder, behind = align_imspec(imspec)
if expression:
return f"expression {expression}"
return " ".join(name)
def get_imspec_expr(imspec, **kwargs) -> str:
name, expression, tag, at_list, layer, zorder, behind = align_imspec(imspec)
rv = []
if expression:
rv.append(f"expression {util.get_code(expression, **kwargs)}")
else:
rv.append(" ".join(name))
if layer:
rv.append(f"onlayer {layer}")
if at_list:
at_list = ", ".join([util.get_code(at, **kwargs) for at in at_list])
rv.append(f"at {at_list}")
if tag:
rv.append(f"as {util.get_code(tag, **kwargs)}")
if zorder:
rv.append(f"zorder {util.get_code(zorder, **kwargs)}")
if behind:
behind = ", ".join([util.get_code(b, **kwargs) for b in behind])
rv.append(f"behind {behind}")
return " ".join(rv)
class Node(object):
filename: str
linenumber: int
next: "Node | None"
"""
Node that unconditionally follows this one in the abstract syntax tree,
or None if this node is the last one in the block.
"""
translatable: ClassVar[bool] = False
"""
True if this node is translatable, False otherwise.
(This can be set on the class or the instance.)
"""
translation_relevant: ClassVar[bool] = False
@property
def name(self) -> str | tuple[Any, ...] | None:
"""
The name property stores and retreives the name for the node.
This is one of:
* A string, when the node is a label.
* A tuple, in (filename, version, serial) format. This is stored efficently,
as it makes up most nodes in Ren'Py.
* Longer tuples, like (filename, version, serial, ...) are rare, but used.
* None, when the name is not known.
"""
if self._name:
return self._name
elif self.name_version:
return (self.filename, self.name_version, self.name_serial)
else:
return None
@name.setter
def name(self, value: str | tuple[Any, ...] | None):
if hasattr(self, "filename"):
match value:
case (self.filename, int(version), int(serial)):
self._name = None
self.name_version = version
self.name_serial = serial
return
self._name = value
def __init__(self, loc: tuple[str, int] = ("", 0)):
"""
Initializes this Node object.
`loc`
A (filename, physical line number) tuple giving the
logical line on which this Node node starts.
"""
self.filename = loc[0]
self.linenumber = loc[1]
self.name = None
self.next = None
def get_translation_strings(self) -> list[tuple[int, str]]:
"""
Return a possibly empty list of linenumber, string pairs of strings
that are additional translation strings for this node.
"""
return []
def get_children(self, f: Callable[["Node"], Any]) -> None:
"""
Calls `f` with this node and its children.
"""
f(self)
class ParameterInfo(object):
def get_code(self, **kwargs) -> str:
"""
>>> ParameterInfo([("a", 1), ("b", 2)]).get_code()
'(a=1, b=2)'
>>> ParameterInfo().get_code()
'()'
"""
param_str = []
positional = list(filter(lambda i: i[0] in self.positional, self.parameters))
for parameter in positional:
if parameter[1] is not None:
param_str.append(f"{parameter[0]}={parameter[1]}")
else:
param_str.append(f"{parameter[0]}")
if self.extrapos:
param_str.append("*%s" % self.extrapos)
nameonly = list(filter(lambda i: i not in positional, self.parameters))
if nameonly:
if not self.extrapos:
param_str.append("*")
for parameter in nameonly:
if parameter[1] is not None:
param_str.append(f"{parameter[0]}={parameter[1]}")
else:
param_str.append(f"{parameter[0]}")
if self.extrakw:
param_str.append(f"**{self.extrakw}")
param = ", ".join(param_str)
return f"({param})"
class ArgumentInfo(object):
def get_code(self, **kwargs):
args = []
for key, val in self.arguments:
if key is not None:
args.append(f"{key}={val}")
else:
args.append(val)
if getattr(self, "starred_indexes", None):
raise NotImplementedError
if getattr(self, "doublestarred_indexes", None):
raise NotImplementedError
if getattr(self, "extrakw", None):
raise NotImplementedError
extrapos = getattr(self, "extrapos", None)
if extrapos:
args.append(f"*{extrapos}")
return "(" + ", ".join(args) + ")"
class PyExpr(str):
"""
Represents a string containing python code.
"""
__slots__ = [
"filename",
"linenumber",
"py",
"hashcode",
"column",
]
def __new__(cls, s, filename=None, linenumber=0, py=3, hashcode=None, column=0):
self = str.__new__(cls, s)
self.filename = filename # type: ignore
self.linenumber = linenumber # type: ignore
self.py = py # type: ignore
self.hashcode = hashcode # type: ignore
self.column = column # type: ignore
return self
def __getnewargs__(self):
return (str(self), self.filename, self.linenumber, self.py, self.hashcode, self.column)
def get_code(self, **kwargs) -> str:
return str(self)
class PyCode(object):
def __setstate__(self, state):
if isinstance(state, dict):
self.__dict__.update(state)
else:
self.state = state
def get_code(self, **kwargs) -> str:
source = getattr(self, "source", None)
if source:
return source
return util.get_code(self.state[1])
class Scry(object):
pass
class Say(Node):
"""
https://www.renpy.org/doc/html/dialogue.html#say-statement
Grammar::
[WHO [ATTRIBUTES] [@TEMPORARY_ATTRIBUTES]] WHAT [nointeract] [id IDENTIFIER] [with TRANSITION]
Examples::
"Hello, world."
e "Hello, world."
e happy "Hello, world." with dissolve
e "Hello, world." id greet_001
"""
def get_code(self, dialogue_filter=None, **kwargs):
rv = []
who = util.attr(self, "who")
if who:
rv.append(util.get_code(who, **kwargs))
attributes = util.attr(self, "attributes")
if attributes is not None:
rv.extend(attributes)
temporary_attributes = util.attr(self, "temporary_attributes")
if temporary_attributes:
rv.append("@")
rv.extend(temporary_attributes)
what = util.attr(self, "what")
if dialogue_filter is not None:
what = dialogue_filter(what)
rv.append(translation.encode_say_string(what))
interact = util.attr(self, "interact")
if not interact:
rv.append("nointeract")
identifier = util.attr(self, "identifier")
explicit_identifier = util.attr(self, "explicit_identifier")
if identifier and explicit_identifier:
rv.append("id")
rv.append(util.get_code(identifier, **kwargs))
arguments = util.attr(self, "arguments")
if arguments:
rv.append(util.get_code(arguments, **kwargs))
# This has to be at the end.
with_ = util.attr(self, "with_")
if with_:
rv.append("with")
rv.append(util.get_code(with_, **kwargs))
return " ".join(rv)
class Init(Node):
"""
https://www.renpy.org/doc/html/python.html#init-python-statement
init python:
https://www.renpy.org/doc/html/lifecycle.html#init-offset-statement
init offset = 42
"""
block: list[Node]
priority: int
def get_code(self, **kwargs) -> str:
if len(self.block) == 1:
next = self.block[0]
if isinstance(next, Python) or isinstance(next, EarlyPython):
return f"init {util.get_code(next, **kwargs)}"
if self.priority == -500 and isinstance(next, Screen):
return util.get_code(self.block, **kwargs)
if self.priority == 500 and isinstance(next, Image):
return util.get_code(next, **kwargs)
if (
isinstance(next, Define)
or isinstance(next, Default)
or isinstance(next, Transform)
):
return util.get_code(next, **kwargs)
if self.priority == 0 and all(
isinstance(item, TranslateString) for item in self.block
):
return util.get_code(self.block, **kwargs)
start = "init"
if self.priority:
start += f" {self.priority}"
if len(self.block) == 0:
raise NotImplementedError
inner_code = util.get_code(self.block, **kwargs)
if inner_code.count("\n") == 0:
return f"{start} {inner_code}"
rv = [start + ":"]
rv.append(util.indent(inner_code))
return "\n".join(rv)
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
class Label(Node):
"""
https://www.renpy.org/doc/html/label.html
Grammar::
label NAME [PARAMETERS] [hide] :
BLOCK
Examples::
label sample1:
"Here is 'sample1' label."
label sample2(a="default"):
"Here is 'sample2' label."
"a = [a]"
label hidden_scope hide:
$ x = 1
"""
translation_relevant = True
block: list[Node]
parameters: ParameterInfo | None = None
hide: bool = False
def get_name(self):
"""
Get the name of the label.
"""
return util.attr(self, "name") or util.attr(self, "_name")
def get_code(self, **kwargs) -> str:
start = "label"
name = self.get_name()
if name:
start += f" {name}"
parameters = util.attr(self, "parameters")
if parameters:
start += f"{util.get_code(parameters, **kwargs)}"
hide = util.attr(self, "hide")
if hide:
start += " hide"
block = util.attr(self, "block")
if not block:
block = Pass()
return util.label_code(start, block, **kwargs)
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
class Python(Node):
"""
https://www.renpy.org/doc/html/python.html#python-statements
Grammar::
python [hide] [in STORE] :
BLOCK
$ PYTHON_CODE
Examples::
python:
flag = True
python hide:
secret = "not in global namespace"
python in mystore:
myvar = 1
$ flag = True
"""
def get_code(self, **kwargs) -> str:
inner_code = util.get_code(self.code, **kwargs)
store = parse_store_name(util.attr(self, "store"))
hide = util.attr(self, "hide")
# For single-line Python statements
# without store or hide attributes, use the $ shorthand
if not store and not hide and "\n" not in inner_code:
return f"$ {inner_code}"
start = "python"
if hide:
start += " hide"
if store:
start += f" in {store}"
rv = [start + ":"]
rv.append(util.indent(f"{inner_code}"))
return "\n".join(rv)
class EarlyPython(Node):
"""
https://www.renpy.org/doc/html/python.html#python-statements
Like ``python``, but runs before all other ``init`` blocks.
Grammar::
python early [hide] [in STORE] :
BLOCK
$ PYTHON_CODE
Examples::
python early:
flag = True
python early hide:
secret = "not in global namespace"
$ flag = True
"""
def get_code(self, **kwargs) -> str:
inner_code = util.get_code(self.code, **kwargs)
storename = parse_store_name(util.attr(self, "store"))
hide = util.attr(self, "hide")
if not storename and not hide and len(inner_code.split("\n")) == 1:
return f"$ {inner_code}"
start = "python early"
if hide:
start += " hide"
if storename:
start += f" in {storename}"
return util.label_code(start, util.attr(self, "code"), **kwargs)
class Image(Node):
"""
https://www.renpy.org/doc/html/displayables.html#images
Grammar::
image NAME = EXPRESSION
image NAME :
atl_block
Examples::
image logo = "logo.png"
image logo = Image("logo.png", xalign=1.0)
"""
def get_code(self, **kwargs) -> str:
start = "image"
if self.imgname:
start += f" {' '.join(self.imgname)}"
code = util.attr(self, "code")
if code:
return f"{start} = {util.get_code(code,**kwargs)}"
return util.label_code(start, util.attr(self, "atl"), **kwargs)
class Transform(Node):
"""
https://www.renpy.org/doc/html/transforms.html#transform-statement
atl_transform ::= "transform" qualname ( "(" parameters ")" )? ":"
atl_block
"""
def get_code(self, **kwargs) -> str:
"""
transform notif_t:
alpha 0
ease .2 alpha 1
pause 2.6
ease .2 alpha 0 yzoom 0
"""
start = "transform"
varname = util.attr(self, "varname")
if varname:
start += f" {varname}"
parameters = util.attr(self, "parameters")
if parameters:
start += f"{util.get_code(parameters, **kwargs)}"
return util.label_code(start, util.attr(self, "atl"), **kwargs)
class Show(Node):
"""
https://www.renpy.org/doc/html/displaying_images.html#show-statement
Grammar::
show (NAME | expression EXPR) [onlayer LAYER] [at TRANSFORMS] [as TAG]
[zorder ZORDER] [behind TAGS] [with TRANSITION]
show NAME :
atl_block
Examples::
show eileen happy
show eileen happy at left with dissolve
show expression "eileen_happy.png" as eileen
"""
def get_code(self, **kwargs) -> str:
start = "show"
name = get_imspec_expr(self.imspec)
if name:
start += f" {name}"
with_expr = kwargs.get("with_expr", None)
if with_expr:
start += f" with {util.get_code(with_expr, **kwargs)}"
return util.label_code(start, util.attr(self, "atl"), **kwargs)
class ShowLayer(Node):
"""
https://www.renpy.org/doc/html/displaying_images.html#camera-and-show-layer-statements
show layer <name> at a,b,c :
atl
"""
def get_code(self, **kwargs) -> str:
start = "show layer"
layer = util.attr(self, "layer")
if layer:
start += f" {layer}"
if not layer:
start += " master"
logging.warning(
"ShowLayer not have a layer name: %s:%s", self.filename, self.linenumber
)
at_list = util.attr(self, "at_list")
if at_list:
at_list = ", ".join([util.get_code(at, **kwargs) for at in at_list])
start += f" at {at_list}"
return util.label_code(start, util.attr(self, "atl"), **kwargs)
class Scene(Node):
"""
https://www.renpy.org/doc/html/displaying_images.html#scene-statement
example:
scene bg room [with dissolve]
"""
def get_code(self, **kwargs) -> str:
start = "scene"
imspec = util.attr(self, "imspec")
layer = util.attr(self, "layer")
if imspec:
start += f" {get_imspec_expr(imspec, **kwargs)}"
elif layer:
start += f" onlayer {layer}"
with_expr = kwargs.get("with_expr", None)
if with_expr:
start += f" with {util.get_code(with_expr, **kwargs)}"
return util.label_code(start, util.attr(self, "atl"), **kwargs)
class Hide(Node):
"""
https://www.renpy.org/doc/html/displaying_images.html#hide-statement
Grammar::
hide (NAME | expression EXPR) [onlayer LAYER] [with TRANSITION]
Examples::
hide eileen
hide eileen with dissolve
"""
def get_code(self, **kwargs) -> str:
start = "hide"
name = get_imspec_expr(self.imspec)
if name:
start += f" {name}"
with_expr = kwargs.get("with_expr", None)
if with_expr:
start += f" with {util.get_code(with_expr, **kwargs)}"
return start
class With(Node):
"""
https://www.renpy.org/doc/html/displaying_images.html#with-statement
Grammar::
with TRANSITION
Examples::
with dissolve
with fade
"""
def get_code(self, **kwargs) -> str:
paired = util.attr(self, "paired")
if paired:
raise Exception("With paired not covered")
expr = util.attr(self, "expr")
if isinstance(expr, str):
return f"with {expr}"
return f"with {util.get_code(expr, **kwargs)}"
class Call(Node):
"""
https://www.renpy.org/doc/html/label.html#call-statement
call subroutine
call subroutine(2)
call expression "sub" + "routine" pass (count=3)
call [expression SIMPLE_EXPRESSION] [pass] [ARGUMENTS] [from LABEL]
call LABEL [ARGUMENTS]
"""
def get_code(self, **kwargs) -> str:
start = "call"
expression = util.attr(self, "expression")
if expression:
if expression == True:
start += " expression"
else:
start += f" expression {util.get_code(expression,**kwargs)}"
label = util.attr(self, "label")
if label:
start += f" {label}"
arguments = util.attr(self, "arguments")
if arguments:
start += f"{util.get_code(arguments,**kwargs)}"
from_label = kwargs.get("from_label", None)
if from_label:
start += f" from {from_label.get_name()}"
return start
class Return(Node):
"""
https://www.renpy.org/doc/html/label.html#return-statement
Grammar::
return [EXPRESSION]
Examples::
return
return "result"
"""
def __new__(cls, *args, **kwargs):
self = Node.__new__(cls)
self.expression = None
return self
def get_code(self, **kwargs) -> str:
expression = util.attr(self, "expression")
if expression:
return f"return {util.get_code(expression, **kwargs)}"
else:
return "return"
class Menu(Node):
"""
https://www.renpy.org/doc/html/menus.html#in-game-menus
"""
translation_relevant = True
items: list[tuple[str, str, list[Node] | None]]
statement_start: Node # type: ignore
set: str | None = None
with_: str | None = None
has_caption: bool = False
arguments: ArgumentInfo | None = None
item_arguments: list[ArgumentInfo | None] | None = None
rollback: str = "force" # type: ignore
def _is_caption(item):
label, condition, block = item
return condition == "True" and block is None
def get_code(self, **kwargs) -> str:
"""
default menuset = set()
menu chapter_1_places:
set menuset
"Where should I go?"
"Dallas, TX" (150, sale=True):
jump dallas
"Go to class." if True:
jump go_to_class
"Go to the bar.":
jump go_to_bar
"Go to jail.":
jump go_to_jail
"""
start = "menu"
label = util.attr(kwargs, "menu_label")
if label:
if isinstance(label, Label):
start += f" {label.get_name()}"
else:
start += f" {label}"
arguments = util.attr(self, "arguments")
if arguments:
start += f" {util.get_code(arguments,**kwargs)}"
rv = [start + ":"]
with_ = util.attr(self, "with_")
if with_:
rv.append(util.indent(f"with {with_}"))
attrset = util.attr(self, "set")
if attrset:
rv.append(util.indent(f"set {attrset}"))
say = util.attr(kwargs, "menu_say")
if say:
rv.append(util.indent(util.get_code(say, **kwargs)))
items = util.attr(self, "items")
item_arguments = util.attr(self, "item_arguments")
for idx, (say, cond, expr) in enumerate(items):
sel = translation.encode_say_string(say)
if item_arguments:
argument = item_arguments[idx]
# argument may None
if argument:
sel += f"{util.get_code(argument,**kwargs)}"
if cond and cond != "True":
sel += f" if {util.get_code(cond,**kwargs)}"
# a empty expr means a caption
rv.append(util.indent(util.label_code(sel, expr, **kwargs)))
if len(rv) == 1:
rv.append("pass") # if no items, add a pass statement
return "\n".join(rv)
def get_translation_strings(self):
rv = super().get_translation_strings()
for caption, _, block in self.items:
if config.old_substitutions:
caption = caption.replace("%%", "%")
if caption is None:
continue
# Empty lines after the caption will strill make
# this caption to be repoprted on wrong line,
# but it is still better than line number of the menu itself
# which can be hundreds of lines away.
if block:
loc = block[0].linenumber - 1
else:
loc = self.linenumber
rv.append((loc, caption))
return rv
def get_children(self, f):
f(self)
for _label, _condition, block in self.items:
if block:
for i in block:
i.get_children(f)
class Jump(Node):
"""
https://www.renpy.org/doc/html/label.html#jump-statement
"""
def get_code(self, **kwargs) -> str:
rv = "jump"
expression = util.attr(self, "expression")
if expression is True:
rv += " expression"
elif expression: # maybe backward compatibility?
rv += f" {util.get_code(expression, **kwargs)}"
target = util.attr(self, "target")
if target:
rv += f" {target}"
return rv
class Pass(Node):
"""
pass
"""
def get_code(self, **kwargs) -> str:
# return ""
# TODO: check if this is correct
# may be return "pass" or "" at cases ?
return "pass"
class While(Node):
"""
https://www.renpy.org/doc/html/conditional.html#while-statement
while lines: # evaluates to True as long as the list is not empty
play sound lines.pop(0) # removes the first element
pause
while True:
"This is the song that never terminates."
"It goes on and on, my compatriots."
"""
condition: str
block: list[Node]
def get_code(self, **kwargs) -> str:
start = f"while {self.condition}"
return util.label_code(start, util.attr(self, "block"), **kwargs)
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
class If(Node):
"""
https://www.renpy.org/doc/html/conditional.html#if-statement
Grammar::
if CONDITION :
BLOCK
[elif CONDITION :
BLOCK]
[else :
BLOCK]
Examples::
if flag:
"Flag is set."
elif other:
"Other is set."
else:
"Nothing is set."
"""
def get_code(self, **kwargs) -> str:
rv = []
entries = util.attr(self, "entries")
for index, (cond, body) in enumerate(entries):
if cond is None:
logging.warning(
"Unexpected None condition in if statement at %s:%s",
self.filename,
self.linenumber,
)
block = util.indent(util.get_code(body, **kwargs))
if index == 0:
rv.append(f"if {cond}:\n{block}")
continue
if index == len(entries) - 1:
rv.append(f"else:\n{block}")
continue
rv.append(f"elif {cond}:\n{block}")
return "\n".join(rv)
def get_children(self, f):
f(self)
for _condition, block in self.entries:
for i in block:
i.get_children(f)
class UserStatement(Node):
line: str
parsed: Any
block: list[Any] = []
translatable: bool = False # type: ignore
code_block: list[Node] | None = None
translation_relevant: bool = False # type: ignore
rollback: Literal["normal", "never", "force"] = "normal"
atl: "RawBlock | None" = None
subparses: list["SubParse"] = []
init_priority: int | None = None
init_offset: int | None = None
def __new__(cls, *args, **kwargs):
self = Node.__new__(cls)
self.block = []
self.code_block = None
self.translatable = False
self.translation_relevant = False
self.rollback = "normal"
self.subparses = []
return self
def get_code(self, **kwargs) -> str:
start = self.line
rv = [start]
if self.block:
rv.append(util.indent(util.get_block_code(self.block, **kwargs)))
return "\n".join(rv)
def get_translation_strings(self) -> list[tuple[int, str]]:
rv = super().get_translation_strings()
if strings := self.call("translation_strings"):
for i in strings:
if not isinstance(i, tuple):
i = (self.linenumber, i)
rv.append(i)
return rv
def get_children(self, f):
f(self)
if self.code_block is not None:
for i in self.code_block:
i.get_children(f)
for i in self.subparses:
for j in i.block:
j.get_children(f)
class PostUserStatement(Node):
"""Post-execution node for user-defined statements (Ren'Py 8.x+).
Appears after a UserStatement to handle post-execution logic.
Carries a reference back to the parent statement.
"""
parent: Node | None = None
def get_code(self, **kwargs) -> str:
return ""
class StoreNamespace(object):
pass
class Define(Node):
"""
https://www.renpy.org/doc/html/python.html#define-statement
Grammar::
define [PRIORITY] [STORE.]NAME (= | |= | += ) EXPRESSION
Examples::
define e = Character("Eileen")
define -2 gui.accent_color = '#ffdd1e'
define config.tag_layer |= { "eileen": "master" }
"""
def get_code(self, **kwargs) -> str:
start = "define"
priority = util.attr(self, "priority")
if priority:
start += f" {priority}"
store_name = parse_store_name(util.attr(self, "store"))
varname = util.attr(self, "varname")
operator = "="
if getattr(self, "operator", None):
operator = self.operator
if store_name:
varname = f"{store_name}.{varname}"
return f"{start} {varname} {operator} {util.get_code(self.code,**kwargs)}"
# All the default statements, in the order they were registered.
default_statements = []
class Default(Node):
"""
https://www.renpy.org/doc/html/python.html#default-statement
Grammar::
default [STORE.]NAME = EXPRESSION
Examples::
default points = 0
default mystore.flag = False
"""
def get_code(self, **kwargs) -> str:
# trim store or store. prefix
st = parse_store_name(util.attr(self, "store"))
varname = util.attr(self, "varname")
if st:
varname = f"{st}.{varname}"
return f"default {varname} = {util.get_code(self.code,**kwargs)}"
class Screen(Node):
"""
https://www.renpy.org/doc/html/screens.html#screen-language
Grammar::
screen NAME [PARAMETERS] [KEYWORDS]:
SCREEN_LANGUAGE_BLOCK
"""
def get_code(self, **kwargs) -> str:
return util.get_code(self.screen, **kwargs)
class Translate(Node):
"""
translate arabic python:
gui.REGULAR_FONT = "DejaVuSans.ttf"
gui.LIGHT_FONT = "DejaVuSans.ttf"
gui.FONT_SCALE = .9
gui.REGULAR_BOLD = True
"""
rollback = "never"
translation_relevant = True
identifier: str
alternate: str | None
language: str | None
block: list[Node]
after: Node | None
def __init__(self, loc, identifier, language, block, alternate=None):
super(Translate, self).__init__(loc)
self.identifier = identifier
self.alternate = alternate
self.language = language
self.block = block
def get_code(self, **kwargs) -> str:
language = util.attr(self, "language")
identifier = util.attr(self, "identifier")
alternate = util.attr(self, "alternate")
if not language and not identifier:
return ""
start = f"translate {language} {identifier}"
if alternate:
start += f" alternate {alternate}"
if self.block:
start += ":"
rv = [start]
for item in self.block:
callkwargs = kwargs.copy()
# inject translate language and label to all children
callkwargs.update({"language": language, "label": identifier})
rv.append(util.indent(util.get_code(item, **callkwargs)))
return "\n".join(rv)
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
class EndTranslate(Node):
def get_code(self, **kwargs) -> str:
return ""
class TranslateString(Node):
"""
https://www.renpy.org/doc/html/translating_renpy.html#language-specific-translations
translate french strings:
old "OK"
new ""
"""
translation_relevant = True
language: str
old: str
new: str
newloc: tuple[str, int]
def get_code(self, **kwargs) -> str:
language = util.attr(self, "language")
if not language:
language = "None"
return (
f"translate {language} strings:\n"
f"{util.indent(f'old {translation.encode_say_string(self.old)}')} \n"
f"{util.indent(f'new {translation.encode_say_string(self.new)}')}"
)
class TranslatePython(Node):
"""
https://www.renpy.org/doc/html/translating_renpy.html
A ``python`` block inside a ``translate`` statement, run for a specific
language during init.
Grammar::
translate LANGUAGE python:
BLOCK
Example::
translate arabic python:
gui.REGULAR_FONT = "DejaVuSans.ttf"
"""
language: str
code: PyCode
class TranslateBlock(Node):
translation_relevant = True
block: list[Node]
language: str
def get_code(self, **kwargs) -> str:
return util.get_code(self.block, **kwargs)
def get_children(self, f):
f(self)
for i in self.block:
i.get_children(f)
class TranslateSay(Node):
"""
translate say:
"Hello" "Bonjour"
"""
translatable = True
translation_relevant = True
alternate: str | None
language: str | None
def get_code(self, **kwargs) -> str:
old, new = util.attr(self, "old"), util.attr(self, "new")
if old or new:
return f'translate say:\n{util.indent(f"{self.old} {self.new}")}'
return ""
class TranslateEarlyBlock(TranslateBlock):
def get_code(self, **kwargs) -> str:
for item in self.block:
kwargs.update({"language": self.language})
return util.get_code(item, **kwargs)
class Style(Node):
"""
# https://www.renpy.org/doc/html/style.html#defining-styles-style-statement
# Creates a new style, inheriting from default.
style big_red:
size 40
# Updates the style.
style big_red color "#f00"
# Takes the properties of label_text from big_red, but only if we're
# on a touch system.
style label_text:
variant "touch"
take big_red
# Style a has all the properties of style c, except that when not present
# in c, the properties are taken from b or b's parents.
style a is b:
take c
"""
def get_code(self, **kwargs) -> str:
start = f"style {self.style_name}"
properties = self.properties.copy()
parent = util.attr(self, "parent")
if parent:
start += f" is {parent}"
clear = util.attr(self, "clear")
if clear:
properties["clear"] = None
take = util.attr(self, "take")
if take:
properties["take"] = take
delattr = util.attr(self, "delattr")
if delattr:
for d in delattr:
properties["delattr"] = d
variant = util.attr(self, "variant")
if variant:
properties["variant"] = variant
if not properties:
return start
if len(properties) < 3:
return f"{start} {util.get_code_properties(properties)}"
rv = [start + ":"]
rv.append(util.indent(util.get_code_properties(properties, newline=True)))
return "\n".join(rv)
class Testcase(Node):
"""
Ren'Py 8.x+: Represents a testcase statement for automated testing.
Grammar::
testcase LABEL [OPTIONS] :
BLOCK
Example::
testcase "test_menu" label start:
"Hello"
"""
label: str = ""
test: str = ""
block: list[Node] = []
description: str | None = None
options: str | None = None
def get_code(self, **kwargs) -> str:
start = "testcase"
label = util.attr(self, "label")
if label:
start += f" {translation.encode_say_string(label)}"
test = util.attr(self, "test")
if test:
start += f" label {test}"
options = util.attr(self, "options")
if options:
start += f" {options}"
block = util.attr(self, "block")
return util.label_code(start, block, **kwargs)
class Camera(Node):
"""
https://www.renpy.org/doc/html/3dstage.html#using-the-3d-stage
# Enabling the 3D stage for the background layer.
camera background:
perspective True
camera master at a,b,c:
atl
"""
def get_code(self, **kwargs) -> str:
start = "camera"
if self.layer:
start += f" {self.layer}"
if self.at_list:
start += f" at {util.get_code(self.at_list,**kwargs)}"
return util.label_code(start, util.attr(self, "atl"), **kwargs)