CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/sl2/slparser.py
2025-03-03 23:00:33 +01:00

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)