1046 lines
28 KiB
Python
1046 lines
28 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 collections
|
|
import renpy.sl2
|
|
import renpy.sl2.slast as slast
|
|
|
|
from ast import literal_eval
|
|
|
|
# A tuple of style prefixes that we know of.
|
|
STYLE_PREFIXES = [
|
|
'',
|
|
'insensitive_',
|
|
'hover_',
|
|
'idle_',
|
|
'activate_',
|
|
'selected_',
|
|
'selected_insensitive_',
|
|
'selected_hover_',
|
|
'selected_idle_',
|
|
'selected_activate_',
|
|
]
|
|
|
|
##############################################################################
|
|
# Parsing.
|
|
|
|
# The parser that things are being added to.
|
|
parser = None
|
|
|
|
# All statements we know about.
|
|
all_statements = [ ]
|
|
|
|
# Statements that can contain children.
|
|
childbearing_statements = set()
|
|
|
|
|
|
class Positional(object):
|
|
"""
|
|
This represents a positional parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
# This is a map from (prefix, use_style_prefixes) to a set of property names.
|
|
properties = collections.defaultdict(set)
|
|
|
|
|
|
class Keyword(object):
|
|
"""
|
|
This represents an optional keyword parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
properties['', False].add(name)
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
class Style(object):
|
|
"""
|
|
This represents a style parameter to a function.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
properties['', True].add(self.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
|
|
|
|
properties[prefix, True].add(self.name)
|
|
|
|
if parser:
|
|
parser.add(self)
|
|
|
|
|
|
class Parser(object):
|
|
|
|
# The number of children this statement takes, out of 0, 1, or "many".
|
|
# This defaults to "many" so the has statement errors out when not
|
|
# inside something that takes a single child.
|
|
nchildren = "many"
|
|
|
|
def __init__(self, name, statement=True):
|
|
|
|
# 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 = { }
|
|
|
|
# True if this parser takes "as".
|
|
self.variable = False
|
|
|
|
if statement:
|
|
all_statements.append(self)
|
|
|
|
global parser
|
|
parser = 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, loc, l, layout_mode=False, keyword=True):
|
|
word = l.word() or l.match(r'\$')
|
|
|
|
if word and word in self.children:
|
|
if layout_mode:
|
|
c = self.children[word].parse_layout(loc, l, self, keyword)
|
|
else:
|
|
c = self.children[word].parse(loc, l, self, keyword)
|
|
|
|
return c
|
|
else:
|
|
return None
|
|
|
|
def parse_layout(self, loc, l, parent, keyword):
|
|
l.error("The %s statement cannot be used as a container for the has statement." % self.name)
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
"""
|
|
This is expected to parse a function statement, and to return
|
|
a list of python ast statements.
|
|
|
|
`loc`
|
|
The location of the current statement.
|
|
|
|
`l`
|
|
The lexer.
|
|
|
|
`parent`
|
|
The parent Parser of the current statement.
|
|
"""
|
|
|
|
raise Exception("Not Implemented")
|
|
|
|
def parse_contents(self, l, target, layout_mode=False, can_has=False, can_tag=False, block_only=False, keyword=True):
|
|
"""
|
|
Parses the remainder of the current line of `l`, and all of its subblock,
|
|
looking for keywords and children.
|
|
|
|
`layout_mode`
|
|
If true, parsing continues to the end of `l`, rather than stopping
|
|
with the end of the first logical line.
|
|
|
|
`can_has`
|
|
If true, we should parse layouts.
|
|
|
|
`can_tag`
|
|
If true, we should parse the ``tag`` keyword, as it's used by
|
|
screens.
|
|
|
|
`block_only`
|
|
If true, only parse the block and not the initial properties.
|
|
"""
|
|
|
|
seen_keywords = set()
|
|
block = False
|
|
|
|
# Parses a keyword argument from the lexer.
|
|
def parse_keyword(l, expect, first_line):
|
|
name = l.word()
|
|
|
|
if name is None:
|
|
l.error(expect)
|
|
|
|
if can_tag and name == "tag":
|
|
if target.tag is not None:
|
|
l.error('keyword argument %r appears more than once in a %s statement.' % (name, self.name))
|
|
|
|
target.tag = l.require(l.word)
|
|
l.expect_noblock(name)
|
|
return True
|
|
|
|
if self.variable:
|
|
if name == "as":
|
|
if target.variable is not None:
|
|
l.error('an as clause may only appear once in a %s statement.' % (self.name,))
|
|
|
|
target.variable = l.require(l.word)
|
|
return
|
|
|
|
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)
|
|
|
|
if name == "at" and block and l.keyword("transform"):
|
|
l.require(":")
|
|
l.expect_eol()
|
|
l.expect_block("ATL block")
|
|
expr = renpy.atl.parse_atl(l.subblock_lexer())
|
|
target.atl_transform = expr
|
|
return
|
|
|
|
expr = l.comma_expression()
|
|
|
|
if (not keyword) and (not renpy.config.keyword_after_python):
|
|
try:
|
|
literal_eval(expr)
|
|
except:
|
|
l.error("a non-constant keyword argument like '%s %s' is not allowed after a python block." % (name, expr))
|
|
|
|
target.keyword.append((name, expr))
|
|
|
|
if not first_line:
|
|
l.expect_noblock(name)
|
|
|
|
if block_only:
|
|
l.expect_eol()
|
|
l.expect_block(self.name)
|
|
block = True
|
|
|
|
else:
|
|
|
|
# If not block_only, 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.', True)
|
|
|
|
# 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)
|
|
|
|
# If we have a block, parse it. This also takes care of parsing the
|
|
# block after a has clause.
|
|
|
|
for l in lexers:
|
|
|
|
while l.advance():
|
|
|
|
state = l.checkpoint()
|
|
loc = l.get_location()
|
|
|
|
if l.keyword(r'has'):
|
|
if not can_has:
|
|
l.error("The has statement is not allowed here.")
|
|
|
|
if target.has_noncondition_child():
|
|
l.error("The has statement may not be given after a child has been supplied.")
|
|
|
|
c = self.parse_statement(loc, l, layout_mode=True, keyword=keyword)
|
|
|
|
if c is None:
|
|
l.error('Has expects a child statement.')
|
|
|
|
target.children.append(c)
|
|
|
|
if c.has_python():
|
|
keyword = False
|
|
|
|
continue
|
|
|
|
c = self.parse_statement(loc, l)
|
|
|
|
# Ignore passes.
|
|
if isinstance(c, slast.SLPass):
|
|
continue
|
|
|
|
# If not none, add the child to our AST.
|
|
if c is not None:
|
|
target.children.append(c)
|
|
|
|
if c.has_python():
|
|
keyword = False
|
|
|
|
continue
|
|
|
|
l.revert(state)
|
|
|
|
if not l.eol():
|
|
parse_keyword(l, "expected a keyword argument or child statement.", False)
|
|
|
|
while not l.eol():
|
|
parse_keyword(l, "expected a keyword argument or end of line.", False)
|
|
|
|
def add_positional(self, name):
|
|
global parser
|
|
parser = self
|
|
|
|
Positional(name)
|
|
return self
|
|
|
|
def add_property(self, name):
|
|
global parser
|
|
parser = self
|
|
|
|
Keyword(name)
|
|
return self
|
|
|
|
def add_style_property(self, name):
|
|
global parser
|
|
parser = self
|
|
|
|
Style(name)
|
|
return self
|
|
|
|
def add_prefix_style_property(self, prefix, name):
|
|
global parser
|
|
parser = self
|
|
|
|
PrefixStyle(prefix, name)
|
|
return self
|
|
|
|
def add_property_group(self, group, prefix=''):
|
|
global parser
|
|
parser = self
|
|
|
|
if group not in renpy.sl2.slproperties.property_groups:
|
|
raise Exception("{!r} is not a known property group.".format(group))
|
|
|
|
for prop in renpy.sl2.slproperties.property_groups[group]:
|
|
if isinstance(prop, Keyword):
|
|
Keyword(prefix + prop.name)
|
|
else:
|
|
PrefixStyle(prefix, prop.name)
|
|
|
|
return self
|
|
|
|
|
|
def add(thing):
|
|
parser.add(thing)
|
|
|
|
|
|
# A singleton value.
|
|
many = renpy.object.Sentinel("many")
|
|
|
|
|
|
def register_sl_displayable(*args, **kwargs):
|
|
"""
|
|
:doc: custom_sl class
|
|
:args: (name, displayable, style, nchildren=0, scope=False, replaces=False, default_keywords={}, default_properties=True)
|
|
|
|
Registers a screen language statement that creates a displayable.
|
|
|
|
`name`
|
|
The name of the screen language statement, a string containing a Ren'Py
|
|
keyword. This keyword is used to introduce the new statement.
|
|
|
|
`displayable`
|
|
This is a function that, when called, returns a displayable
|
|
object. All position arguments, properties, and style properties
|
|
are passed as arguments to this function. Other keyword arguments
|
|
are also given to this function, a described below.
|
|
|
|
This must return a Displayable. If it returns multiple displayables,
|
|
the _main attribute of the outermost displayable should be set to
|
|
the "main" displayable - the one that children should be added
|
|
to.
|
|
|
|
`style`
|
|
The base name of the style of this displayable. If the style property
|
|
is not given, this will have the style prefix added to it. The
|
|
computed style is passed to the `displayable` function as the
|
|
``style`` keyword argument.
|
|
|
|
`nchildren`
|
|
The number of children of this displayable. One of:
|
|
|
|
0
|
|
The displayable takes no children.
|
|
1
|
|
The displayable takes 1 child. If more than one child is given,
|
|
the children are placed in a Fixed.
|
|
"many"
|
|
The displayable takes more than one child.
|
|
|
|
|
|
The following arguments should be passed in using keyword arguments:
|
|
|
|
`replaces`
|
|
If true, and the displayable replaces a prior displayable, that displayable
|
|
is passed as a parameter to the new displayable.
|
|
|
|
`default_keywords`
|
|
The default set of keyword arguments to supply to the displayable.
|
|
|
|
`default_properties`
|
|
If true, the ui and position properties are added by default.
|
|
|
|
Returns an object that can have positional arguments and properties
|
|
added to it by calling the following methods. Each of these methods
|
|
returns the object it is called on, allowing methods to be chained
|
|
together.
|
|
|
|
.. method:: add_positional(name)
|
|
|
|
Adds a positional argument with `name`
|
|
|
|
.. method:: add_property(name)
|
|
|
|
Adds a property with `name`. Properties are passed as keyword
|
|
arguments.
|
|
|
|
.. method:: add_style_property(name)
|
|
|
|
Adds a family of properties, ending with `name` and prefixed with
|
|
the various style property prefixes. For example, if called with
|
|
("size"), this will define size, idle_size, hover_size, etc.
|
|
|
|
.. method:: add_prefix_style_property(prefix, name)
|
|
|
|
Adds a family of properties with names consisting of `prefix`,
|
|
a style property prefix, and `name`. For example, if called
|
|
with a prefix of `text_` and a name of `size`, this will
|
|
create text_size, text_idle_size, text_hover_size, etc.
|
|
|
|
.. method:: add_property_group(group, prefix='')
|
|
|
|
Adds a group of properties, prefixed with `prefix`. `Group` may
|
|
be one of the strings:
|
|
|
|
* "bar"
|
|
* "box"
|
|
* "button"
|
|
* "position"
|
|
* "text"
|
|
* "window"
|
|
|
|
These correspond to groups of :ref:`style-properties`. Group can
|
|
also be "ui", in which case it adds the :ref:`common ui properties <common-properties>`.
|
|
"""
|
|
|
|
rv = DisplayableParser(*args, **kwargs)
|
|
|
|
for i in childbearing_statements:
|
|
i.add(rv)
|
|
|
|
screen_parser.add(rv)
|
|
|
|
if rv.nchildren != 0:
|
|
childbearing_statements.add(rv)
|
|
|
|
for i in all_statements:
|
|
rv.add(i)
|
|
|
|
rv.add(if_statement)
|
|
|
|
return rv
|
|
|
|
|
|
class DisplayableParser(Parser):
|
|
|
|
def __init__(self, name, displayable, style, nchildren=0, scope=False,
|
|
pass_context=False, imagemap=False, replaces=False, default_keywords={},
|
|
hotspot=False, default_properties=True):
|
|
"""
|
|
`scope`
|
|
If true, the scope is passed into the displayable functionas a keyword
|
|
argument named "scope".
|
|
|
|
`pass_context`
|
|
If true, the context is passed as the first positional argument of the
|
|
displayable.
|
|
|
|
`imagemap`
|
|
If true, the displayable is treated as defining an imagemap. (The imagemap
|
|
is added to and removed from renpy.ui.imagemap_stack as appropriate.)
|
|
|
|
`hotspot`
|
|
If true, the displayable is treated as a hotspot. (It needs to be
|
|
re-created if the imagemap it belongs to has changed.)
|
|
|
|
`default_properties`
|
|
If true, the ui and positional properties are added by default.
|
|
"""
|
|
|
|
super(DisplayableParser, self).__init__(name)
|
|
|
|
# The displayable that is called when this statement runs.
|
|
self.displayable = displayable
|
|
|
|
if nchildren == "many":
|
|
nchildren = many
|
|
|
|
# The number of children we have.
|
|
self.nchildren = nchildren
|
|
|
|
if nchildren != 0:
|
|
childbearing_statements.add(self)
|
|
|
|
self.style = style
|
|
self.scope = scope
|
|
self.pass_context = pass_context
|
|
self.imagemap = imagemap
|
|
self.hotspot = hotspot
|
|
self.replaces = replaces
|
|
self.default_keywords = default_keywords
|
|
self.variable = True
|
|
|
|
Keyword("arguments")
|
|
Keyword("properties")
|
|
|
|
if default_properties:
|
|
add(renpy.sl2.slproperties.ui_properties)
|
|
add(renpy.sl2.slproperties.position_properties)
|
|
|
|
def parse_layout(self, loc, l, parent, keyword):
|
|
return self.parse(loc, l, parent, keyword, layout_mode=True)
|
|
|
|
def parse(self, loc, l, parent, keyword, layout_mode=False):
|
|
|
|
rv = slast.SLDisplayable(
|
|
loc,
|
|
self.displayable,
|
|
scope=self.scope,
|
|
child_or_fixed=(self.nchildren == 1),
|
|
style=self.style,
|
|
pass_context=self.pass_context,
|
|
imagemap=self.imagemap,
|
|
replaces=self.replaces,
|
|
default_keywords=self.default_keywords,
|
|
hotspot=self.hotspot,
|
|
)
|
|
|
|
for _i in self.positional:
|
|
expr = l.simple_expression()
|
|
|
|
if expr is None:
|
|
break
|
|
|
|
rv.positional.append(expr)
|
|
|
|
can_has = (self.nchildren == 1)
|
|
self.parse_contents(l, rv, layout_mode=layout_mode, can_has=can_has, can_tag=False)
|
|
|
|
if len(rv.positional) != len(self.positional):
|
|
for i in rv.keyword:
|
|
if i[0] == 'arguments':
|
|
break
|
|
else:
|
|
l.error("{} statement expects {} positional arguments, got {}.".format(self.name, len(self.positional), len(rv.positional)))
|
|
|
|
return rv
|
|
|
|
|
|
class IfParser(Parser):
|
|
|
|
def __init__(self, name, node_type, parent_contents):
|
|
"""
|
|
`node_type`
|
|
The type of node to create.
|
|
|
|
`parent_contents`
|
|
If true, our children must be children of our parent. Otherwise,
|
|
our children must be children of ourself.
|
|
"""
|
|
|
|
super(IfParser, self).__init__(name)
|
|
|
|
self.node_type = node_type
|
|
self.parent_contents = parent_contents
|
|
|
|
if not parent_contents:
|
|
childbearing_statements.add(self)
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
if self.parent_contents:
|
|
contents_from = parent
|
|
else:
|
|
contents_from = self
|
|
|
|
rv = self.node_type(loc)
|
|
|
|
condition = l.require(l.python_expression)
|
|
|
|
l.require(':')
|
|
|
|
block = slast.SLBlock(loc)
|
|
contents_from.parse_contents(l, block, block_only=True)
|
|
|
|
rv.entries.append((condition, block))
|
|
|
|
state = l.checkpoint()
|
|
|
|
while l.advance():
|
|
|
|
loc = l.get_location()
|
|
|
|
if l.keyword("elif"):
|
|
|
|
condition = l.require(l.python_expression)
|
|
l.require(':')
|
|
|
|
block = slast.SLBlock(loc)
|
|
contents_from.parse_contents(l, block, block_only=True, keyword=keyword)
|
|
|
|
rv.entries.append((condition, block))
|
|
|
|
state = l.checkpoint()
|
|
|
|
elif l.keyword("else"):
|
|
|
|
condition = None
|
|
l.require(':')
|
|
|
|
block = slast.SLBlock(loc)
|
|
contents_from.parse_contents(l, block, block_only=True, keyword=keyword)
|
|
|
|
rv.entries.append((condition, block))
|
|
|
|
state = l.checkpoint()
|
|
|
|
break
|
|
|
|
else:
|
|
l.revert(state)
|
|
break
|
|
|
|
return rv
|
|
|
|
|
|
if_statement = IfParser("if", slast.SLIf, True)
|
|
IfParser("showif", slast.SLShowIf, False)
|
|
|
|
|
|
class ForParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(ForParser, self).__init__(name)
|
|
childbearing_statements.add(self)
|
|
|
|
def name_or_tuple_pattern(self, l):
|
|
"""
|
|
Matches either a name or a tuple pattern. If a single name is being
|
|
matched, returns it. Otherwise, returns None.
|
|
"""
|
|
|
|
name = None
|
|
pattern = False
|
|
|
|
while True:
|
|
|
|
if l.match(r"\("):
|
|
name = self.name_or_tuple_pattern(l)
|
|
l.require(r'\)')
|
|
pattern = True
|
|
else:
|
|
name = l.name()
|
|
|
|
if name is None:
|
|
break
|
|
|
|
if l.match(r","):
|
|
pattern = True
|
|
else:
|
|
break
|
|
|
|
if pattern:
|
|
return None
|
|
|
|
if name is not None:
|
|
return name
|
|
|
|
l.error("expected variable or tuple pattern.")
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
l.skip_whitespace()
|
|
|
|
tuple_start = l.pos
|
|
name = self.name_or_tuple_pattern(l)
|
|
|
|
if not name:
|
|
name = "_sl2_i"
|
|
pattern = l.text[tuple_start:l.pos]
|
|
stmt = pattern + " = " + name
|
|
code = renpy.ast.PyCode(stmt, loc)
|
|
else:
|
|
code = None
|
|
|
|
if l.match('index'):
|
|
index_expression = l.require(l.say_expression)
|
|
else:
|
|
index_expression = None
|
|
|
|
l.require('in')
|
|
|
|
expression = l.require(l.python_expression)
|
|
|
|
l.require(':')
|
|
l.expect_eol()
|
|
|
|
rv = slast.SLFor(loc, name, expression, index_expression)
|
|
|
|
if code:
|
|
rv.children.append(slast.SLPython(loc, code))
|
|
|
|
self.parse_contents(l, rv, block_only=True)
|
|
|
|
return rv
|
|
|
|
|
|
ForParser("for")
|
|
|
|
|
|
class OneLinePythonParser(Parser):
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
loc = l.get_location()
|
|
source = l.require(l.rest_statement)
|
|
|
|
l.expect_eol()
|
|
l.expect_noblock("one-line python")
|
|
|
|
code = renpy.ast.PyCode(source, loc)
|
|
return slast.SLPython(loc, code)
|
|
|
|
|
|
OneLinePythonParser("$")
|
|
|
|
|
|
class MultiLinePythonParser(Parser):
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
loc = l.get_location()
|
|
|
|
l.require(':')
|
|
|
|
l.expect_eol()
|
|
l.expect_block("python block")
|
|
|
|
source = l.python_block()
|
|
|
|
code = renpy.ast.PyCode(source, loc)
|
|
return slast.SLPython(loc, code)
|
|
|
|
|
|
MultiLinePythonParser("python")
|
|
|
|
|
|
class PassParser(Parser):
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
l.expect_eol()
|
|
|
|
return slast.SLPass(loc)
|
|
|
|
|
|
PassParser("pass")
|
|
|
|
|
|
class DefaultParser(Parser):
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
name = l.require(l.word)
|
|
l.require(r'=')
|
|
rest = l.rest()
|
|
|
|
l.expect_eol()
|
|
l.expect_noblock('default statement')
|
|
|
|
return slast.SLDefault(loc, name, rest)
|
|
|
|
|
|
DefaultParser("default")
|
|
|
|
|
|
class UseParser(Parser):
|
|
|
|
def __init__(self, name):
|
|
super(UseParser, self).__init__(name)
|
|
childbearing_statements.add(self)
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
if l.keyword('expression'):
|
|
target = l.require(l.simple_expression)
|
|
l.keyword('pass')
|
|
else:
|
|
target = l.require(l.word)
|
|
|
|
args = renpy.parser.parse_arguments(l)
|
|
|
|
if l.keyword('id'):
|
|
id_expr = l.simple_expression()
|
|
else:
|
|
id_expr = None
|
|
|
|
if l.match(':'):
|
|
l.expect_eol()
|
|
l.expect_block("use statement")
|
|
|
|
block = slast.SLBlock(loc)
|
|
self.parse_contents(l, block, can_has=True, block_only=True)
|
|
|
|
else:
|
|
l.expect_eol()
|
|
l.expect_noblock("use statement")
|
|
|
|
block = None
|
|
|
|
return slast.SLUse(loc, target, args, id_expr, block)
|
|
|
|
|
|
UseParser("use")
|
|
Keyword("style_prefix")
|
|
Keyword("style_group")
|
|
|
|
|
|
class TranscludeParser(Parser):
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
l.expect_eol()
|
|
return slast.SLTransclude(loc)
|
|
|
|
|
|
TranscludeParser("transclude")
|
|
|
|
|
|
class CustomParser(Parser):
|
|
"""
|
|
:doc: custom_sl class
|
|
:name: renpy.register_sl_statement
|
|
|
|
Registers a custom screen language statement with Ren'Py.
|
|
|
|
`name`
|
|
This must be a word. It's the name of the custom screen language
|
|
statement.
|
|
|
|
`positional`
|
|
The number of positional parameters this statement takes.
|
|
|
|
`children`
|
|
The number of children this custom statement takes. This should
|
|
be 0, 1, or "many", which means zero or more.
|
|
|
|
`screen`
|
|
The screen to use. If not given, defaults to `name`.
|
|
|
|
Returns an object that can have positional arguments and properties
|
|
added to it. This object has the same .add_ methods as the objects
|
|
returned by :class:`renpy.register_sl_displayable`.
|
|
"""
|
|
|
|
def __init__(self, name, positional=0, children="many", screen=None):
|
|
Parser.__init__(self, name)
|
|
|
|
if children == "many":
|
|
children = many
|
|
|
|
for i in childbearing_statements:
|
|
i.add(self)
|
|
|
|
screen_parser.add(self)
|
|
|
|
self.nchildren = children
|
|
|
|
if self.nchildren != 0:
|
|
childbearing_statements.add(self)
|
|
|
|
for i in all_statements:
|
|
self.add(i)
|
|
|
|
self.add(if_statement)
|
|
|
|
global parser
|
|
parser = None
|
|
|
|
# The screen to use.
|
|
if screen is not None:
|
|
self.screen = screen
|
|
else:
|
|
self.screen = name
|
|
|
|
# The number of positional parameters required.
|
|
self.positional = positional
|
|
|
|
def parse(self, loc, l, parent, keyword):
|
|
|
|
arguments = [ ]
|
|
|
|
# Parse positional arguments.
|
|
for _i in range(self.positional):
|
|
expr = l.require(l.simple_expression)
|
|
arguments.append((None, expr))
|
|
|
|
# Parser keyword arguments and children.
|
|
block = slast.SLBlock(loc)
|
|
can_has = (self.nchildren == 1)
|
|
self.parse_contents(l, block, can_has=can_has, can_tag=False)
|
|
|
|
# Add the keyword arguments, and create an ArgumentInfo object.
|
|
arguments.extend(block.keyword)
|
|
block.keyword = [ ]
|
|
|
|
args = renpy.ast.ArgumentInfo(arguments, None, None)
|
|
|
|
# We only need a SLBlock if we have children.
|
|
if not block.children:
|
|
block = None
|
|
|
|
# Create the Use statement.
|
|
return slast.SLUse(loc, self.screen, args, None, block)
|
|
|
|
|
|
class ScreenParser(Parser):
|
|
|
|
def __init__(self):
|
|
super(ScreenParser, self).__init__("screen", statement=False)
|
|
|
|
def parse(self, loc, l, parent, name="_name", keyword=True):
|
|
|
|
screen = slast.SLScreen(loc)
|
|
|
|
screen.name = l.require(l.word)
|
|
screen.parameters = renpy.parser.parse_parameters(l)
|
|
|
|
self.parse_contents(l, screen, can_tag=True)
|
|
|
|
keyword = dict(screen.keyword)
|
|
|
|
screen.modal = keyword.get("modal", "False")
|
|
screen.zorder = keyword.get("zorder", "0")
|
|
screen.variant = keyword.get("variant", "None")
|
|
screen.predict = keyword.get("predict", "None")
|
|
screen.layer = keyword.get("layer", "'screens'")
|
|
screen.sensitive = keyword.get("sensitive", "True")
|
|
|
|
return screen
|
|
|
|
|
|
screen_parser = ScreenParser()
|
|
Keyword("modal")
|
|
Keyword("zorder")
|
|
Keyword("variant")
|
|
Keyword("predict")
|
|
Keyword("style_group")
|
|
Keyword("style_prefix")
|
|
Keyword("layer")
|
|
Keyword("sensitive")
|
|
parser = None
|
|
|
|
|
|
def init():
|
|
screen_parser.add(all_statements)
|
|
|
|
for i in all_statements:
|
|
|
|
if i in childbearing_statements:
|
|
i.add(all_statements)
|
|
else:
|
|
i.add(if_statement)
|
|
|
|
|
|
def parse_screen(l, loc):
|
|
"""
|
|
Parses the screen statement.
|
|
"""
|
|
|
|
return screen_parser.parse(loc, l, None)
|