Project

Profile

Help

How to connect?
Download (9.83 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / sharp / engine.py @ c89c6b5b

1
# Copyright (c) 2016, LE GOFF Vincent
2
# All rights reserved.
3

    
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are met:
6

    
7
# * Redistributions of source code must retain the above copyright notice, this
8
#   list of conditions and the following disclaimer.
9

    
10
# * Redistributions in binary form must reproduce the above copyright notice,
11
#   this list of conditions and the following disclaimer in the documentation
12
#   and/or other materials provided with the distribution.
13

    
14
# * Neither the name of ytranslate nor the names of its
15
#   contributors may be used to endorse or promote products derived from
16
#   this software without specific prior written permission.
17

    
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

    
29
"""Module containing the SharpEngine class."""
30

    
31
from textwrap import dedent
32

    
33
from sharp import FUNCTIONS
34

    
35
class SharpScript(object):
36

    
37
    """Class representing a SharpScript engine.
38

39
    An SharpScript engine is often linked with the game's main engine
40
    and an individual client, which is itself optionally linked to
41
    the ui application.
42

43
    """
44

    
45
    def __init__(self, engine, client):
46
        self.engine = engine
47
        self.client = client
48
        self.locals = {}
49
        self.globals = globals()
50

    
51
        # Adding the functions
52
        for name, function in FUNCTIONS.items():
53
            function = function(engine, client, self)
54
            self.globals[name] = function.run
55

    
56
    def execute(self, code):
57
        """Execute the SharpScript code given as an argument."""
58
        if isinstance(code, str):
59
            instructions = self.feed(code)
60
        else:
61
            instructions = [code]
62

    
63
        globals = self.globals
64
        locals = self.locals
65
        for instruction in instructions:
66
            exec(instruction, globals, locals)
67

    
68
    def feed(self, content):
69
        """Feed the SharpScript engine with a string content.
70

71
        The content is probably a file with several statements in
72
        SharpScript, or a single statement.  In all cases, this function
73
        returns the list of Python codes corresponding with
74
        this suite of statements.
75

76
        """
77
        # Execute Python code if necessary
78
        codes = []
79
        while content.startswith("{+"):
80
            end = self.find_right_brace(content)
81
            code = content[2:end - 1].lstrip("\n").rstrip("\n ")
82
            code = repr(dedent(code)).replace("\\n", "\n")
83
            code = "compile(" + code + ", 'SharpScript', 'exec')"
84
            codes.append(code)
85
            content = content[end + 1:]
86

    
87
        # The remaining must be SharpScript, splits into statements
88
        statements = self.split_statements(content)
89
        for statement in statements:
90
            pycode = self.convert_to_python(statement)
91
            codes.append(pycode)
92

    
93
        return codes
94

    
95
    def convert_to_python(self, statement):
96
        """Convert the statement to Python and return the str code.
97

98
        The statement given in argument should be a tuple:  The first
99
        argument of the tuple should be a function (like '#play' or
100
        '#send').  The remaining arguments should be put in a string,
101
        except for other Sharp or Python code.
102

103
        """
104
        function_name = statement[0][1:].lower()
105
        arguments = []
106
        kwargs = {}
107
        for argument in statement[1:]:
108
            if argument.startswith("{+"):
109
                argument = argument[2:-1].lstrip("\n").rstrip("\n ")
110
                argument = repr(dedent(argument)).replace("\\n", "\n")
111
                argument = "compile(" + argument + ", 'SharpScript', 'exec')"
112
            elif argument.startswith("{"):
113
                argument = repr(argument[1:-1])
114
                argument = self.replace_semicolons(argument)
115
            elif argument[0] in "-+":
116
                kwargs[argument[1:]] = True if argument[0] == "+" else False
117
                continue
118
            else:
119
                argument = repr(argument)
120
                argument = self.replace_semicolons(argument)
121

    
122
            arguments.append(argument)
123

    
124
        code = function_name + "(" + ", ".join(arguments)
125
        if arguments and kwargs:
126
            code += ", "
127

    
128
        code += ", ".join([name + "=" + repr(value) for name, value in \
129
                kwargs.items()])
130

    
131
        return code + ")"
132

    
133
    def split_statements(self, content):
134
        """Split the given string content into different statements.
135

136
        A statement is one-line short at the very least.  It can be
137
        longer by that, if it's enclosed into braces.
138

139
        """
140
        statements = []
141
        i = 0
142
        function_name = ""
143
        arguments = []
144
        while True:
145
            remaining = content[i:]
146

    
147
            # If remaining is empty, saves the statement and exits the loop
148
            if not remaining or remaining.isspace():
149
                if function_name:
150
                    statements.append((function_name, ) + tuple(arguments))
151

    
152
                break
153

    
154
            # If remaining begins with a new line
155
            if remaining[0] == "\n":
156
                if function_name:
157
                    statements.append((function_name, ) + tuple(arguments))
158
                    function_name = ""
159
                    arguments = []
160

    
161
                i += 1
162
                continue
163

    
164
            # If remaining begins with a space
165
            if remaining[0].isspace():
166
                remaining = remaining[1:]
167
                i += 1
168
                continue
169

    
170
            # If the function_name is not defined, take the first parameter
171
            if not function_name:
172
                if remaining.startswith("#") and not remaining.startswith(
173
                        "##"):
174
                    # This is obviously a function name
175
                    function_name = remaining.splitlines()[0].split(" ")[0]
176
                    arguments = []
177
                    i += len(function_name)
178
                else:
179
                    function_name = "#send"
180
                    argument = remaining.splitlines()[0]
181
                    i += len(argument)
182

    
183
                    if argument.startswith("##"):
184
                        argument = argument[1:]
185

    
186
                    arguments = [argument]
187
            elif remaining[0] == "{":
188
                end = self.find_right_brace(remaining)
189
                argument = remaining[:end + 1]
190
                i += end + 1
191
                if argument.startswith("##"):
192
                    argument = argument[1:]
193

    
194
                arguments.append(argument)
195
            else:
196
                argument = remaining.splitlines()[0].split(" ")[0]
197
                i += len(argument)
198
                if argument.startswith("##"):
199
                    argument = argument[1:]
200

    
201
                arguments.append(argument)
202

    
203
        return statements
204

    
205
    def find_right_brace(self, text):
206
        """Find the right brace matching the opening one.
207

208
        This function doesn't only look for the first right brace (}).
209
        It looks for a brace that would close the text and return the
210
        position of this character.  For instance:
211
            >>> Engine.find_right_brace("{first parameter {with} something} else")
212
            33
213

214
        """
215
        level = 0
216
        i = 0
217
        while i < len(text):
218
            char = text[i]
219
            if char == "{":
220
                level += 1
221
            elif char == "}":
222
                level -= 1
223

    
224
            if level == 0:
225
                return i
226

    
227
            i += 1
228

    
229
        return None
230

    
231
    @staticmethod
232
    def replace_semicolons(text):
233
        """Replace all not-escaped semi-colons."""
234
        i = 0
235
        while i < len(text):
236
            remaining = text[i:]
237
            if remaining.startswith(";;"):
238
                i += 2
239
                continue
240
            elif remaining.startswith(";"):
241
                text = text[:i] + "\n" + text[i + 1:]
242
            i += 1
243

    
244
        return text.replace(";;", ";")
245

    
246
    def format(self, content):
247
        """Write SharpScript and return a string.
248

249
        This method takes as argument the SharpScript content and formats it.  It therefore replaces the default formatting.  Arguments are escaped this way:
250

251
        * If the argument contains space, escape it with braces.
252
        * If the argument contains new line, indent it.
253
        * If the argument contains semi colons, keep it on one line.
254

255
        """
256
        instructions = self.split_statements(content)
257

    
258
        # At this stage, the instructions are formatted in Python
259
        lines = []
260
        for arguments in instructions:
261
            function = arguments[0].lower()
262
            arguments = list(arguments[1:])
263

    
264
            # Escape the arguments if necessary
265
            for i, argument in enumerate(arguments):
266
                arguments[i] = self.escape_argument(argument)
267

    
268
            line = function + " " + " ".join(arguments)
269
            lines.append(line.rstrip(" "))
270

    
271
        return "\n".join(lines)
272

    
273
    @staticmethod
274
    def escape_argument(argument):
275
        """Escape the argument if needed."""
276
        if argument.startswith("{"):
277
            pass
278
        elif "\n" in argument:
279
            lines = argument.splitlines()
280
            argument = "{" + "\n    ".join(lines) + "\n}"
281
        elif " " in argument:
282
            argument = "{" + argument + "}"
283

    
284
        return argument
(2-2/3)