1378 lines
33 KiB
Python
1378 lines
33 KiB
Python
# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person
|
|
# obtaining a copy of this software and associated documentation files
|
|
# (the "Software"), to deal in the Software without restriction,
|
|
# including without limitation the rights to use, copy, modify, merge,
|
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
# and to permit persons to whom the Software is furnished to do so,
|
|
# subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
from __future__ import print_function
|
|
|
|
import renpy.display
|
|
import contextlib
|
|
|
|
# Grab the python versions of the parser and ast modules.
|
|
ast = __import__("ast")
|
|
|
|
# The filename of the file we're parsing.
|
|
filename = None
|
|
|
|
new_variable_serial = 0
|
|
|
|
# Returns the name of a new variable.
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def new_variable():
|
|
global new_variable_serial
|
|
|
|
new_variable_serial += 1
|
|
yield "_%d" % new_variable_serial
|
|
new_variable_serial -= 1
|
|
|
|
|
|
def increment_lineno(node, amount):
|
|
for node in ast.walk(node):
|
|
if hasattr(node, 'lineno'):
|
|
node.lineno += amount
|
|
|
|
|
|
class LineNumberNormalizer(ast.NodeVisitor):
|
|
|
|
def __init__(self):
|
|
self.last_line = 1
|
|
|
|
def generic_visit(self, node):
|
|
|
|
if hasattr(node, 'lineno'):
|
|
self.last_line = max(self.last_line, node.lineno)
|
|
node.lineno = self.last_line
|
|
|
|
super(LineNumberNormalizer, self).generic_visit(node)
|
|
|
|
|
|
##############################################################################
|
|
# Parsing.
|
|
|
|
# The parser that things are being added to.
|
|
parser = None
|
|
|
|
|
|
class Positional(object):
|
|
"""
|
|
This represents a positional parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
class Keyword(object):
|
|
"""
|
|
This represents an optional keyword parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
STYLE_PREFIXES = [
|
|
'',
|
|
'insensitive_',
|
|
'hover_',
|
|
'idle_',
|
|
'activate_',
|
|
'selected_',
|
|
'selected_insensitive_',
|
|
'selected_hover_',
|
|
'selected_idle_',
|
|
'selected_activate_',
|
|
]
|
|
|
|
|
|
class Style(object):
|
|
"""
|
|
This represents a style parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
class PrefixStyle(object):
|
|
"""
|
|
This represents a prefixed style parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, prefix, name):
|
|
self.prefix = prefix
|
|
self.name = name
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
class Parser(object):
|
|
|
|
def __init__(self, name):
|
|
|
|
# The name of this object.
|
|
self.name = name
|
|
|
|
# The positional arguments, keyword arguments, and child
|
|
# statements of this statement.
|
|
self.positional = [ ]
|
|
self.keyword = { }
|
|
self.children = { }
|
|
|
|
all_statements.append(self)
|
|
|
|
def __repr__(self):
|
|
return "<%s: %s>" % (self.__class__.__name__, self.name)
|
|
|
|
def add(self, i):
|
|
"""
|
|
Adds a clause to this parser.
|
|
"""
|
|
|
|
if isinstance(i, list):
|
|
for j in i:
|
|
self.add(j)
|
|
|
|
return
|
|
|
|
if isinstance(i, Positional):
|
|
self.positional.append(i)
|
|
|
|
elif isinstance(i, Keyword):
|
|
self.keyword[i.name] = i
|
|
|
|
elif isinstance(i, Style):
|
|
for j in STYLE_PREFIXES:
|
|
self.keyword[j + i.name] = i
|
|
|
|
elif isinstance(i, PrefixStyle):
|
|
for j in STYLE_PREFIXES:
|
|
self.keyword[i.prefix + j + i.name] = i
|
|
|
|
elif isinstance(i, Parser):
|
|
self.children[i.name] = i
|
|
|
|
def parse_statement(self, l, name, layout_mode=False):
|
|
word = l.word() or l.match(r'\$')
|
|
|
|
if word and word in self.children:
|
|
if layout_mode:
|
|
c = self.children[word].parse_layout(l, name)
|
|
else:
|
|
c = self.children[word].parse(l, name)
|
|
|
|
return c
|
|
else:
|
|
return None
|
|
|
|
def parse_layout(self, l, name):
|
|
l.error("The %s statement cannot be used as a container for the has statement." % self.name)
|
|
|
|
def parse_children(self, stmt, l, name):
|
|
l.expect_block(stmt)
|
|
|
|
l = l.subblock_lexer()
|
|
|
|
rv = [ ]
|
|
|
|
with new_variable() as child_name:
|
|
|
|
count = 0
|
|
|
|
while l.advance():
|
|
|
|
if len(l.block) != 1:
|
|
rv.extend(self.parse_exec("%s = (%s, %d)" % (child_name, name, count), l.number))
|
|
else:
|
|
child_name = name
|
|
|
|
c = self.parse_statement(l, child_name)
|
|
if c is None:
|
|
l.error('Expected screen language statement.')
|
|
|
|
rv.extend(c)
|
|
count += 1
|
|
|
|
return rv
|
|
|
|
def parse_eval(self, expr, lineno=1):
|
|
"""
|
|
Parses an expression for eval, and then strips off the module
|
|
and expr instances, and adjusts the line number.
|
|
"""
|
|
|
|
if isinstance(expr, unicode):
|
|
expr = renpy.python.escape_unicode(expr)
|
|
|
|
try:
|
|
rv = ast.parse(expr, 'eval').body[0].value
|
|
except SyntaxError as e:
|
|
raise renpy.parser.ParseError(
|
|
filename,
|
|
lineno + e[1][1] - 1,
|
|
"Syntax error while parsing python expression.",
|
|
e[1][3],
|
|
e[1][2])
|
|
|
|
increment_lineno(rv, lineno-1)
|
|
|
|
return rv
|
|
|
|
def parse_exec(self, code, lineno=1):
|
|
"""
|
|
Parses an expression for exec, then strips off the module and
|
|
adjusts the line number. Returns a list of statements.
|
|
"""
|
|
|
|
if isinstance(code, unicode):
|
|
code = renpy.python.escape_unicode(code)
|
|
|
|
try:
|
|
rv = ast.parse(code, 'exec')
|
|
except SyntaxError as e:
|
|
|
|
raise renpy.parser.ParseError(
|
|
filename,
|
|
lineno + e[1][1] - 1,
|
|
"Syntax error while parsing python code.",
|
|
e[1][3],
|
|
e[1][2])
|
|
|
|
increment_lineno(rv, lineno-1)
|
|
|
|
return rv.body
|
|
|
|
def parse_simple_expression(self, l):
|
|
lineno = l.number
|
|
expr = l.require(l.simple_expression)
|
|
|
|
return self.parse_eval(expr, lineno)
|
|
|
|
def parse_comma_expression(self, l):
|
|
lineno = l.number
|
|
expr = l.require(l.comma_expression)
|
|
|
|
return self.parse_eval(expr, lineno)
|
|
|
|
def parse(self, l, name):
|
|
"""
|
|
This is expected to parse a function statement, and to return
|
|
a list of python ast statements.
|
|
|
|
`l` the lexer.
|
|
|
|
`name` the name of the variable containing the name of the
|
|
current statement.
|
|
"""
|
|
|
|
raise Exception("Not Implemented")
|
|
|
|
|
|
# A singleton value.
|
|
many = renpy.object.Sentinel("many")
|
|
|
|
|
|
class FunctionStatementParser(Parser):
|
|
"""
|
|
This is responsible for parsing function statements.
|
|
"""
|
|
|
|
def __init__(self, name, function, nchildren=0, unevaluated=False, scope=False):
|
|
|
|
super(FunctionStatementParser, self).__init__(name)
|
|
|
|
# Functions that are called when this statement runs.
|
|
self.function = function
|
|
|
|
# The number of children we have.
|
|
self.nchildren = nchildren
|
|
|
|
# True if we should evaluate arguments and children. False
|
|
# if we should just pass them into our child.
|
|
self.unevaluated = unevaluated
|
|
|
|
# Add us to the appropriate lists.
|
|
global parser
|
|
parser = self
|
|
|
|
if nchildren != 0:
|
|
childbearing_statements.append(self)
|
|
|
|
self.scope = scope
|
|
|
|
def parse_layout(self, l, name):
|
|
return self.parse(l, name, True)
|
|
|
|
def parse(self, l, name, layout_mode=False):
|
|
|
|
# The list of nodes this function returns.
|
|
rv = [ ]
|
|
|
|
# The line number of the current node.
|
|
lineno = l.number
|
|
|
|
if layout_mode and self.nchildren == 0:
|
|
l.error("The %s statement cannot be used as a layout." % self.name)
|
|
|
|
func = self.parse_eval(self.function, lineno)
|
|
|
|
call_node = ast.Call(
|
|
lineno=lineno,
|
|
col_offset=0,
|
|
func=func,
|
|
args=[ ],
|
|
keywords=[ ],
|
|
starargs=None,
|
|
kwargs=None,
|
|
)
|
|
|
|
seen_keywords = set()
|
|
|
|
# Parses a keyword argument from the lexer.
|
|
def parse_keyword(l, expect):
|
|
name = l.word()
|
|
|
|
if name is None:
|
|
l.error(expect)
|
|
|
|
if name not in self.keyword:
|
|
l.error('%r is not a keyword argument or valid child for the %s statement.' % (name, self.name))
|
|
|
|
if name in seen_keywords:
|
|
l.error('keyword argument %r appears more than once in a %s statement.' % (name, self.name))
|
|
|
|
seen_keywords.add(name)
|
|
|
|
expr = self.parse_comma_expression(l)
|
|
|
|
call_node.keywords.append(
|
|
ast.keyword(arg=str(name), value=expr),
|
|
)
|
|
|
|
# We assume that the initial keyword has been parsed already,
|
|
# so we start with the positional arguments.
|
|
|
|
for _i in self.positional:
|
|
call_node.args.append(self.parse_simple_expression(l))
|
|
|
|
# Next, we allow keyword arguments on the starting line.
|
|
while True:
|
|
if l.match(':'):
|
|
l.expect_eol()
|
|
l.expect_block(self.name)
|
|
block = True
|
|
break
|
|
|
|
if l.eol():
|
|
l.expect_noblock(self.name)
|
|
block = False
|
|
break
|
|
|
|
parse_keyword(l, "expected a keyword argument, colon, or end of line.")
|
|
|
|
rv.append(ast.Expr(value=call_node))
|
|
|
|
if self.nchildren == 1:
|
|
rv.extend(self.parse_exec('ui.child_or_fixed()'))
|
|
|
|
needs_close = (self.nchildren != 0)
|
|
|
|
# The index of the child we're adding to this statement.
|
|
child_index = 0
|
|
|
|
# A list of lexers we need to parse the contents of.
|
|
lexers = [ ]
|
|
|
|
if block:
|
|
lexers.append(l.subblock_lexer())
|
|
|
|
if layout_mode:
|
|
lexers.append(l)
|
|
|
|
# The variable we store the child's name in.
|
|
with new_variable() as child_name:
|
|
|
|
# If we have a block, parse it. This also takes care of parsing the
|
|
# block of a has clause.
|
|
|
|
for l in lexers:
|
|
|
|
while l.advance():
|
|
|
|
state = l.checkpoint()
|
|
|
|
if l.keyword(r'has'):
|
|
if self.nchildren != 1:
|
|
l.error("The %s statement does not take a layout." % self.name)
|
|
|
|
if child_index != 0:
|
|
l.error("The has statement may not be given after a child has been supplied.")
|
|
|
|
c = self.parse_statement(l, child_name, layout_mode=True)
|
|
|
|
if c is None:
|
|
l.error('Has expects a child statement.')
|
|
|
|
# Remove the call to child_or_fixed.
|
|
rv.pop()
|
|
|
|
rv.extend(self.parse_exec("%s = (%s, %d)" % (child_name, name, child_index)))
|
|
rv.extend(c)
|
|
|
|
needs_close = False
|
|
|
|
continue
|
|
|
|
c = self.parse_statement(l, child_name)
|
|
|
|
if c is not None:
|
|
|
|
rv.extend(self.parse_exec("%s = (%s, %d)" % (child_name, name, child_index)))
|
|
rv.extend(c)
|
|
|
|
child_index += 1
|
|
|
|
continue
|
|
|
|
l.revert(state)
|
|
|
|
if not l.eol():
|
|
parse_keyword(l, "expected a keyword argument or child statement.")
|
|
|
|
while not l.eol():
|
|
parse_keyword(l, "expected a keyword argument or end of line.")
|
|
|
|
if needs_close:
|
|
rv.extend(self.parse_exec("ui.close()"))
|
|
|
|
if "id" not in seen_keywords:
|
|
call_node.keywords.append(ast.keyword(arg="id", value=self.parse_eval(name, lineno)))
|
|
|
|
if "scope" not in seen_keywords and self.scope:
|
|
call_node.keywords.append(ast.keyword(arg="scope", value=self.parse_eval("_scope", lineno)))
|
|
|
|
return rv
|
|
|
|
|
|
##############################################################################
|
|
# Definitions of screen language statements.
|
|
|
|
# Used to allow statements to take styles.
|
|
styles = [ ]
|
|
|
|
# All statements defined, and statements that take children.
|
|
all_statements = [ ]
|
|
childbearing_statements = [ ]
|
|
|
|
position_property_names = [
|
|
"anchor",
|
|
"xanchor",
|
|
"yanchor",
|
|
"pos",
|
|
"xpos",
|
|
"ypos",
|
|
"align",
|
|
"xalign",
|
|
"yalign",
|
|
"xoffset",
|
|
"yoffset",
|
|
"maximum",
|
|
"xmaximum",
|
|
"ymaximum",
|
|
"area",
|
|
"clipping",
|
|
"xfill",
|
|
"yfill",
|
|
# no center, since it can conflict with the center transform.
|
|
"xcenter",
|
|
"ycenter",
|
|
"xsize",
|
|
"ysize",
|
|
"xysize",
|
|
"alt",
|
|
"debug",
|
|
]
|
|
|
|
position_properties = [ Style(i) for i in position_property_names ]
|
|
text_position_properties = [ PrefixStyle("text_", i) for i in position_property_names ]
|
|
side_position_properties = [ PrefixStyle("side_", i) for i in position_property_names ]
|
|
|
|
text_property_names = [
|
|
"antialias",
|
|
"vertical",
|
|
"black_color",
|
|
"bold",
|
|
"color",
|
|
"drop_shadow",
|
|
"drop_shadow_color",
|
|
"first_indent",
|
|
"font",
|
|
"size",
|
|
"hyperlink_functions",
|
|
"italic",
|
|
"justify",
|
|
"kerning",
|
|
"language",
|
|
"layout",
|
|
"line_leading",
|
|
"line_spacing",
|
|
"minwidth",
|
|
"min_width",
|
|
"newline_indent",
|
|
"outlines",
|
|
"rest_indent",
|
|
"ruby_style",
|
|
"slow_cps",
|
|
"slow_cps_multiplier",
|
|
"slow_abortable",
|
|
"strikethrough",
|
|
"text_align",
|
|
"text_y_fudge",
|
|
"underline",
|
|
"minimum",
|
|
"xminimum",
|
|
"yminimum",
|
|
]
|
|
|
|
text_properties = [ Style(i) for i in text_property_names ]
|
|
text_text_properties = [ PrefixStyle("text_", i) for i in text_property_names ]
|
|
|
|
window_properties = [ Style(i) for i in [
|
|
"background",
|
|
"foreground",
|
|
"left_margin",
|
|
"right_margin",
|
|
"bottom_margin",
|
|
"top_margin",
|
|
"xmargin",
|
|
"ymargin",
|
|
"left_padding",
|
|
"right_padding",
|
|
"top_padding",
|
|
"bottom_padding",
|
|
"xpadding",
|
|
"ypadding",
|
|
"size_group",
|
|
"minimum",
|
|
"xminimum",
|
|
"yminimum",
|
|
] ]
|
|
|
|
button_properties = [ Style(i) for i in [
|
|
"sound",
|
|
"mouse",
|
|
"focus_mask",
|
|
"child",
|
|
"keyboard_focus",
|
|
] ]
|
|
|
|
bar_properties = [ Style(i) for i in [
|
|
"bar_vertical",
|
|
"bar_invert",
|
|
"bar_resizing",
|
|
"left_gutter",
|
|
"right_gutter",
|
|
"top_gutter",
|
|
"bottom_gutter",
|
|
"left_bar",
|
|
"right_bar",
|
|
"top_bar",
|
|
"bottom_bar",
|
|
"thumb",
|
|
"thumb_shadow",
|
|
"thumb_offset",
|
|
"mouse",
|
|
"unscrollable",
|
|
"keyboard_focus",
|
|
] ]
|
|
|
|
box_properties = [ Style(i) for i in [
|
|
"box_layout",
|
|
"box_wrap",
|
|
"box_wrap_spacing",
|
|
"box_reverse",
|
|
"order_reverse",
|
|
"spacing",
|
|
"first_spacing",
|
|
"fit_first",
|
|
"minimum",
|
|
"xminimum",
|
|
"yminimum",
|
|
] ]
|
|
|
|
ui_properties = [
|
|
Keyword("at"),
|
|
Keyword("id"),
|
|
Keyword("style"),
|
|
Keyword("style_group"),
|
|
Keyword("focus"),
|
|
Keyword("default"),
|
|
]
|
|
|
|
|
|
def add(thing):
|
|
parser.add(thing)
|
|
|
|
|
|
##############################################################################
|
|
# UI statements.
|
|
|
|
FunctionStatementParser("null", "ui.null", 0)
|
|
Keyword("width")
|
|
Keyword("height")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
|
|
FunctionStatementParser("text", "ui.text", 0, scope=True)
|
|
Positional("text")
|
|
Keyword("slow")
|
|
Keyword("slow_done")
|
|
Keyword("substitute")
|
|
Keyword("scope")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(text_properties)
|
|
|
|
FunctionStatementParser("hbox", "ui.hbox", many)
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(box_properties)
|
|
|
|
FunctionStatementParser("vbox", "ui.vbox", many)
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(box_properties)
|
|
|
|
FunctionStatementParser("fixed", "ui.fixed", many)
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(box_properties)
|
|
|
|
FunctionStatementParser("grid", "ui.grid", many)
|
|
Positional("cols")
|
|
Positional("rows")
|
|
Keyword("transpose")
|
|
Style("spacing")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
FunctionStatementParser("side", "ui.side", many)
|
|
Positional("positions")
|
|
Style("spacing")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
# Omit sizer, as we can always just put an xmaximum and ymaximum on an item.
|
|
|
|
for name in [ "window", "frame" ]:
|
|
FunctionStatementParser(name, "ui." + name, 1)
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(window_properties)
|
|
|
|
FunctionStatementParser("key", "ui.key", 0)
|
|
Positional("key")
|
|
Keyword("action")
|
|
|
|
FunctionStatementParser("timer", "ui.timer", 0)
|
|
Positional("delay")
|
|
Keyword("action")
|
|
Keyword("repeat")
|
|
|
|
# Omit behaviors.
|
|
# Omit menu as being too high-level.
|
|
|
|
FunctionStatementParser("input", "ui.input", 0)
|
|
Keyword("default")
|
|
Keyword("length")
|
|
Keyword("allow")
|
|
Keyword("exclude")
|
|
Keyword("copypaste")
|
|
Keyword("prefix")
|
|
Keyword("suffix")
|
|
Keyword("changed")
|
|
Keyword("pixel_width")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(text_properties)
|
|
|
|
FunctionStatementParser("image", "ui.image", 0)
|
|
Positional("im")
|
|
|
|
# Omit imagemap_compat for being too high level (and obsolete).
|
|
|
|
FunctionStatementParser("button", "ui.button", 1)
|
|
Keyword("action")
|
|
Keyword("clicked")
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
Keyword("alternate")
|
|
Keyword("selected")
|
|
Keyword("sensitive")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(window_properties)
|
|
add(button_properties)
|
|
|
|
FunctionStatementParser("imagebutton", "ui.imagebutton", 0)
|
|
Keyword("auto")
|
|
Keyword("idle")
|
|
Keyword("hover")
|
|
Keyword("insensitive")
|
|
Keyword("selected_idle")
|
|
Keyword("selected_hover")
|
|
Keyword("selected_insensitive")
|
|
Keyword("action")
|
|
Keyword("clicked")
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
Keyword("alternate")
|
|
Keyword("image_style")
|
|
Keyword("selected")
|
|
Keyword("sensitive")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(window_properties)
|
|
add(button_properties)
|
|
|
|
FunctionStatementParser("textbutton", "ui.textbutton", 0, scope=True)
|
|
Positional("label")
|
|
Keyword("action")
|
|
Keyword("clicked")
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
Keyword("alternate")
|
|
Keyword("text_style")
|
|
Keyword("substitute")
|
|
Keyword("scope")
|
|
Keyword("selected")
|
|
Keyword("sensitive")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(window_properties)
|
|
add(button_properties)
|
|
add(text_position_properties)
|
|
add(text_text_properties)
|
|
|
|
FunctionStatementParser("label", "ui.label", 0, scope=True)
|
|
Positional("label")
|
|
Keyword("text_style")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(window_properties)
|
|
add(text_position_properties)
|
|
add(text_text_properties)
|
|
|
|
for name in [ "bar", "vbar" ]:
|
|
FunctionStatementParser(name, "ui." + name, 0)
|
|
Keyword("adjustment")
|
|
Keyword("range")
|
|
Keyword("value")
|
|
Keyword("changed")
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(bar_properties)
|
|
|
|
# Omit autobar. (behavior)
|
|
|
|
FunctionStatementParser("viewport", "ui.viewport", 1)
|
|
Keyword("child_size")
|
|
Keyword("mousewheel")
|
|
Keyword("arrowkeys")
|
|
Keyword("draggable")
|
|
Keyword("edgescroll")
|
|
Keyword("xadjustment")
|
|
Keyword("yadjustment")
|
|
Keyword("xinitial")
|
|
Keyword("yinitial")
|
|
Keyword("scrollbars")
|
|
PrefixStyle("side_", "spacing")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(side_position_properties)
|
|
|
|
# Omit conditional. (behavior)
|
|
|
|
FunctionStatementParser("imagemap", "ui.imagemap", many)
|
|
Keyword("ground")
|
|
Keyword("hover")
|
|
Keyword("insensitive")
|
|
Keyword("idle")
|
|
Keyword("selected_hover")
|
|
Keyword("selected_idle")
|
|
Keyword("selected_insensitive")
|
|
Keyword("auto")
|
|
Keyword("alpha")
|
|
Keyword("cache")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
FunctionStatementParser("hotspot", "ui.hotspot_with_child", 1)
|
|
Positional("spot")
|
|
Keyword("action")
|
|
Keyword("clicked")
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(window_properties)
|
|
add(button_properties)
|
|
|
|
FunctionStatementParser("hotbar", "ui.hotbar", 0)
|
|
Positional("spot")
|
|
Keyword("adjustment")
|
|
Keyword("range")
|
|
Keyword("value")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
add(bar_properties)
|
|
|
|
|
|
FunctionStatementParser("transform", "ui.transform", 1)
|
|
Keyword("at")
|
|
Keyword("id")
|
|
for i in renpy.atl.PROPERTIES:
|
|
Style(i)
|
|
|
|
FunctionStatementParser("add", "ui.add", 0)
|
|
Positional("im")
|
|
Keyword("at")
|
|
Keyword("id")
|
|
for i in renpy.atl.PROPERTIES:
|
|
Style(i)
|
|
|
|
FunctionStatementParser("on", "ui.on", 0)
|
|
Positional("event")
|
|
Keyword("action")
|
|
|
|
FunctionStatementParser("drag", "ui.drag", 1)
|
|
Keyword("drag_name")
|
|
Keyword("draggable")
|
|
Keyword("droppable")
|
|
Keyword("drag_raise")
|
|
Keyword("dragged")
|
|
Keyword("dropped")
|
|
Keyword("drop_allowable")
|
|
Keyword("drag_handle")
|
|
Keyword("drag_joined")
|
|
Keyword("clicked")
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
Keyword("mouse_drop")
|
|
Style("child")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
FunctionStatementParser("draggroup", "ui.draggroup", many)
|
|
Keyword("min_overlap")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
FunctionStatementParser("mousearea", "ui.mousearea", 0)
|
|
Keyword("hovered")
|
|
Keyword("unhovered")
|
|
Style("focus_mask")
|
|
add(ui_properties)
|
|
add(position_properties)
|
|
|
|
|
|
##############################################################################
|
|
# Control-flow statements.
|
|
|
|
class PassParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(PassParser, self).__init__(name)
|
|
|
|
def parse(self, l, name):
|
|
return self.parse_exec("pass", l.number)
|
|
|
|
|
|
PassParser("pass")
|
|
|
|
|
|
class DefaultParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(DefaultParser, self).__init__(name)
|
|
|
|
def parse(self, l, name):
|
|
|
|
name = l.require(l.word)
|
|
l.require(r'=')
|
|
rest = l.rest()
|
|
|
|
code = "_scope.setdefault(%r, (%s))" % (name, rest)
|
|
|
|
return self.parse_exec(code, l.number)
|
|
|
|
|
|
DefaultParser("default")
|
|
|
|
|
|
class UseParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(UseParser, self).__init__(name)
|
|
childbearing_statements.append(self)
|
|
|
|
def parse(self, l, name):
|
|
|
|
lineno = l.number
|
|
|
|
target_name = l.require(l.word)
|
|
|
|
code = "renpy.use_screen(%r" % target_name
|
|
|
|
args = renpy.parser.parse_arguments(l)
|
|
|
|
if args:
|
|
|
|
for k, v in args.arguments:
|
|
if k is None:
|
|
code += ", (%s)" % v
|
|
else:
|
|
code += ", %s=(%s)" % (k, v)
|
|
|
|
code += ", _name=%s, _scope=_scope" % name
|
|
|
|
if args:
|
|
|
|
if args.extrapos:
|
|
code += ", *(%s)" % args.extrapos
|
|
|
|
if args.extrakw:
|
|
code += ", **(%s)" % args.extrakw
|
|
|
|
code += ")"
|
|
|
|
return self.parse_exec(code, lineno)
|
|
|
|
|
|
UseParser("use")
|
|
|
|
|
|
class IfParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(IfParser, self).__init__(name)
|
|
childbearing_statements.append(self)
|
|
|
|
def parse(self, l, name):
|
|
|
|
with new_variable() as child_name:
|
|
|
|
count = 0
|
|
|
|
lineno = l.number
|
|
condition = self.parse_eval(l.require(l.python_expression), lineno)
|
|
|
|
l.require(':')
|
|
l.expect_eol()
|
|
|
|
body = self.parse_exec("%s = (%s, %d)" % (child_name, name, count))
|
|
body.extend(self.parse_children('if', l, child_name))
|
|
|
|
orelse = [ ]
|
|
|
|
rv = ast.If(test=condition, body=body, orelse=orelse, lineno=lineno, col_offset=0)
|
|
|
|
count += 1
|
|
|
|
state = l.checkpoint()
|
|
|
|
while l.advance():
|
|
|
|
old_orelse = orelse
|
|
lineno = l.number
|
|
|
|
if l.keyword("elif"):
|
|
condition = self.parse_eval(l.require(l.python_expression), lineno)
|
|
|
|
body = self.parse_exec("%s = (%s, %d)" % (child_name, name, count))
|
|
body.extend(self.parse_children('if', l, child_name))
|
|
|
|
orelse = [ ]
|
|
old_orelse.append(ast.If(test=condition, body=body, orelse=orelse, lineno=lineno, col_offset=0))
|
|
|
|
count += 1
|
|
|
|
state = l.checkpoint()
|
|
|
|
elif l.keyword("else"):
|
|
|
|
old_orelse.extend(self.parse_exec("%s = (%s, %d)" % (child_name, name, count)))
|
|
old_orelse.extend(self.parse_children('if', l, child_name))
|
|
|
|
break
|
|
|
|
else:
|
|
l.revert(state)
|
|
break
|
|
|
|
return [ rv ]
|
|
|
|
|
|
IfParser("if")
|
|
|
|
|
|
class ForParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(ForParser, self).__init__(name)
|
|
childbearing_statements.append(self)
|
|
|
|
def parse_tuple_pattern(self, l):
|
|
|
|
is_tuple = False
|
|
pattern = [ ]
|
|
|
|
while True:
|
|
|
|
lineno = l.number
|
|
|
|
if l.match(r"\("):
|
|
p = self.parse_tuple_pattern(l)
|
|
else:
|
|
p = l.name().encode("utf-8")
|
|
|
|
if not p:
|
|
break
|
|
|
|
pattern.append(ast.Name(id=p, ctx=ast.Store(), lineno=lineno, col_offset=0))
|
|
|
|
if l.match(r","):
|
|
is_tuple = True
|
|
else:
|
|
break
|
|
|
|
if not pattern:
|
|
l.error("Expected tuple pattern.")
|
|
|
|
if not is_tuple:
|
|
return pattern[0]
|
|
else:
|
|
return ast.Tuple(elts=pattern, ctx=ast.Store())
|
|
|
|
def parse(self, l, name):
|
|
|
|
lineno = l.number
|
|
|
|
pattern = self.parse_tuple_pattern(l)
|
|
|
|
l.require('in')
|
|
|
|
expression = self.parse_eval(l.require(l.python_expression), l.number)
|
|
|
|
l.require(':')
|
|
l.expect_eol()
|
|
|
|
with new_variable() as counter_name:
|
|
|
|
with new_variable() as child_name:
|
|
|
|
children = self.parse_exec("%s = (%s, %s)" % (child_name, name, counter_name))
|
|
children.extend(self.parse_children('for', l, child_name))
|
|
children.extend(self.parse_exec("%s += 1" % counter_name))
|
|
|
|
rv = self.parse_exec("%s = 0" % counter_name)
|
|
|
|
rv.append(ast.For(
|
|
target=pattern,
|
|
iter=expression,
|
|
body=children,
|
|
orelse=[],
|
|
lineno=lineno,
|
|
col_offset=0))
|
|
|
|
return rv
|
|
|
|
|
|
ForParser("for")
|
|
|
|
|
|
class PythonParser(Parser):
|
|
|
|
def __init__(self, name, one_line):
|
|
super(PythonParser, self).__init__(name)
|
|
|
|
self.one_line = one_line
|
|
|
|
def parse(self, l, name):
|
|
lineno = l.number
|
|
|
|
if self.one_line:
|
|
python_code = l.rest()
|
|
l.expect_noblock('one-line python statement')
|
|
else:
|
|
l.require(':')
|
|
l.expect_block('python block')
|
|
|
|
python_code = l.python_block()
|
|
lineno += 1
|
|
|
|
return self.parse_exec(python_code, lineno)
|
|
|
|
|
|
PythonParser("$", True)
|
|
PythonParser("python", False)
|
|
|
|
|
|
##############################################################################
|
|
# Add all_statements to the statements that take children.
|
|
|
|
for i in childbearing_statements:
|
|
i.add(all_statements)
|
|
|
|
##############################################################################
|
|
# Definition of the screen statement.
|
|
|
|
# class ScreenFunction(renpy.object.Object):
|
|
|
|
# def __init__(self, children):
|
|
# self.children = children
|
|
|
|
# def __call__(self, _name=(), _scope=None, **kwargs):
|
|
|
|
# for i, child in enumerate(self.children):
|
|
# child.evaluate(_name + (i,), _scope)
|
|
|
|
# def screen_function(positional, keyword, children):
|
|
# name = renpy.python.py_eval(positional[0].source)
|
|
# function = ScreenFunction(children)
|
|
|
|
# values = {
|
|
# "name" : name,
|
|
# "function" : function,
|
|
# }
|
|
|
|
# for k, v in keyword.iteritems():
|
|
# values[k] = renpy.python.py_eval(v.source)
|
|
|
|
# return values
|
|
|
|
|
|
# screen_stmt = FunctionStatementParser("screen", screen_function, unevaluated=True)
|
|
# Positional("name", Word)
|
|
# Keyword("modal", Expression)
|
|
# Keyword("zorder", Expression)
|
|
# Keyword("tag", Word)
|
|
# add(all_statements)
|
|
|
|
class ScreenLangScreen(renpy.object.Object):
|
|
"""
|
|
This represents a screen defined in the screen language.
|
|
"""
|
|
|
|
__version__ = 1
|
|
|
|
variant = "None"
|
|
|
|
# Predict should be false for screens created before
|
|
# prediction existed.
|
|
predict = "False"
|
|
|
|
parameters = None
|
|
location = None
|
|
|
|
def __init__(self):
|
|
|
|
# The name of the screen.
|
|
self.name = name
|
|
|
|
# Should this screen be declared as modal?
|
|
self.modal = "False"
|
|
|
|
# The screen's zorder.
|
|
self.zorder = "0"
|
|
|
|
# The screen's tag.
|
|
self.tag = None
|
|
|
|
# The PyCode object containing the screen's code.
|
|
self.code = None
|
|
|
|
# The variant of screen we're defining.
|
|
self.variant = "None" # expr.
|
|
|
|
# Should we predict this screen?
|
|
self.predict = "None" # expr.
|
|
|
|
# The parameters this screen takes.
|
|
self.parameters = None
|
|
|
|
def after_upgrade(self, version):
|
|
if version < 1:
|
|
self.modal = "False"
|
|
self.zorder = "0"
|
|
|
|
def define(self, location):
|
|
"""
|
|
Defines a screen.
|
|
"""
|
|
|
|
renpy.display.screen.define_screen(
|
|
self.name,
|
|
self,
|
|
modal=self.modal,
|
|
zorder=self.zorder,
|
|
tag=self.tag,
|
|
variant=renpy.python.py_eval(self.variant),
|
|
predict=renpy.python.py_eval(self.predict),
|
|
parameters=self.parameters,
|
|
location=self.location,
|
|
)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
scope = kwargs["_scope"]
|
|
|
|
if self.parameters:
|
|
|
|
args = scope.get("_args", ())
|
|
kwargs = scope.get("_kwargs", { })
|
|
|
|
values = renpy.ast.apply_arguments(self.parameters, args, kwargs)
|
|
scope.update(values)
|
|
|
|
renpy.python.py_exec_bytecode(self.code.bytecode, locals=scope)
|
|
|
|
|
|
class ScreenParser(Parser):
|
|
|
|
def __init__(self):
|
|
super(ScreenParser, self).__init__("screen")
|
|
|
|
def parse(self, l, name="_name"):
|
|
|
|
location = l.get_location()
|
|
screen = ScreenLangScreen()
|
|
|
|
def parse_keyword(l):
|
|
if l.match('modal'):
|
|
screen.modal = l.require(l.simple_expression)
|
|
return True
|
|
|
|
if l.match('zorder'):
|
|
screen.zorder = l.require(l.simple_expression)
|
|
return True
|
|
|
|
if l.match('tag'):
|
|
screen.tag = l.require(l.word)
|
|
return True
|
|
|
|
if l.match('variant'):
|
|
screen.variant = l.require(l.simple_expression)
|
|
return True
|
|
|
|
if l.match('predict'):
|
|
screen.predict = l.require(l.simple_expression)
|
|
return True
|
|
|
|
return False
|
|
|
|
lineno = l.number
|
|
|
|
screen.name = l.require(l.word)
|
|
screen.parameters = renpy.parser.parse_parameters(l)
|
|
|
|
while parse_keyword(l):
|
|
continue
|
|
|
|
l.require(':')
|
|
l.expect_eol()
|
|
l.expect_block('screen statement')
|
|
|
|
l = l.subblock_lexer()
|
|
|
|
rv = [ ]
|
|
count = 0
|
|
|
|
with new_variable() as child_name:
|
|
|
|
while l.advance():
|
|
|
|
if parse_keyword(l):
|
|
while parse_keyword(l):
|
|
continue
|
|
|
|
l.expect_eol()
|
|
continue
|
|
|
|
rv.extend(self.parse_exec("%s = (%s, %d)" % (child_name, name, count), l.number))
|
|
|
|
c = self.parse_statement(l, child_name)
|
|
|
|
if c is None:
|
|
l.error('Expected a screen language statement.')
|
|
|
|
rv.extend(c)
|
|
count += 1
|
|
|
|
node = ast.Module(body=rv, lineno=lineno, col_offset=0)
|
|
ast.fix_missing_locations(node)
|
|
LineNumberNormalizer().visit(node)
|
|
|
|
# Various bits of debugging code:
|
|
|
|
# print ast.dump(node, True, True)
|
|
|
|
# a = compile(node, 'foo', 'exec')
|
|
# import dis
|
|
# dis.dis(a)
|
|
|
|
# import unparse
|
|
# print
|
|
# print screen.name, "-----------------------------------------"
|
|
# unparse.Unparser(node)
|
|
|
|
screen.code = renpy.ast.PyCode(node, location, 'exec')
|
|
|
|
return screen
|
|
|
|
|
|
screen_parser = ScreenParser()
|
|
screen_parser.add(all_statements)
|
|
|
|
|
|
def parse_screen(l):
|
|
"""
|
|
Parses the screen statement.
|
|
"""
|
|
|
|
global filename
|
|
|
|
filename = l.filename
|
|
|
|
screen = screen_parser.parse(l)
|
|
return screen
|