# Copyright 2004-2022 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 division, absolute_import, with_statement, print_function, unicode_literals
from renpy.compat import PY2, basestring, bchr, bord, chr, open, pystr, range, round, str, tobytes, unicode # *


import renpy
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) # type: ignore
                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, str):
            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.args[1][1] - 1,
                "Syntax error while parsing python expression.",
                e.args[1][3],
                e.args[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, str):
            code = renpy.python.escape_unicode(code)

        try:
            rv = ast.parse(code, 'exec')
        except SyntaxError as e:

            raise renpy.parser.ParseError(
                filename,
                lineno + e.args[1][1] - 1,
                "Syntax error while parsing python code.",
                e.args[1][3],
                e.args[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) # type: ignore

                        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) # type: ignore


##############################################################################
# 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("dragging")
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 parse(self, l, name):
        return self.parse_exec("pass", l.number)


PassParser("pass")


class DefaultParser(Parser):

    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)

        extrapos = None
        extrakw = None

        if args:

            index = 0

            for k, v in args.arguments:

                if index in args.starred_indexes:
                    extrapos = v
                elif index in args.doublestarred_indexes:
                    extrakw = v
                elif k is None:
                    code += ", (%s)" % v
                else:
                    code += ", %s=(%s)" % (k, v)

                index += 1

        code += ", _name=%s, _scope=_scope" % name

        if args:

            if extrapos:
                code += ", *(%s)" % extrapos
            if extrakw:
                code += ", **(%s)" % 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) # type: ignore


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) # type: ignore

        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) # type: ignore
                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') # type: ignore

        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
