github / src / sharp / engine.py @ fa348e33
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.locals)
|
54 |
self.globals[name] = function.run
|
55 |
|
56 |
def execute(self, code): |
57 |
"""Execute the SharpScript code given as an argument."""
|
58 |
instructions = self.feed(code)
|
59 |
globals = self.globals
|
60 |
locals = self.locals
|
61 |
for instruction in instructions: |
62 |
exec(instruction, globals, locals) |
63 |
|
64 |
def feed(self, content): |
65 |
"""Feed the SharpScript engine with a string content.
|
66 |
|
67 |
The content is probably a file with several statements in
|
68 |
SharpScript, or a single statement. In all cases, this function
|
69 |
returns the list of Python codes corresponding with
|
70 |
this suite of statements.
|
71 |
|
72 |
"""
|
73 |
# First, splits into statements
|
74 |
statements = self.split_statements(content)
|
75 |
codes = [] |
76 |
for statement in statements: |
77 |
pycode = self.convert_to_python(statement)
|
78 |
codes.append(pycode) |
79 |
|
80 |
return codes
|
81 |
|
82 |
def convert_to_python(self, statement): |
83 |
"""Convert the statement to Python and return the str code.
|
84 |
|
85 |
The statement given in argument should be a tuple: The first
|
86 |
argument of the tuple should be a function (like '#play' or
|
87 |
'#send'). The remaining arguments should be put in a string,
|
88 |
except for other Sharp or Python code.
|
89 |
|
90 |
"""
|
91 |
function_name = statement[0][1:].lower() |
92 |
arguments = [] |
93 |
for argument in statement[1:]: |
94 |
if argument.startswith("{+"): |
95 |
argument = argument[3:-2] |
96 |
code = dedent(argument) |
97 |
argument = "compile(" + repr(code) + ", 'SharpScript', 'exec')" |
98 |
elif argument.startswith("{"): |
99 |
argument = repr(argument[1:-1]) |
100 |
else:
|
101 |
argument = repr(argument)
|
102 |
|
103 |
arguments.append(argument) |
104 |
|
105 |
return function_name + "(" + ", ".join(arguments) + ")" |
106 |
|
107 |
def split_statements(self, content): |
108 |
"""Split the given string content into different statements.
|
109 |
|
110 |
A statement is one-line short at the very least. It can be
|
111 |
longer by that, if it's enclosed into braces.
|
112 |
|
113 |
"""
|
114 |
statements = [] |
115 |
i = 0
|
116 |
function_name = ""
|
117 |
arguments = [] |
118 |
while True: |
119 |
remaining = content[i:] |
120 |
|
121 |
# If remaining is empty, saves the statement and exits the loop
|
122 |
if not remaining or remaining.isspace(): |
123 |
if function_name:
|
124 |
statements.append((function_name, ) + tuple(arguments))
|
125 |
|
126 |
break
|
127 |
|
128 |
# If remaining begins with a new line
|
129 |
if remaining[0] == "\n": |
130 |
if function_name:
|
131 |
statements.append((function_name, ) + tuple(arguments))
|
132 |
function_name = ""
|
133 |
arguments = [] |
134 |
|
135 |
i += 1
|
136 |
continue
|
137 |
|
138 |
# If remaining begins with a space
|
139 |
if remaining[0].isspace(): |
140 |
remaining = remaining[1:]
|
141 |
i += 1
|
142 |
continue
|
143 |
|
144 |
# If the function_name is not defined, take the first parameter
|
145 |
if not function_name: |
146 |
if remaining.startswith("#"): |
147 |
# This is obviously a function name
|
148 |
function_name = remaining.splitlines()[0].split(" ")[0] |
149 |
arguments = [] |
150 |
i += len(function_name)
|
151 |
else:
|
152 |
function_name = "#send"
|
153 |
argument = remaining.splitlines()[0]
|
154 |
i += len(argument)
|
155 |
arguments = [argument] |
156 |
elif remaining[0] == "{": |
157 |
end = self.find_right_brace(remaining)
|
158 |
argument = remaining[:end + 1]
|
159 |
arguments.append(argument) |
160 |
i += end + 1
|
161 |
else:
|
162 |
argument = remaining.splitlines()[0].split(" ")[0] |
163 |
i += len(argument)
|
164 |
arguments.append(argument) |
165 |
|
166 |
return statements
|
167 |
|
168 |
def find_right_brace(self, text): |
169 |
"""Find the right brace matching the opening one.
|
170 |
|
171 |
This function doesn't only look for the first right brace (}).
|
172 |
It looks for a brace that would close the text and return the
|
173 |
position of this character. For instance:
|
174 |
>>> Engine.find_right_brace("{first parameter {with} something} else")
|
175 |
33
|
176 |
|
177 |
"""
|
178 |
level = 0
|
179 |
i = 0
|
180 |
while i < len(text): |
181 |
char = text[i] |
182 |
if char == "{": |
183 |
level += 1
|
184 |
elif char == "}": |
185 |
level -= 1
|
186 |
|
187 |
if level == 0: |
188 |
return i
|
189 |
|
190 |
i += 1
|
191 |
|
192 |
return None |