1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22#########################################################################
23# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
24#
25# When adding fields to a class in an __init__ method, we need to ensure that
26# field is copied in the copy() method.
27
28from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
29from renpy.compat import *
30from renpy.compat.pickle import loads, dumps
31
32import ast
33import collections
34import linecache
35import zlib
36import weakref
37
38import renpy.display
39import renpy.pyanalysis
40import renpy.sl2
41
42from renpy.display.transform import Transform, ATLTransform
43from renpy.display.layout import Fixed
44from renpy.display.predict import displayable as predict_displayable
45
46from renpy.python import py_eval_bytecode
47from renpy.pyanalysis import Analysis, NOT_CONST, LOCAL_CONST, GLOBAL_CONST, ccache
48
49import hashlib
50import time
51
52# This file contains the abstract syntax tree for a screen language
53# screen.
54
55# A serial number that makes each SLNode unique.
56serial = int(time.time() * 1000000)
57
58# A sentinel used to indicate we should use the value found in the
59# expression.
60use_expression = renpy.object.Sentinel("use_expression")
61
62# The filename that's currently being compiled.
63filename = '<screen language>'
64
65# A log that's used for profiling information.
66profile_log = renpy.log.open("profile_screen", developer=True, append=False, flush=False)
67
68
69def compile_expr(loc, node):
70    """
71    Wraps the node in a python AST, and compiles it.
72    """
73
74    filename = loc[0]
75    if filename in renpy.python.py3_files:
76        flags = renpy.python.py3_compile_flags
77    else:
78        flags = renpy.python.new_compile_flags
79
80    expr = ast.Expression(body=node)
81    ast.fix_missing_locations(expr)
82    return compile(expr, filename, "eval", flags, 1)
83
84
85class SLContext(renpy.ui.Addable):
86    """
87    A context object that can be passed to the execute methods, and can also
88    be placed in renpy.ui.stack.
89    """
90
91    def __init__(self, parent=None):
92        if parent is not None:
93            self.__dict__.update(parent.__dict__)
94            return
95
96        # The local scope that python code is evaluated in.
97        self.scope = { }
98
99        # The scope of the top-level screen.
100        self.root_scope = self.scope
101
102        # The global scope that python code is evaluated in.
103        self.globals = { }
104
105        # A list of child displayables that will be added to an outer
106        # displayable.
107        self.children = [ ]
108
109        # A map from keyword arguments to their values.
110        self.keywords = { }
111
112        # The style prefix that is given to children of this displayable.
113        self.style_prefix = None
114
115        # A cache associated with this context. The cache maps from
116        # statement serial to information associated with the statement.
117        self.new_cache = { }
118
119        # The old cache, used to take information from the old version of
120        # this displayable.
121        self.old_cache = { }
122
123        # The miss cache, used to take information that isn't present in
124        # old_cache.
125        self.miss_cache = { }
126
127        # The number of times a particular use statement has been called
128        # in the current screen. We use this to generate a unique name for
129        # each call site.
130        self.use_index = collections.defaultdict(int)
131
132        # When a constant node uses the scope, we add it to this list, so
133        # it may be reused. (If None, no list is used.)
134        self.uses_scope = None
135
136        # When a constant node has an id, we added it to this dict, so it
137        # may be reused. (If None, no dict is used.)
138        self.widgets = None
139
140        # True if we should dump debug information to the profile log.
141        self.debug = False
142
143        # True if we're predicting the screen.
144        self.predicting = False
145
146        # True if we're updating the screen.
147        self.updating = False
148
149        # A list of nodes we've predicted, for cases where predicting more than
150        # once could be a performance problem.
151        self.predicted = set()
152
153        # True if we're in a true showif block, False if we're in a false showif
154        # block, or None if we're not in a showif block.
155        self.showif = None
156
157        # True if there was a failure in this statement or any of its children.
158        # Fails can only occur when predicting, as otherwise an exception
159        # would be thrown.
160        self.fail = False
161
162        # The parent context of a use statement with a block.
163        self.parent = None
164
165        # The use statement containing the transcluded block.
166        self.transclude = None
167
168        # True if it's unlikely this node will run. This is used in prediction
169        # to speed things up.
170        self.unlikely = False
171
172        # The old and new generations of the use_cache.
173        self.new_use_cache = { }
174        self.old_use_cache = { }
175
176    def add(self, d, key):
177        self.children.append(d)
178
179    def close(self, d):
180        raise Exception("Spurious ui.close().")
181
182
183class SLNode(object):
184    """
185    The base class for screen language nodes.
186    """
187
188    # The type of constant this node is.
189    constant = GLOBAL_CONST
190
191    # True if this node has at least one keyword that applies to its
192    # parent. False otherwise.
193    has_keyword = False
194
195    # True if this node should be the last keyword parsed.
196    last_keyword = False
197
198    def __init__(self, loc):
199        global serial
200        serial += 1
201
202        # A unique serial number assigned to this node.
203        self.serial = serial
204
205        # The location of this node, a (file, line) tuple.
206        self.location = loc
207
208    def instantiate(self, transclude):
209        """
210        Instantiates a new instance of this class, copying the global
211        attributes of this class onto the new instance.
212        """
213
214        cls = type(self)
215
216        rv = cls.__new__(cls)
217        rv.serial = self.serial
218        rv.location = self.location
219
220        return rv
221
222    def copy(self, transclude):
223        """
224        Makes a copy of this node.
225
226        `transclude`
227            The constness of transclude statements.
228        """
229
230        raise Exception("copy not implemented by " + type(self).__name__)
231
232    def report_traceback(self, name, last):
233        if last:
234            return None
235
236        filename, line = self.location
237
238        return [ (filename, line, name, None) ]
239
240    def analyze(self, analysis):
241        """
242        Performs static analysis on Python code used in this statement.
243        """
244
245        # By default, does nothing.
246
247    def prepare(self, analysis):
248        """
249        This should be called before the execute code is called, and again
250        after init-level code (like the code in a .rpym module or an init
251        python block) is called.
252
253        `analysis`
254            A pyanalysis.Analysis object containing the analysis of this screen.
255        """
256
257        # By default, does nothing.
258
259    def execute(self, context):
260        """
261        Execute this node, updating context as appropriate.
262        """
263
264        raise Exception("execute not implemented by " + type(self).__name__)
265
266    def keywords(self, context):
267        """
268        Execute this node, updating context.keywords as appropriate.
269        """
270
271        # By default, does nothing.
272        return
273
274    def copy_on_change(self, cache):
275        """
276        Flags the displayables that are created by this node and its children
277        as copy-on-change.
278        """
279
280        return
281
282    def debug_line(self):
283        """
284        Writes information about the line we're on to the debug log.
285        """
286
287        filename, lineno = self.location
288        full_filename = renpy.exports.unelide_filename(filename)
289
290        line = linecache.getline(full_filename, lineno) or ""
291        line = line.decode("utf-8")
292
293        profile_log.write("  %s:%d %s", filename, lineno, line.rstrip())
294
295        if self.constant:
296            profile_log.write("    potentially constant")
297
298    def used_screens(self, callback):
299        """
300        Calls callback with the name of each screen this node and its
301        children use.
302        """
303
304        return
305
306    def has_transclude(self):
307        """
308        Returns true if this node is a transclude or has a transclude as a child.
309        """
310
311        return False
312
313    def has_python(self):
314        """
315        Returns true if this node is Python or has a python node as a child.
316        """
317
318        return False
319
320
321# A sentinel used to indicate a keyword argument was not given.
322NotGiven = renpy.object.Sentinel("NotGiven")
323
324
325class SLBlock(SLNode):
326    """
327    Represents a screen language block that can contain keyword arguments
328    and child displayables.
329    """
330
331    # RawBlock from parse or None if not present.
332    atl_transform = None
333
334    def __init__(self, loc):
335        SLNode.__init__(self, loc)
336
337        # A list of keyword argument, expr tuples.
338        self.keyword = [ ]
339
340        # A list of child SLNodes.
341        self.children = [ ]
342
343    def instantiate(self, transclude):
344        rv = SLNode.instantiate(self, transclude)
345        rv.keyword = self.keyword
346        rv.children = [ i.copy(transclude) for i in self.children ]
347        rv.atl_transform = self.atl_transform
348
349        return rv
350
351    def copy(self, transclude):
352        return self.instantiate(transclude)
353
354    def analyze(self, analysis):
355
356        for i in self.children:
357            i.analyze(analysis)
358
359    def prepare(self, analysis):
360
361        for i in self.children:
362            i.prepare(analysis)
363            self.constant = min(self.constant, i.constant)
364
365        # Compile the keywords.
366
367        keyword_values = { }
368        keyword_keys = [ ]
369        keyword_exprs = [ ]
370
371        for k, expr in self.keyword:
372
373            node = ccache.ast_eval(expr)
374
375            const = analysis.is_constant(node)
376
377            if const == GLOBAL_CONST:
378                keyword_values[k] = py_eval_bytecode(compile_expr(self.location, node))
379            else:
380                keyword_keys.append(ast.Str(s=k))
381                keyword_exprs.append(node) # Will be compiled as part of ast.Dict below.
382
383            self.constant = min(self.constant, const)
384
385        if keyword_values:
386            self.keyword_values = keyword_values
387        else:
388            self.keyword_values = None
389
390        if keyword_keys:
391            node = ast.Dict(keys=keyword_keys, values=keyword_exprs)
392            ast.copy_location(node, keyword_exprs[0])
393            self.keyword_exprs = compile_expr(self.location, node)
394        else:
395            self.keyword_exprs = None
396
397        self.has_keyword = bool(self.keyword)
398        self.keyword_children = [ ]
399
400        if self.atl_transform is not None:
401            self.has_keyword = True
402
403            self.atl_transform.mark_constant()
404            const = self.atl_transform.constant
405            self.constant = min(self.constant, const)
406
407        was_last_keyword = False
408        for i in self.children:
409            if i.has_keyword:
410
411                if was_last_keyword:
412                    raise Exception("Properties are not allowed here.")
413
414                self.keyword_children.append(i)
415                self.has_keyword = True
416
417            if i.last_keyword:
418                self.last_keyword = True
419                was_last_keyword = True
420                if not renpy.config.developer:
421                    break
422
423    def execute(self, context):
424
425        # Note: SLBlock.execute() is inlined in various locations for performance
426        # reasons.
427
428        for i in self.children:
429
430            try:
431                i.execute(context)
432            except:
433                if not context.predicting:
434                    raise
435
436    def keywords(self, context):
437
438        keyword_values = self.keyword_values
439
440        if keyword_values is not None:
441            context.keywords.update(keyword_values)
442
443        keyword_exprs = self.keyword_exprs
444
445        if keyword_exprs is not None:
446            context.keywords.update(eval(keyword_exprs, context.globals, context.scope))
447
448        for i in self.keyword_children:
449            i.keywords(context)
450
451        if self.atl_transform is not None:
452            transform = ATLTransform(self.atl_transform, context=context.scope)
453            context.keywords["at"] = transform
454
455        style_prefix = context.keywords.pop("style_prefix", NotGiven)
456
457        if style_prefix is NotGiven:
458            style_prefix = context.keywords.pop("style_group", NotGiven)
459
460        if style_prefix is not NotGiven:
461            context.style_prefix = style_prefix
462
463    def copy_on_change(self, cache):
464        for i in self.children:
465            i.copy_on_change(cache)
466
467    def used_screens(self, callback):
468        for i in self.children:
469            i.used_screens(callback)
470
471    def has_transclude(self):
472        for i in self.children:
473            if i.has_transclude():
474                return True
475
476        return False
477
478    def has_python(self):
479        return any(i.has_python() for i in self.children)
480
481    def has_noncondition_child(self):
482        """
483        Returns true if this block has a child that is not an SLIf statement,
484        or false otherwise.
485        """
486
487        worklist = list(self.children)
488
489        while worklist:
490
491            n = worklist.pop(0)
492
493            if type(n) is SLBlock:
494                worklist.extend(n.children)
495            elif isinstance(n, SLIf):
496                for _, block in n.entries:
497                    worklist.append(block)
498            else:
499                return True
500
501        return False
502
503    def keyword_exist(self, name):
504        """
505        Returns true if this block or it's SLIf children have parsed `name` keyword,
506        or false otherwise.
507        """
508
509        if name in dict(self.keyword):
510            return True
511
512        for n in self.children:
513
514            if isinstance(n, SLIf):
515                if n.keyword_exist(name):
516                    return True
517
518        return False
519
520
521list_or_tuple = (list, tuple)
522
523
524class SLCache(object):
525    """
526    The type of cache associated with an SLDisplayable.
527    """
528
529    def __init__(self):
530
531        # The displayable object created.
532        self.displayable = None
533
534        # The positional arguments that were used to create the displayable.
535        self.positional = None
536
537        # The keyword arguments that were used to created the displayable.
538        self.keywords = None
539
540        # A list of the children that were added to self.displayable.
541        self.children = None
542
543        # The outermost old transform.
544        self.outer_transform = None
545
546        # The innermost old transform.
547        self.inner_transform = None
548
549        # The transform (or list of transforms) that was used to create self.transform.
550        self.raw_transform = None
551
552        # The imagemap stack entry we reuse.
553        self.imagemap = None
554
555        # If this can be represented as a single constant displayable,
556        # do so.
557        self.constant = None
558
559        # For a constant statement, a list of our children that use
560        # the scope.
561        self.constant_uses_scope = [ ]
562
563        # For a constant statement, a map from children to widgets.
564        self.constant_widgets = { }
565
566        # True if the displayable should be re-created if its arguments
567        # or children are changed.
568        self.copy_on_change = False
569
570        # The ShowIf this statement was wrapped in the last time it was wrapped.
571        self.old_showif = None
572
573        # The SLUse that was transcluded by this SLCache statement.
574        self.transclude = None
575
576        # The style prefix used when this statement was first created.
577        self.style_prefix = None
578
579
580# A magic value that, if returned by a displayable function, is not added to
581# the parent.
582NO_DISPLAYABLE = renpy.display.layout.Null()
583
584
585class SLDisplayable(SLBlock):
586    """
587    A screen language AST node that corresponds to a displayable being
588    added to the tree.
589    """
590
591    hotspot = False
592    variable = None
593
594    # A list of variables that are locally constant.
595    local_constant = [ ]
596
597    def __init__(self, loc, displayable, scope=False, child_or_fixed=False, style=None, text_style=None, pass_context=False, imagemap=False, replaces=False, default_keywords={}, hotspot=False, variable=None):
598        """
599        `displayable`
600            A function that, when called with the positional and keyword
601            arguments, causes the displayable to be displayed.
602
603        `scope`
604            If true, the scope is supplied as an argument to the displayable.
605
606        `child_or_fixed`
607            If true and the number of children of this displayable is not one,
608            the children are added to a Fixed, and the Fixed is added to the
609            displayable.
610
611        `style`
612            The base name of the main style.
613
614        `pass_context`
615            If given, the context is passed in as the first positional argument
616            of the displayable.
617
618        `imagemap`
619            True if this is an imagemap, and should be handled as one.
620
621        `hotspot`
622            True if this is a hotspot that depends on the imagemap it was
623            first displayed with.
624
625        `replaces`
626            True if the object this displayable replaces should be
627            passed to it.
628
629        `default_keywords`
630            The default keyword arguments to supply to the displayable.
631
632        `variable`
633            A variable that the main displayable is assigned to.
634        """
635
636        SLBlock.__init__(self, loc)
637
638        self.displayable = displayable
639
640        self.scope = scope
641        self.child_or_fixed = child_or_fixed
642        self.style = style
643        self.pass_context = pass_context
644        self.imagemap = imagemap
645        self.hotspot = hotspot
646        self.replaces = replaces
647        self.default_keywords = default_keywords
648        self.variable = variable
649
650        # Positional argument expressions.
651        self.positional = [ ]
652
653    def copy(self, transclude):
654        rv = self.instantiate(transclude)
655
656        rv.displayable = self.displayable
657        rv.scope = self.scope
658        rv.child_or_fixed = self.child_or_fixed
659        rv.style = self.style
660        rv.pass_context = self.pass_context
661        rv.imagemap = self.imagemap
662        rv.hotspot = self.hotspot
663        rv.replaces = self.replaces
664        rv.default_keywords = self.default_keywords
665        rv.variable = self.variable
666        rv.positional = self.positional
667
668        return rv
669
670    def analyze(self, analysis):
671
672        if self.imagemap:
673
674            const = GLOBAL_CONST
675
676            for _k, expr in self.keyword:
677                const = min(const, analysis.is_constant_expr(expr))
678
679            analysis.push_control(imagemap=(const != GLOBAL_CONST))
680
681        if self.hotspot:
682            self.constant = min(analysis.imagemap(), self.constant)
683
684        SLBlock.analyze(self, analysis)
685
686        if self.imagemap:
687            analysis.pop_control()
688
689        # If we use a scope, store the local constants that need to be
690        # kept and placed into the scope.
691        if self.scope:
692            self.local_constant = list(analysis.local_constant)
693
694        if self.variable is not None:
695            const = self.constant
696
697            for i in self.positional:
698                const = min(self.constant, analysis.is_constant_expr(i))
699
700            for k, v in self.keyword:
701                const = min(self.constant, analysis.is_constant_expr(v))
702
703            if self.keyword_exist("id"):
704                const = NOT_CONST
705
706            if const == LOCAL_CONST:
707                analysis.mark_constant(self.variable)
708            elif const == NOT_CONST:
709                analysis.mark_not_constant(self.variable)
710
711    def prepare(self, analysis):
712
713        SLBlock.prepare(self, analysis)
714
715        # Prepare the positional arguments.
716
717        exprs = [ ]
718        values = [ ]
719        has_exprs = False
720        has_values = False
721
722        for a in self.positional:
723            node = ccache.ast_eval(a)
724
725            const = analysis.is_constant(node)
726
727            if const == GLOBAL_CONST:
728                values.append(py_eval_bytecode(compile_expr(self.location, node)))
729                exprs.append(ast.Num(n=0))
730                has_values = True
731            else:
732                values.append(use_expression)
733                exprs.append(node) # Will be compiled as part of the tuple.
734                has_exprs = True
735
736            self.constant = min(self.constant, const)
737
738        if has_values:
739            self.positional_values = values
740        else:
741            self.positional_values = None
742
743        if has_exprs:
744            t = ast.Tuple(elts=exprs, ctx=ast.Load())
745            ast.copy_location(t, exprs[0])
746            self.positional_exprs = compile_expr(self.location, t)
747        else:
748            self.positional_exprs = None
749
750        # We do not pass keywords to our parents.
751        self.has_keyword = False
752
753        # We want to preserve last_keyword, however, in case we run a
754        # python block.
755
756        # If we have the id property, we're not constant - since we may get
757        # additional keywords via id. (It's unlikely, but id should be pretty
758        # rare.)
759        if self.keyword_exist("id"):
760            self.constant = NOT_CONST
761
762        if self.variable is not None:
763            self.constant = NOT_CONST
764
765    def keywords(self, context):
766        # We do not want to pass keywords to our parents, so just return.
767        return
768
769    def execute(self, context):
770
771        debug = context.debug
772
773        screen = renpy.ui.screen
774
775        cache = context.old_cache.get(self.serial, None) or context.miss_cache.get(self.serial, None)
776
777        if not isinstance(cache, SLCache):
778            cache = SLCache()
779
780        context.new_cache[self.serial] = cache
781
782        copy_on_change = cache.copy_on_change
783
784        if debug:
785            self.debug_line()
786
787        if cache.constant and (cache.style_prefix == context.style_prefix):
788
789            for i, local_scope, context_scope in cache.constant_uses_scope:
790
791                if context_scope is None:
792                    context_scope = context.root_scope
793
794                if local_scope:
795                    scope = dict(context_scope)
796                    scope.update(local_scope)
797                else:
798                    scope = context_scope
799
800                if copy_on_change:
801                    if i._scope(scope, False):
802                        cache.constant = None
803                        break
804                else:
805                    i._scope(scope, True)
806
807            else:
808
809                d = cache.constant
810
811                if d is not NO_DISPLAYABLE:
812                    if context.showif is not None:
813                        d = self.wrap_in_showif(d, context, cache)
814
815                    context.children.append(d)
816
817                if context.uses_scope is not None:
818                    context.uses_scope.extend(cache.constant_uses_scope)
819
820                if debug:
821                    profile_log.write("    reused constant displayable")
822
823                return
824
825        # Create the context.
826        ctx = SLContext(context)
827
828        # True if we encountered an exception that we're recovering from
829        # due to being in prediction mode.
830        fail = False
831
832        # The main displayable we're predicting.
833        main = None
834
835        # True if we've pushed something onto the imagemap stack.
836        imagemap = False
837
838        try:
839            # Evaluate the positional arguments.
840            positional_values = self.positional_values
841            positional_exprs = self.positional_exprs
842
843            if positional_values and positional_exprs:
844                values = eval(positional_exprs, context.globals, context.scope)
845                positional = [ b if (a is use_expression) else a for a, b in zip(positional_values, values) ]
846            elif positional_values:
847                positional = positional_values
848            elif positional_exprs:
849                positional = eval(positional_exprs, context.globals, context.scope)
850            else:
851                positional = [ ]
852
853            keywords = ctx.keywords = self.default_keywords.copy()
854
855            if self.constant:
856                ctx.uses_scope = [ ]
857
858            SLBlock.keywords(self, ctx)
859
860            arguments = keywords.pop("arguments", None)
861            if arguments:
862                positional += arguments
863
864            properties = keywords.pop("properties", None)
865            if properties:
866                keywords.update(properties)
867
868            # Get the widget id and transform, if any.
869            widget_id = keywords.pop("id", None)
870            transform = keywords.pop("at", None)
871
872            # If we don't know the style, figure it out.
873            style_suffix = keywords.pop("style_suffix", None) or self.style
874            if ("style" not in keywords) and style_suffix:
875                if ctx.style_prefix is None:
876                    keywords["style"] = style_suffix
877                else:
878                    keywords["style"] = ctx.style_prefix + "_" + style_suffix
879
880            if widget_id and (widget_id in screen.widget_properties):
881                keywords.update(screen.widget_properties[widget_id])
882
883            old_d = cache.displayable
884            if old_d:
885                old_main = old_d._main or old_d
886            else:
887                old_main = None
888
889            reused = False
890
891            if debug:
892                self.report_arguments(cache, positional, keywords, transform)
893
894            can_reuse = (old_d is not None) and (positional == cache.positional) and (keywords == cache.keywords) and (context.style_prefix == cache.style_prefix)
895            if (self.variable is not None) and copy_on_change:
896                can_reuse = False
897
898            # A hotspot can only be reused if the imagemap it belongs to has
899            # not changed.
900            if self.hotspot:
901
902                imc = renpy.ui.imagemap_stack[-1]
903                if cache.imagemap is not imc:
904                    can_reuse = False
905
906                cache.imagemap = imc
907
908            if can_reuse:
909                reused = True
910                d = old_d
911
912                # The main displayable, if d is a composite displayable. (This is
913                # the one that gets the scope, and gets children added to it.)
914                main = old_main
915
916                if widget_id and not ctx.unlikely:
917                    screen.widgets[widget_id] = main
918                    screen.base_widgets[widget_id] = d
919
920                if self.scope and main._uses_scope:
921                    if copy_on_change:
922                        if main._scope(ctx.scope, False):
923                            reused = False
924                    else:
925                        main._scope(ctx.scope, True)
926
927            if reused and self.imagemap:
928                imagemap = True
929                cache.imagemap.reuse()
930                renpy.ui.imagemap_stack.append(cache.imagemap)
931
932            if not reused:
933                cache.positional = positional
934                cache.keywords = keywords.copy()
935
936                # This child creation code is copied below, for the copy_on_change
937                # case.
938                if self.scope:
939                    keywords["scope"] = ctx.scope
940
941                if self.replaces and ctx.updating:
942                    keywords['replaces'] = old_main
943
944                # Pass the context
945                if self.pass_context:
946                    keywords['context'] = ctx
947
948                d = self.displayable(*positional, **keywords)
949                main = d._main or d
950
951                main._location = self.location
952
953                if widget_id and not ctx.unlikely:
954                    screen.widgets[widget_id] = main
955                    screen.base_widgets[widget_id] = d
956                # End child creation code.
957
958                imagemap = self.imagemap
959
960                cache.copy_on_change = False # We no longer need to copy on change.
961                cache.children = None # Re-add the children.
962
963            if debug:
964                if reused:
965                    profile_log.write("    reused displayable")
966                elif self.constant:
967                    profile_log.write("    created constant displayable")
968                else:
969                    profile_log.write("    created displayable")
970
971        except:
972            if not context.predicting:
973                raise
974            fail = True
975
976        if self.variable is not None:
977            context.scope[self.variable] = main
978
979        ctx.children = [ ]
980        ctx.showif = None
981
982        stack = renpy.ui.stack
983        stack.append(ctx)
984
985        try:
986
987            # Evaluate children. (Inlined SLBlock.execute)
988            for i in self.children:
989                try:
990                    i.execute(ctx)
991                except:
992                    if not context.predicting:
993                        raise
994                    fail = True
995
996        finally:
997
998            ctx.keywords = None
999
1000            stack.pop()
1001
1002            if imagemap:
1003                cache.imagemap = renpy.ui.imagemap_stack.pop()
1004                cache.imagemap.cache.finish()
1005
1006        # If a failure occurred during prediction, predict main (if known),
1007        # and ctx.children, and return.
1008        if fail:
1009            predict_displayable(main)
1010
1011            for i in ctx.children:
1012                predict_displayable(i)
1013
1014            context.fail = True
1015
1016            return
1017
1018        if ctx.children != cache.children:
1019
1020            if reused and copy_on_change:
1021
1022                # This is a copy of the child creation code from above.
1023                if self.scope:
1024                    keywords["scope"] = ctx.scope
1025
1026                if self.replaces and context.updating:
1027                    keywords['replaces'] = old_main
1028
1029                if self.pass_context:
1030                    keywords['context'] = ctx
1031
1032                d = self.displayable(*positional, **keywords)
1033                main = d._main or d
1034
1035                main._location = self.location
1036
1037                if widget_id:
1038                    screen.widgets[widget_id] = main
1039                    screen.base_widgets[widget_id] = d
1040                # End child creation code.
1041
1042                cache.copy_on_change = False
1043                reused = False
1044
1045            if reused:
1046                main._clear()
1047
1048            if self.child_or_fixed and len(ctx.children) != 1:
1049                f = Fixed()
1050
1051                for i in ctx.children:
1052                    f.add(i)
1053
1054                main.add(f)
1055
1056            else:
1057                for i in ctx.children:
1058                    main.add(i)
1059
1060        # Inform the focus system about replacement displayables.
1061        if (not context.predicting) and (old_d is not None):
1062            replaced_by = renpy.display.focus.replaced_by
1063            replaced_by[id(old_d)] = d
1064
1065            if d is not main:
1066                for old_part, new_part in zip(old_d._composite_parts, d._composite_parts):
1067                    replaced_by[id(old_part)] = new_part
1068
1069        cache.displayable = d
1070        cache.children = ctx.children
1071        cache.style_prefix = context.style_prefix
1072
1073        if (transform is not None) and (d is not NO_DISPLAYABLE):
1074            if reused and (transform == cache.raw_transform):
1075
1076                if isinstance(cache.inner_transform, renpy.display.transform.Transform):
1077                    if cache.inner_transform.child is not d:
1078                        cache.inner_transform.set_child(d, duplicate=False)
1079
1080                d = cache.outer_transform
1081
1082            else:
1083                old_outer_transform = cache.outer_transform
1084
1085                cache.raw_transform = transform
1086                cache.inner_transform = None
1087                cache.outer_transform = None
1088
1089                if isinstance(transform, Transform):
1090                    d = transform(child=d)
1091                    d._unique()
1092
1093                    cache.inner_transform = d
1094                    cache.outer_transform = d
1095
1096                elif isinstance(transform, list_or_tuple):
1097                    for t in transform:
1098                        if isinstance(t, Transform):
1099                            d = t(child=d)
1100
1101                            cache.outer_transform = d
1102                            if cache.inner_transform is None:
1103                                cache.inner_transform = d
1104
1105                        else:
1106                            d = t(d)
1107                            cache.raw_transform = None
1108                            cache.outer_transform = None
1109                            cache.inner_transform = None
1110
1111                        d._unique()
1112
1113                else:
1114                    d = transform(d)
1115                    d._unique()
1116                    cache.raw_transform = None
1117                    cache.outer_transform = None
1118                    cache.inner_transform = None
1119
1120                if isinstance(d, Transform):
1121
1122                    if not context.updating:
1123                        old_outer_transform = None
1124
1125                    d.take_state(old_outer_transform)
1126                    d.take_execution_state(old_outer_transform)
1127
1128        else:
1129            cache.inner_transform = None
1130            cache.outer_transform = None
1131            cache.raw_transform = None
1132
1133        if ctx.fail:
1134            context.fail = True
1135
1136        else:
1137            if self.constant:
1138                cache.constant = d
1139
1140                if self.scope and main._uses_scope:
1141
1142                    local_scope = { }
1143
1144                    for i in self.local_constant:
1145                        if i in ctx.scope:
1146                            local_scope[i] = ctx.scope[i]
1147
1148                    if ctx.scope is context.root_scope:
1149                        ctx.uses_scope.append((main, local_scope, None))
1150                    else:
1151                        ctx.uses_scope.append((main, local_scope, ctx.scope))
1152
1153                cache.constant_uses_scope = ctx.uses_scope
1154
1155                if context.uses_scope is not None:
1156                    context.uses_scope.extend(ctx.uses_scope)
1157
1158        if d is not NO_DISPLAYABLE:
1159
1160            if context.showif is not None:
1161                d = self.wrap_in_showif(d, context, cache)
1162
1163            context.children.append(d)
1164
1165    def wrap_in_showif(self, d, context, cache):
1166        """
1167        Wraps `d` in a ShowIf displayable.
1168        """
1169
1170        rv = renpy.sl2.sldisplayables.ShowIf(context.showif, cache.old_showif)
1171        rv.add(d)
1172
1173        if not context.predicting:
1174            cache.old_showif = rv
1175
1176        return rv
1177
1178    def report_arguments(self, cache, positional, keywords, transform):
1179        if positional:
1180            report = [ ]
1181
1182            values = self.positional_values or ([ use_expression ] * len(positional))
1183
1184            for i in range(len(positional)):
1185
1186                if values[i] is not use_expression:
1187                    report.append("const")
1188                elif cache.positional is None:
1189                    report.append("new")
1190                elif cache.positional[i] == positional[i]:
1191                    report.append("equal")
1192                else:
1193                    report.append("not-equal")
1194
1195            profile_log.write("    args: %s", " ".join(report))
1196
1197        values = self.keyword_values or { }
1198
1199        if keywords:
1200            report = { }
1201
1202            if cache.keywords is None:
1203                for k in keywords:
1204
1205                    if k in values:
1206                        report[k] = "const"
1207                        continue
1208
1209                    report[k] = "new"
1210
1211            else:
1212                for k in keywords:
1213                    k = str(k)
1214
1215                    if k in values:
1216                        report[k] = "const"
1217                        continue
1218
1219                    if k not in cache.keywords:
1220                        report[k] = "new-only"
1221                        continue
1222
1223                    if keywords[k] == cache.keywords[k]:
1224                        report[k] = "equal"
1225                    else:
1226                        report[k] = "not-equal"
1227
1228                for k in cache.keywords:
1229                    if k not in keywords:
1230                        report[k] = "old-only"
1231
1232            profile_log.write("    kwargs: %r", report)
1233
1234        if transform is not None:
1235            if "at" in values:
1236                profile_log.write("    at: const")
1237            elif cache.raw_transform is None:
1238                profile_log.write("    at: new")
1239            elif cache.raw_transform == transform:
1240                profile_log.write("    at: equal")
1241            else:
1242                profile_log.write("    at: not-equal")
1243
1244    def copy_on_change(self, cache):
1245        c = cache.get(self.serial, None)
1246
1247        if isinstance(c, SLCache):
1248            c.copy_on_change = True
1249
1250        for i in self.children:
1251            i.copy_on_change(cache)
1252
1253
1254class SLIf(SLNode):
1255    """
1256    A screen language AST node that corresponds to an If/Elif/Else statement.
1257    """
1258
1259    def __init__(self, loc):
1260        """
1261        An AST node that represents an if statement.
1262        """
1263        SLNode.__init__(self, loc)
1264
1265        # A list of entries, with each consisting of an expression (or
1266        # None, for the else block) and a SLBlock.
1267        self.entries = [ ]
1268
1269    def copy(self, transclude):
1270        rv = self.instantiate(transclude)
1271
1272        rv.entries = [ (expr, block.copy(transclude)) for expr, block in self.entries ]
1273
1274        return rv
1275
1276    def analyze(self, analysis):
1277
1278        const = GLOBAL_CONST
1279
1280        for cond, _block in self.entries:
1281            if cond is not None:
1282                const = min(const, analysis.is_constant_expr(cond))
1283
1284        analysis.push_control(const)
1285
1286        for _cond, block in self.entries:
1287            block.analyze(analysis)
1288
1289        analysis.pop_control()
1290
1291    def prepare(self, analysis):
1292
1293        # A list of prepared entries, with each consisting of expression
1294        # bytecode and a SLBlock.
1295        self.prepared_entries = [ ]
1296
1297        for cond, block in self.entries:
1298            if cond is not None:
1299                node = ccache.ast_eval(cond)
1300
1301                self.constant = min(self.constant, analysis.is_constant(node))
1302
1303                cond = compile_expr(self.location, node)
1304
1305            block.prepare(analysis)
1306            self.constant = min(self.constant, block.constant)
1307            self.prepared_entries.append((cond, block))
1308
1309            self.has_keyword |= block.has_keyword
1310            self.last_keyword |= block.last_keyword
1311
1312    def execute(self, context):
1313
1314        if context.predicting:
1315            self.execute_predicting(context)
1316            return
1317
1318        for cond, block in self.prepared_entries:
1319            if cond is None or eval(cond, context.globals, context.scope):
1320                for i in block.children:
1321                    i.execute(context)
1322                return
1323
1324    def execute_predicting(self, context):
1325        # A variant of the this code that runs while predicting, executing
1326        # all paths of the if.
1327
1328        # True if no block has been the main choice yet.
1329        first = True
1330
1331        # Other blocks that we predict if not predicted.
1332        false_blocks = [ ]
1333
1334        for cond, block in self.prepared_entries:
1335            try:
1336                cond_value = (cond is None) or eval(cond, context.globals, context.scope)
1337            except:
1338                cond_value = False
1339
1340            # The taken branch.
1341            if first and cond_value:
1342                first = False
1343
1344                for i in block.children:
1345                    try:
1346                        i.execute(context)
1347                    except:
1348                        pass
1349
1350            else:
1351                false_blocks.append(block)
1352
1353        # Has any instance of this node been predicted? We only predict
1354        # once per node, for performance reasons.
1355        if self.serial in context.predicted:
1356            return
1357
1358        context.predicted.add(self.serial)
1359
1360        # Not-taken branches.
1361        for block in false_blocks:
1362            ctx = SLContext(context)
1363            ctx.children = [ ]
1364            ctx.unlikely = True
1365
1366            for i in block.children:
1367                try:
1368                    i.execute(ctx)
1369                except:
1370                    pass
1371
1372            for i in ctx.children:
1373                predict_displayable(i)
1374
1375    def keywords(self, context):
1376
1377        for cond, block in self.prepared_entries:
1378            if cond is None or eval(cond, context.globals, context.scope):
1379                block.keywords(context)
1380                return
1381
1382    def copy_on_change(self, cache):
1383        for _cond, block in self.entries:
1384            block.copy_on_change(cache)
1385
1386    def used_screens(self, callback):
1387        for _cond, block in self.entries:
1388            block.used_screens(callback)
1389
1390    def has_transclude(self):
1391        for _cond, block in self.entries:
1392            if block.has_transclude():
1393                return True
1394
1395        return False
1396
1397    def has_python(self):
1398        return any(i[1].has_python() for i in self.entries)
1399
1400    def keyword_exist(self, name):
1401        return any(i[1].keyword_exist(name) for i in self.entries)
1402
1403
1404class SLShowIf(SLNode):
1405    """
1406    The AST node that corresponds to the showif statement.
1407    """
1408
1409    def __init__(self, loc):
1410        """
1411        An AST node that represents an if statement.
1412        """
1413        SLNode.__init__(self, loc)
1414
1415        # A list of entries, with each consisting of an expression (or
1416        # None, for the else block) and a SLBlock.
1417        self.entries = [ ]
1418
1419    def copy(self, transclude):
1420        rv = self.instantiate(transclude)
1421
1422        rv.entries = [ (expr, block.copy(transclude)) for expr, block in self.entries ]
1423
1424        return rv
1425
1426    def analyze(self, analysis):
1427
1428        for _cond, block in self.entries:
1429            block.analyze(analysis)
1430
1431    def prepare(self, analysis):
1432
1433        # A list of prepared entries, with each consisting of expression
1434        # bytecode and a SLBlock.
1435        self.prepared_entries = [ ]
1436
1437        for cond, block in self.entries:
1438            if cond is not None:
1439                node = ccache.ast_eval(cond)
1440
1441                self.constant = min(self.constant, analysis.is_constant(node))
1442
1443                cond = compile_expr(self.location, node)
1444
1445            block.prepare(analysis)
1446            self.constant = min(self.constant, block.constant)
1447            self.prepared_entries.append((cond, block))
1448
1449        self.last_keyword = True
1450
1451    def execute(self, context):
1452
1453        # This is true when the block should be executed - when no outer
1454        # showif is False, and when no prior block in this showif has
1455        # executed.
1456        first_true = context.showif is not False
1457
1458        for cond, block in self.prepared_entries:
1459
1460            ctx = SLContext(context)
1461
1462            if not first_true:
1463                ctx.showif = False
1464            else:
1465                if cond is None or eval(cond, context.globals, context.scope):
1466                    ctx.showif = True
1467                    first_true = False
1468                else:
1469                    ctx.showif = False
1470
1471            for i in block.children:
1472                i.execute(ctx)
1473
1474            if ctx.fail:
1475                context.fail = True
1476
1477    def copy_on_change(self, cache):
1478        for _cond, block in self.entries:
1479            block.copy_on_change(cache)
1480
1481    def used_screens(self, callback):
1482        for _cond, block in self.entries:
1483            block.used_screens(callback)
1484
1485    def has_transclude(self):
1486        for _cond, block in self.entries:
1487            if block.has_transclude():
1488                return True
1489
1490        return False
1491
1492    def has_python(self):
1493        return any(i[1].has_python() for i in self.entries)
1494
1495
1496class SLFor(SLBlock):
1497    """
1498    The AST node that corresponds to a for statement. This only supports
1499    simple for loops that assign a single variable.
1500    """
1501
1502    index_expression = None
1503
1504    def __init__(self, loc, variable, expression, index_expression):
1505        SLBlock.__init__(self, loc)
1506
1507        self.variable = variable
1508        self.expression = expression
1509        self.index_expression = index_expression
1510
1511    def copy(self, transclude):
1512        rv = self.instantiate(transclude)
1513
1514        rv.variable = self.variable
1515        rv.expression = self.expression
1516        rv.index_expression = self.index_expression
1517
1518        return rv
1519
1520    def analyze(self, analysis):
1521
1522        if analysis.is_constant_expr(self.expression) == GLOBAL_CONST:
1523            analysis.push_control(True)
1524            analysis.mark_constant(self.variable)
1525        else:
1526            analysis.push_control(False)
1527            analysis.mark_not_constant(self.variable)
1528
1529        SLBlock.analyze(self, analysis)
1530
1531        analysis.pop_control()
1532
1533    def prepare(self, analysis):
1534        node = ccache.ast_eval(self.expression)
1535
1536        const = analysis.is_constant(node)
1537
1538        if const == GLOBAL_CONST:
1539            self.expression_value = py_eval_bytecode(compile_expr(self.location, node))
1540            self.expression_expr = None
1541        else:
1542            self.expression_value = None
1543            self.expression_expr = compile_expr(self.location, node)
1544
1545        self.constant = min(self.constant, const)
1546
1547        SLBlock.prepare(self, analysis)
1548
1549        self.last_keyword = True
1550
1551    def execute(self, context):
1552
1553        variable = self.variable
1554        expr = self.expression_expr
1555
1556        try:
1557            if expr is not None:
1558                value = eval(expr, context.globals, context.scope)
1559            else:
1560                value = self.expression_value
1561        except:
1562            if not context.predicting:
1563                raise
1564
1565            value = [ 0 ]
1566
1567        newcaches = { }
1568
1569        oldcaches = context.old_cache.get(self.serial, newcaches) or { }
1570
1571        if not isinstance(oldcaches, dict):
1572            oldcaches = { }
1573
1574        misscaches = context.miss_cache.get(self.serial, newcaches) or { }
1575
1576        if not isinstance(misscaches, dict):
1577            misscaches = { }
1578
1579        ctx = SLContext(context)
1580
1581        for index, v in enumerate(value):
1582
1583            ctx.scope[variable] = v
1584
1585            children_i = iter(self.children)
1586
1587            # If we have a variable expression as a tuple, it is necessary
1588            # to execute the first child before evaluating the index value,
1589            # because the index can be one of this tuple member.
1590            if variable == "_sl2_i":
1591                sl_python = next(children_i)
1592                # It can only fail if the unpacking fails, but it can still
1593                try:
1594                    sl_python.execute(ctx)
1595                except:
1596                    if not context.predicting:
1597                        raise
1598
1599            if self.index_expression is not None:
1600                index = eval(self.index_expression, ctx.globals, ctx.scope)
1601
1602            ctx.old_cache = oldcaches.get(index, None) or { }
1603
1604            if not isinstance(ctx.old_cache, dict):
1605                ctx.old_cache = {}
1606
1607            ctx.miss_cache = misscaches.get(index, None) or { }
1608
1609            if not isinstance(ctx.miss_cache, dict):
1610                ctx.miss_cache = {}
1611
1612            newcaches[index] = ctx.new_cache = { }
1613
1614            # Inline of SLBlock.execute.
1615
1616            for i in children_i:
1617                try:
1618                    i.execute(ctx)
1619                except:
1620                    if not context.predicting:
1621                        raise
1622
1623            if context.unlikely:
1624                break
1625
1626        context.new_cache[self.serial] = newcaches
1627
1628        if ctx.fail:
1629            context.fail = True
1630
1631    def keywords(self, context):
1632        return
1633
1634    def copy_on_change(self, cache):
1635        c = cache.get(self.serial, None)
1636
1637        if not isinstance(c, dict):
1638            return
1639
1640        for child_cache in c.values():
1641            for i in self.children:
1642                i.copy_on_change(child_cache)
1643
1644
1645class SLPython(SLNode):
1646
1647    def __init__(self, loc, code):
1648        SLNode.__init__(self, loc)
1649
1650        # A pycode object.
1651        self.code = code
1652
1653    def copy(self, transclude):
1654        rv = self.instantiate(transclude)
1655
1656        rv.code = self.code
1657
1658        return rv
1659
1660    def analyze(self, analysis):
1661        analysis.python(self.code.source)
1662
1663    def execute(self, context):
1664        exec(self.code.bytecode, context.globals, context.scope)
1665
1666    def prepare(self, analysis):
1667        self.constant = NOT_CONST
1668        self.last_keyword = True
1669
1670    def has_python(self):
1671        return True
1672
1673
1674class SLPass(SLNode):
1675
1676    def execute(self, context):
1677        return
1678
1679    def copy(self, transclude):
1680        rv = self.instantiate(transclude)
1681
1682        return rv
1683
1684
1685class SLDefault(SLNode):
1686
1687    def __init__(self, loc, variable, expression):
1688        SLNode.__init__(self, loc)
1689
1690        self.variable = variable
1691        self.expression = expression
1692
1693    def copy(self, transclude):
1694        rv = self.instantiate(transclude)
1695
1696        rv.variable = self.variable
1697        rv.expression = self.expression
1698
1699        return rv
1700
1701    def analyze(self, analysis):
1702        analysis.mark_not_constant(self.variable)
1703
1704    def prepare(self, analysis):
1705        self.expr = compile_expr(self.location, ccache.ast_eval(self.expression))
1706        self.constant = NOT_CONST
1707        self.last_keyword = True
1708
1709    def execute(self, context):
1710        scope = context.scope
1711        variable = self.variable
1712
1713        if variable in scope:
1714            return
1715
1716        scope[variable] = eval(self.expr, context.globals, scope)
1717
1718    def has_python(self):
1719        return True
1720
1721
1722class SLUse(SLNode):
1723
1724    id = None
1725    block = None
1726
1727    def __init__(self, loc, target, args, id_expr, block):
1728
1729        SLNode.__init__(self, loc)
1730
1731        # The name of the screen we're accessing.
1732        self.target = target
1733
1734        # If the target is an SL2 screen, the SLScreen node at the root of
1735        # the ast for that screen.
1736        self.ast = None
1737
1738        # If arguments are given, those arguments.
1739        self.args = args
1740
1741        # An expression, if the id property is given.
1742        self.id = id_expr
1743
1744        # A block for transclusion, or None if the statement does not have a
1745        # block.
1746        self.block = block
1747
1748    def copy(self, transclude):
1749
1750        rv = self.instantiate(transclude)
1751
1752        rv.target = self.target
1753        rv.args = self.args
1754        rv.id = self.id
1755
1756        if self.block is not None:
1757            rv.block = self.block.copy(transclude)
1758        else:
1759            rv.block = None
1760
1761        rv.ast = None
1762
1763        return rv
1764
1765    def analyze(self, analysis):
1766
1767        self.last_keyword = True
1768
1769        if self.id:
1770            self.constant = NOT_CONST
1771
1772        if self.block:
1773            self.block.analyze(analysis)
1774
1775    def prepare(self, analysis):
1776
1777        self.ast = None
1778
1779        if self.block:
1780            self.block.prepare(analysis)
1781
1782            if self.block.constant == GLOBAL_CONST:
1783                const = True
1784            else:
1785                const = False
1786        else:
1787            const = True
1788
1789        if isinstance(self.target, renpy.ast.PyExpr):
1790
1791            self.constant = NOT_CONST
1792            const = False
1793            self.ast = None
1794
1795        else:
1796
1797            target = renpy.display.screen.get_screen_variant(self.target)
1798
1799            if target is None:
1800                self.constant = NOT_CONST
1801
1802                if renpy.config.developer:
1803                    raise Exception("A screen named {} does not exist.".format(self.target))
1804                else:
1805                    return
1806
1807            if target.ast is None:
1808                self.constant = NOT_CONST
1809                return
1810
1811            if const:
1812                self.ast = target.ast.const_ast
1813            else:
1814                self.ast = target.ast.not_const_ast
1815
1816            self.constant = min(self.constant, self.ast.constant)
1817
1818    def execute_use_screen(self, context):
1819
1820        # Create an old-style displayable name for this call site.
1821        serial = context.use_index[self.serial]
1822        context.use_index[self.serial] = serial + 1
1823
1824        name = (
1825            context.scope.get("_name", ()),
1826            self.serial,
1827            serial)
1828
1829        if self.args:
1830            args, kwargs = self.args.evaluate(context.scope)
1831        else:
1832            args = [ ]
1833            kwargs = { }
1834
1835        renpy.display.screen.use_screen(self.target, _name=name, _scope=context.scope, *args, **kwargs)
1836
1837    def execute(self, context):
1838
1839        if isinstance(self.target, renpy.ast.PyExpr):
1840            target_name = eval(self.target, context.globals, context.scope)
1841            target = renpy.display.screen.get_screen_variant(target_name)
1842
1843            if target is None:
1844                raise Exception("A screen named {} does not exist.".format(target_name))
1845
1846            ast = target.ast.not_const_ast
1847
1848            id_prefix = "_use_expression"
1849
1850        else:
1851            id_prefix = self.target
1852            ast = self.ast
1853
1854        # If self.ast is not an SL2 screen, run it using renpy.display.screen.use_screen.
1855        if ast is None:
1856            self.execute_use_screen(context)
1857            return
1858
1859        # Otherwise, run the use statement directly.
1860
1861        # Figure out the cache to use.
1862
1863        ctx = SLContext(context)
1864        ctx.new_cache = context.new_cache[self.serial] = { "ast" : ast }
1865        ctx.miss_cache = context.miss_cache.get(self.serial, None) or { }
1866
1867        if self.id:
1868
1869            use_id = (id_prefix, eval(self.id, context.globals, context.scope))
1870
1871            ctx.old_cache = context.old_use_cache.get(use_id, None) or context.old_cache.get(self.serial, None) or { }
1872
1873            if use_id in ctx.old_use_cache:
1874                ctx.updating = True
1875
1876            ctx.new_use_cache[use_id] = ctx.new_cache
1877
1878        else:
1879
1880            ctx.old_cache = context.old_cache.get(self.serial, None) or { }
1881
1882        if not isinstance(ctx.old_cache, dict):
1883            ctx.old_cache = { }
1884        if not isinstance(ctx.miss_cache, dict):
1885            ctx.miss_cache = { }
1886
1887        # Evaluate the arguments.
1888        try:
1889            if self.args:
1890                args, kwargs = self.args.evaluate(context.scope)
1891            else:
1892                args = [ ]
1893                kwargs = { }
1894        except:
1895            if not context.predicting:
1896                raise
1897
1898            args = [ ]
1899            kwargs = { }
1900
1901        # Apply the arguments to the parameters (if present) or to the scope of the used screen.
1902        if ast.parameters is not None:
1903            new_scope = ast.parameters.apply(args, kwargs, ignore_errors=context.predicting)
1904
1905            scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
1906            scope.update(new_scope)
1907
1908        else:
1909
1910            if args:
1911                raise Exception("Screen {} does not take positional arguments. ({} given)".format(self.target, len(args)))
1912
1913            scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
1914            scope.clear()
1915            scope.update(context.scope)
1916            scope.update(kwargs)
1917
1918        scope["_scope"] = scope
1919        ctx.new_cache["scope"] = scope
1920
1921        # Run the child screen.
1922        ctx.scope = scope
1923        ctx.parent = weakref.ref(context)
1924
1925        ctx.transclude = self.block
1926
1927        try:
1928            ast.execute(ctx)
1929        finally:
1930            del scope["_scope"]
1931
1932        if ctx.fail:
1933            context.fail = True
1934
1935    def copy_on_change(self, cache):
1936
1937        c = cache.get(self.serial, None)
1938
1939        if c is None:
1940            return
1941
1942        ast = c.get("ast", None)
1943
1944        if ast is not None:
1945            ast.copy_on_change(c)
1946
1947    def used_screens(self, callback):
1948        if not isinstance(self.target, renpy.ast.PyExpr):
1949            callback(self.target)
1950
1951    def has_transclude(self):
1952        if self.block:
1953            return self.block.has_transclude()
1954        else:
1955            return False
1956
1957
1958class SLTransclude(SLNode):
1959
1960    def __init__(self, loc):
1961        SLNode.__init__(self, loc)
1962
1963    def copy(self, transclude):
1964        rv = self.instantiate(transclude)
1965        rv.constant = transclude
1966        return rv
1967
1968    def execute(self, context):
1969
1970        if not context.transclude:
1971            return
1972
1973        parent = context.parent
1974        if parent is not None:
1975            parent = parent()
1976
1977        ctx = SLContext(parent)
1978        ctx.new_cache = context.new_cache[self.serial] = { }
1979        ctx.old_cache = context.old_cache.get(self.serial, None) or { }
1980        ctx.miss_cache = context.miss_cache.get(self.serial, None) or { }
1981        ctx.uses_scope = context.uses_scope
1982
1983        if not isinstance(ctx.old_cache, dict):
1984            ctx.old_cache = { }
1985        if not isinstance(ctx.miss_cache, dict):
1986            ctx.miss_cache = { }
1987
1988        ctx.new_cache["transclude"] = context.transclude
1989
1990        ctx.children = context.children
1991        ctx.showif = context.showif
1992
1993        try:
1994            renpy.ui.stack.append(ctx)
1995            context.transclude.keywords(ctx)
1996            context.transclude.execute(ctx)
1997        finally:
1998            renpy.ui.stack.pop()
1999
2000        if ctx.fail:
2001            context.fail = True
2002
2003    def copy_on_change(self, cache):
2004
2005        c = cache.get(self.serial, None)
2006
2007        if c is None or "transclude" not in c:
2008            return
2009
2010        SLBlock.copy_on_change(c["transclude"], c)
2011
2012    def has_transclude(self):
2013        return True
2014
2015
2016class SLCustomUse(SLNode):
2017    """This represents special use screen statement defined
2018    by renpy.register_sl_statement.
2019    """
2020
2021    def __init__(self, loc, target, positional, block):
2022
2023        SLNode.__init__(self, loc)
2024
2025        # The name of the screen we're accessing.
2026        self.target = target
2027
2028        # The SL2 SLScreen node at the root of the ast for that screen.
2029        self.ast = None
2030
2031        # Positional argument expressions.
2032        self.positional = positional
2033
2034        # A block for transclusion, from which we also take kwargs.
2035        self.block = block
2036
2037    def copy(self, transclude):
2038
2039        rv = self.instantiate(transclude)
2040
2041        rv.target = self.target
2042        rv.ast = None
2043
2044        rv.positional = self.positional
2045        rv.block = self.block.copy(transclude)
2046
2047        return rv
2048
2049    def analyze(self, analysis):
2050
2051        self.last_keyword = True
2052
2053        self.block.analyze(analysis)
2054
2055    def prepare(self, analysis):
2056
2057        block = self.block
2058
2059        block.prepare(analysis)
2060
2061        # Figure out the ast we want to use.
2062        target = renpy.display.screen.get_screen_variant(self.target)
2063
2064        if target is None:
2065            self.constant = NOT_CONST
2066
2067            if renpy.config.developer:
2068                raise Exception("A screen named {} does not exist.".format(self.target))
2069            else:
2070                return
2071
2072        if target.ast is None:
2073            self.constant = NOT_CONST
2074
2075            if renpy.config.developer:
2076                raise Exception("A screen used in CD SLS should be a SL-based screen.")
2077            else:
2078                return
2079
2080        # If we have the id property, we're not constant - since we may get
2081        # our state via other screen on replace.
2082        if block.keyword_exist("id"):
2083            self.constant = NOT_CONST
2084            self.ast = target.ast.not_const_ast
2085
2086        elif block.constant == GLOBAL_CONST:
2087            self.ast = target.ast.const_ast
2088        else:
2089            self.ast = target.ast.not_const_ast
2090
2091        self.constant = min(self.constant, self.ast.constant)
2092
2093    def execute(self, context):
2094
2095        # Figure out the cache to use.
2096        ctx = SLContext(context)
2097        ctx.new_cache = context.new_cache[self.serial] = { }
2098        ctx.miss_cache = context.miss_cache.get(self.serial, None) or { }
2099
2100        # Evaluate the arguments to use in screen.
2101        try:
2102            args = [eval(i, context.globals, context.scope) for i in self.positional]
2103
2104            kwargs = ctx.keywords = {}
2105
2106            self.block.keywords(ctx)
2107
2108            arguments = kwargs.pop("arguments", None)
2109            if arguments:
2110                args += arguments
2111
2112            properties = kwargs.pop("properties", None)
2113            if properties:
2114                kwargs.update(properties)
2115
2116            # If we don't know the style, figure it out.
2117            style_suffix = kwargs.pop("style_suffix", None)
2118            if ("style" not in kwargs) and style_suffix:
2119                if ctx.style_prefix is None:
2120                    kwargs["style"] = style_suffix
2121                else:
2122                    kwargs["style"] = ctx.style_prefix + "_" + style_suffix
2123
2124        except:
2125            if not context.predicting:
2126                raise
2127
2128            args = [ ]
2129            kwargs = { }
2130
2131        # Get the id and deal with replacement algorithm.
2132        id = kwargs.pop("id", None)
2133        if id is not None:
2134
2135            use_id = (self.target, id)
2136
2137            ctx.old_cache = context.old_use_cache.get(use_id, None) or context.old_cache.get(self.serial, None) or { }
2138
2139            if use_id in ctx.old_use_cache:
2140                ctx.updating = True
2141
2142            ctx.new_use_cache[use_id] = ctx.new_cache
2143
2144        else:
2145
2146            ctx.old_cache = context.old_cache.get(self.serial, None) or { }
2147
2148        if not isinstance(ctx.old_cache, dict):
2149            ctx.old_cache = { }
2150        if not isinstance(ctx.miss_cache, dict):
2151            ctx.miss_cache = { }
2152
2153        ast = self.ast
2154
2155        # Apply the arguments to the parameters (if present) or to the scope of the used screen.
2156        if ast.parameters is not None:
2157            new_scope = ast.parameters.apply(args, kwargs, ignore_errors=context.predicting)
2158
2159            scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
2160            scope.update(new_scope)
2161
2162        else:
2163
2164            if args:
2165                raise Exception("Screen {} does not take positional arguments. ({} given)".format(self.target, len(args)))
2166
2167            scope = ctx.old_cache.get("scope", None) or ctx.miss_cache.get("scope", None) or { }
2168            scope.clear()
2169            scope.update(context.scope)
2170            scope.update(kwargs)
2171
2172        scope["_scope"] = scope
2173        ctx.new_cache["scope"] = scope
2174
2175        # Run the child screen.
2176        ctx.scope = scope
2177        ctx.parent = weakref.ref(context)
2178
2179        # If we have any children, pass them to (possible) transclude
2180        if self.block.children:
2181            ctx.transclude = self.block
2182
2183        try:
2184            ast.execute(ctx)
2185        finally:
2186            del scope["_scope"]
2187
2188        if ctx.fail:
2189            context.fail = True
2190
2191    def copy_on_change(self, cache):
2192
2193        c = cache.get(self.serial, None)
2194        if c is None:
2195            return
2196
2197        self.ast.copy_on_change(c)
2198
2199    def used_screens(self, callback):
2200        callback(self.target)
2201
2202    def has_transclude(self):
2203        return self.block.has_transclude()
2204
2205
2206class SLScreen(SLBlock):
2207    """
2208    This represents a screen defined in the screen language 2.
2209    """
2210
2211    version = 0
2212
2213    # This screen's AST when the transcluded block is entirely
2214    # constant (or there is no transcluded block at all). This may be
2215    # the actual AST, or a copy.
2216    const_ast = None
2217
2218    # A copy of this screen's AST when the transcluded block is not
2219    # constant.
2220    not_const_ast = None
2221
2222    # The analysis
2223    analysis = None
2224
2225    layer = "'screens'"
2226    sensitive = "True"
2227
2228    def __init__(self, loc):
2229
2230        SLBlock.__init__(self, loc)
2231
2232        # The name of the screen.
2233        self.name = None
2234
2235        # Should this screen be declared as modal?
2236        self.modal = "False"
2237
2238        # The screen's zorder.
2239        self.zorder = "0"
2240
2241        # The screen's tag.
2242        self.tag = None
2243
2244        # The variant of screen we're defining.
2245        self.variant = "None" # expr.
2246
2247        # Should we predict this screen?
2248        self.predict = "None" # expr.
2249
2250        # Should this screen be sensitive.
2251        self.sensitive = "True"
2252
2253        # The parameters this screen takes.
2254        self.parameters = None
2255
2256        # The analysis object used for this screen, if the screen has
2257        # already been analyzed.
2258        self.analysis = None
2259
2260        # True if this screen has been prepared.
2261        self.prepared = False
2262
2263    def copy(self, transclude):
2264        rv = self.instantiate(transclude)
2265
2266        rv.name = self.name
2267        rv.modal = self.modal
2268        rv.zorder = self.zorder
2269        rv.tag = self.tag
2270        rv.variant = self.variant
2271        rv.predict = self.predict
2272        rv.parameters = self.parameters
2273        rv.sensitive = self.sensitive
2274
2275        rv.prepared = False
2276        rv.analysis = None
2277
2278        return rv
2279
2280    def define(self, location):
2281        """
2282        Defines a screen.
2283        """
2284
2285        renpy.display.screen.define_screen(
2286            self.name,
2287            self,
2288            modal=self.modal,
2289            zorder=self.zorder,
2290            tag=self.tag,
2291            variant=renpy.python.py_eval(self.variant),
2292            predict=renpy.python.py_eval(self.predict),
2293            parameters=self.parameters,
2294            location=self.location,
2295            layer=renpy.python.py_eval(self.layer),
2296            sensitive=self.sensitive,
2297            )
2298
2299    def analyze(self, analysis):
2300
2301        SLBlock.analyze(self, analysis)
2302
2303    def analyze_screen(self):
2304
2305        # Have we already been analyzed?
2306        if self.const_ast:
2307            return
2308
2309        key = (self.name, self.variant, self.location)
2310
2311        if key in scache.const_analyzed:
2312            self.const_ast = scache.const_analyzed[key]
2313            self.not_const_ast = scache.not_const_analyzed[key]
2314            return
2315
2316        self.const_ast = self
2317
2318        if self.has_transclude():
2319            self.not_const_ast = self.copy(NOT_CONST)
2320            self.not_const_ast.const_ast = self.not_const_ast
2321            targets = [ self.const_ast, self.not_const_ast ]
2322        else:
2323            self.not_const_ast = self.const_ast
2324            targets = [ self.const_ast ]
2325
2326        for ast in targets:
2327            analysis = ast.analysis = Analysis(None)
2328
2329            if ast.parameters:
2330                analysis.parameters(ast.parameters)
2331
2332            ast.analyze(analysis)
2333
2334            while not analysis.at_fixed_point():
2335                ast.analyze(analysis)
2336
2337        scache.const_analyzed[key] = self.const_ast
2338        scache.not_const_analyzed[key] = self.not_const_ast
2339        scache.updated = True
2340
2341    def unprepare_screen(self):
2342        self.prepared = False
2343
2344    def prepare_screen(self):
2345
2346        if self.prepared:
2347            return
2348
2349        self.analyze_screen()
2350
2351        # This version ensures we're not using the cache from an old
2352        # version of the screen.
2353        self.version += 1
2354
2355        self.const_ast.prepare(self.const_ast.analysis)
2356
2357        if self.not_const_ast is not self.const_ast:
2358            self.not_const_ast.prepare(self.not_const_ast.analysis)
2359
2360        self.prepared = True
2361
2362        if renpy.display.screen.get_profile(self.name).const:
2363            profile_log.write("CONST ANALYSIS %s", self.name)
2364
2365            new_constants = [ i for i in self.const_ast.analysis.global_constant if i not in renpy.pyanalysis.constants ]
2366            new_constants.sort()
2367            profile_log.write('    global_const: %s', " ".join(new_constants))
2368
2369            local_constants = list(self.const_ast.analysis.local_constant)
2370            local_constants.sort()
2371            profile_log.write('    local_const: %s', " ".join(local_constants))
2372
2373            not_constants = list(self.const_ast.analysis.not_constant)
2374            not_constants.sort()
2375            profile_log.write('    not_const: %s', " ".join(not_constants))
2376
2377    def execute(self, context):
2378        self.const_ast.keywords(context)
2379        SLBlock.execute(self.const_ast, context)
2380
2381    def report_traceback(self, name, last):
2382        if last:
2383            return None
2384
2385        if name == "__call__":
2386            return [ ]
2387
2388        return SLBlock.report_traceback(self, name, last)
2389
2390    def copy_on_change(self, cache):
2391        SLBlock.copy_on_change(self.const_ast, cache)
2392
2393    def __call__(self, *args, **kwargs):
2394        scope = kwargs["_scope"]
2395        debug = kwargs.get("_debug", False)
2396
2397        if self.parameters:
2398
2399            args = scope.get("_args", ())
2400            kwargs = scope.get("_kwargs", { })
2401
2402            values = renpy.ast.apply_arguments(self.parameters, args, kwargs, ignore_errors=renpy.display.predict.predicting)
2403            scope.update(values)
2404
2405        if not self.prepared:
2406            self.prepare_screen()
2407
2408        current_screen = renpy.display.screen.current_screen()
2409
2410        if current_screen.screen_name[0] in renpy.config.profile_screens:
2411            debug = True
2412
2413        context = SLContext()
2414
2415        context.scope = scope
2416        context.root_scope = scope
2417        context.globals = renpy.python.store_dicts["store"]
2418        context.debug = debug
2419        context.predicting = renpy.display.predict.predicting
2420        context.updating = (current_screen.phase == renpy.display.screen.UPDATE)
2421
2422        name = scope["_name"]
2423
2424        def get_cache(d):
2425            rv = d.get(name, None)
2426
2427            if (not isinstance(rv, dict)) or (rv.get("version", None) != self.version):
2428                rv = { "version" : self.version }
2429                d[name] = rv
2430
2431            return rv
2432
2433        context.old_cache = get_cache(current_screen.cache)
2434        context.miss_cache = get_cache(current_screen.miss_cache)
2435        context.new_cache = { "version" : self.version }
2436
2437        context.old_use_cache = current_screen.use_cache
2438        context.new_use_cache = { }
2439
2440        # This really executes self.const_ast.
2441        self.execute(context)
2442
2443        for i in context.children:
2444            renpy.ui.implicit_add(i)
2445
2446        current_screen.cache[name] = context.new_cache
2447        current_screen.use_cache = context.new_use_cache
2448
2449
2450class ScreenCache(object):
2451
2452    def __init__(self):
2453        self.version = 1
2454
2455        self.const_analyzed = { }
2456        self.not_const_analyzed = { }
2457
2458        self.updated = False
2459
2460
2461scache = ScreenCache()
2462
2463CACHE_FILENAME = "cache/screens.rpyb"
2464
2465
2466def load_cache():
2467    if renpy.game.args.compile: # @UndefinedVariable
2468        return
2469
2470    try:
2471        with renpy.loader.load(CACHE_FILENAME) as f:
2472            digest = f.read(hashlib.md5().digest_size)
2473            if digest != renpy.game.script.digest.digest():
2474                return
2475
2476            s = loads(zlib.decompress(f.read()))
2477
2478        if s.version == scache.version:
2479            renpy.game.script.update_bytecode()
2480            scache.const_analyzed.update(s.const_analyzed)
2481            scache.not_const_analyzed.update(s.not_const_analyzed)
2482
2483    except:
2484        pass
2485
2486
2487def save_cache():
2488    if not scache.updated:
2489        return
2490
2491    if renpy.macapp:
2492        return
2493
2494    try:
2495        data = zlib.compress(dumps(scache, 2), 3)
2496
2497        with open(renpy.loader.get_path(CACHE_FILENAME), "wb") as f:
2498            f.write(renpy.game.script.digest.digest())
2499            f.write(data)
2500    except:
2501        pass
2502