ScratchAnalyzer 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ def toCode(value):
2
+ if isinstance(value, int) or isinstance(value, float):
3
+ code = str(value)
4
+ else:
5
+ try:
6
+ value = float(value)
7
+ if int(value) == value:
8
+ value = int(value)
9
+ code = str(value) # 判断是否是合法的数字字符串
10
+ except ValueError:
11
+ code = f'"{value}"'
12
+ return code
@@ -0,0 +1,277 @@
1
+ import re
2
+
3
+ from .errors import UnsupportedError
4
+ from .iostream import ColoredTqdm
5
+ from .Cast import toCode
6
+ from .public import substack_opcodes, substack_opcodes_need_flush
7
+ import json
8
+ import warnings
9
+
10
+
11
+ class Input(object):
12
+ def __init__(self, _input, target, name):
13
+ self.data = _input
14
+ self.target = target
15
+ self.name = name
16
+
17
+ def toCode(self, translator, func_name, procedures_prototypes):
18
+ if isinstance(self.data[1], str):
19
+ try:
20
+ fieldBlock = self.target.blocks[self.data[1]]
21
+ field = fieldBlock.fields[self.name]
22
+ code = field.toCode(translator)
23
+ except KeyError:
24
+ try:
25
+ blockId = self.data[1]
26
+ code, _ = self.target.blocks[blockId].toCode(translator, 1, func_name, procedures_prototypes)
27
+ except KeyError:
28
+ code = f'Scratch.getVariable(instance, "{self.data[1]}")'
29
+ elif self.data[1]:
30
+ value = self.data[1][1]
31
+ code = f"!!![SPECIAL_CODE_TO_GLOBAL][{value}]!!!"
32
+ else:
33
+ code = '""'
34
+ return code
35
+
36
+ class Field(object):
37
+ def __init__(self, field, target):
38
+ self.data = field
39
+ self.target = target
40
+
41
+ def toCode(self, translator):
42
+ return toCode(self.data[0])
43
+
44
+ class Block(object):
45
+ def __init__(self, block, target):
46
+ # 获取数据
47
+ try:
48
+ self.opcode = block["opcode"]
49
+ except TypeError:
50
+ raise TypeError(f"非法block,没有opcode!: {block}\n\n{target._blocks}")
51
+ self._next = block["next"]
52
+ self.next = None
53
+ self._parent = block.get("parent")
54
+ self.parent = None
55
+ self._inputs = block["inputs"]
56
+ self.inputs = {}
57
+ self._fields = block["fields"]
58
+ self.fields = {}
59
+ self.shadow = block["shadow"]
60
+ self.topLevel = block["topLevel"]
61
+ self.target = target
62
+ self.computed = False
63
+ self.comment = block.get("comment")
64
+ self.data = block
65
+
66
+ def getComment(self, indent, uniqueEnv=False):
67
+ if self.comment and (self.opcode not in substack_opcodes if not uniqueEnv else True):
68
+ text = self.target.comments[self.comment]["text"]
69
+ return f" # {text}".replace("\n", " " * indent + "# ") # 保证多行注释正常
70
+ return ""
71
+
72
+ def compute_relation(self):
73
+ # 计算关联
74
+ self.target = self.target
75
+ self.next = self.target.blocks[self._next] if self._next else None
76
+ self.parent = self.target.blocks[self._parent] if self._parent else None
77
+ self.inputs = {k: Input(v, self.target, k) for k, v in self._inputs.items()}
78
+ self.fields = {k: Field(v, self.target) for k, v in self._fields.items()}
79
+ self.computed = True
80
+
81
+ def generateArgs(self, arg_ids, args, translator, func_name, procedures_prototypes):
82
+ result = {}
83
+ for arg_id in arg_ids:
84
+ if arg_id not in args: # 没有传入参数则跳过,内部会使用默认值
85
+ continue
86
+ value = args[arg_id].toCode(translator, func_name, procedures_prototypes)
87
+ result[arg_id] = value
88
+ return json.dumps(result, ensure_ascii=False)
89
+
90
+ def toCode(self, translator, indent, func_name, procedures_prototypes):
91
+ if self.opcode not in substack_opcodes: # shadow指的是是否有SUBSTACK(子积木,就比如“如果”里包着的积木)
92
+ try:
93
+ code = getattr(translator, self.opcode)
94
+ except AttributeError:
95
+ warnings.warn("不支持的积木操作代码: %s,若警告不会停止则默认继续转换(无法翻译的代码将以注释替代!)" % self.opcode)
96
+ code = f"raise RuntimeError('未成功翻译的代码,操作代码:{self.opcode},请手动翻译!') # !!!不支持的积木操作代码: {self.opcode},请手动翻译!!!"
97
+ # 自定义函数执行的特殊处理
98
+ if self.opcode == "procedures_call":
99
+ code = (code.replace("%[FUNC_NAME]%", "!!![FUNC_NAME_TO_GLOBAL][{proccode}]!!!".format(proccode=self.data["mutation"]["proccode"]))
100
+ .replace("%[ARGS]%", "!!![ARGS_TO_GLOBAL][{args}]!!!").format(args=self.generateArgs(
101
+ json.loads(self.data["mutation"]["argumentids"]),
102
+ self.inputs,
103
+ translator, func_name, procedures_prototypes
104
+ )))
105
+ for name, inp in self.inputs.items():
106
+ code = code.replace(f'%[{name}]%', inp.toCode(translator, func_name, procedures_prototypes))
107
+ for name, field in self.fields.items():
108
+ code = code.replace(f'%[{name}]%', field.toCode(translator))
109
+ pattern = r'%\[.*?\]%' # 非贪婪匹配
110
+ code = re.sub(pattern, 'None', code)
111
+ if self.opcode == "control_stop" and self.fields["STOP_OPTION"].data[0] != "other scripts in sprite":
112
+ code += "\n" + " " * indent + "return"
113
+ if self.opcode == "control_delete_this_clone":
114
+ code += "\n" + " " * indent + "if instance.isClone:"
115
+ code += "\n" + " " * (indent+1) + "return"
116
+ else:
117
+ try:
118
+ code = getattr(translator, self.opcode) # 获取主体代码
119
+ except AttributeError:
120
+ warnings.warn("不支持的积木操作代码: %s,若警告不会停止则默认继续转换(无法翻译的代码将以注释替代!)" % self.opcode)
121
+ code = f"raise RuntimeError('未成功翻译的代码,操作代码:{self.opcode},请手动翻译!') # !!!不支持的积木操作代码: {self.opcode},请手动翻译!!!"
122
+ # 将参数格式化进去
123
+ for name, inp in self.inputs.items():
124
+ if name in ("SUBSTACK", "SUBSTACK2"):
125
+ continue
126
+ code = code.replace(f'%[{name}]%', inp.toCode(translator, func_name, procedures_prototypes)) + self.getComment(indent + 1, uniqueEnv=True)
127
+ for name, field in self.fields.items():
128
+ code = code.replace(f'%[{name}]%', field.toCode(translator))
129
+ pattern = r'%\[.*?\]%' # 非贪婪匹配
130
+ code = re.sub(pattern, 'None', code)
131
+ indent += 1
132
+ code += "\n"
133
+ if self.inputs["SUBSTACK"].data[1]:
134
+ head = self.target.blocks[self.inputs["SUBSTACK"].data[1]]
135
+ block = head
136
+ while True:
137
+ cd, indent = block.toCode(translator, indent, func_name, procedures_prototypes)
138
+ code += (" " * indent) + cd + block.getComment(indent) + "\n"
139
+ block = block.next
140
+ if not block:
141
+ break
142
+ if substack_opcodes_need_flush[self.opcode]:
143
+ if func_name in procedures_prototypes and procedures_prototypes[func_name]["warp"]:
144
+ suffix = ""
145
+ else:
146
+ suffix = "await instance.wait_next_frame() # 普通情况:每次循环末尾都等待刷新"
147
+ else:
148
+ suffix = ""
149
+ code += (" " * indent) + suffix + "\n"
150
+ else:
151
+ code += (" " * indent) + "...\n"
152
+ if "SUBSTACK2" in self.inputs:
153
+ code += "{indent}{before}\n".format(indent=" " * (indent-1),before=getattr(translator, self.opcode+"_before_2"))
154
+ if self.inputs["SUBSTACK2"].data[1]:
155
+ head = self.target.blocks[self.inputs["SUBSTACK2"].data[1]]
156
+ block = head
157
+ while True:
158
+ cd, indent = block.toCode(translator, indent, func_name, procedures_prototypes)
159
+ code += (" " * indent) + cd + block.getComment(indent) + "\n"
160
+ block = block.next
161
+ if not block:
162
+ break
163
+ if substack_opcodes_need_flush[self.opcode]:
164
+ suffix = "await instance.wait_next_frame() # 普通情况:每次循环末尾都等待刷新"
165
+ else:
166
+ suffix = ""
167
+ code += (" " * indent) + suffix + "\n"
168
+ else:
169
+ code += (" " * indent) + "...\n"
170
+ indent -= 1
171
+ return code, indent
172
+
173
+ class Target(object):
174
+ def __init__(self, target):
175
+ # 获取数据
176
+ self.isStage = target["isStage"]
177
+ self.name = target["name"]
178
+ self.variables = target["variables"]
179
+ self.lists = target["lists"]
180
+ self.broadcasts = target["broadcasts"]
181
+ self._blocks = target["blocks"]
182
+ self.comments = target["comments"]
183
+ self.currentCostume = target["currentCostume"]
184
+ self.costumes = target["costumes"]
185
+ self.sounds = target["sounds"]
186
+ self.volume = target["volume"]
187
+ self.layerOrder = target["layerOrder"]
188
+ self.visible = target.get("visible", True)
189
+ self.x = target.get("x", 0)
190
+ self.y = target.get("y", 0)
191
+ self.size = target.get("size", 100)
192
+ self.direction = target.get("direction", 90)
193
+ self.draggable = target.get("draggable", False)
194
+ self.rotationStyle = target.get("rotationStyle", "all around")
195
+ self.tempo = target.get("tempo")
196
+ # 解析剩余参数
197
+ self.args = {k: v for k, v in target.items() if k not in {
198
+ 'isStage',
199
+ 'name',
200
+ 'variables',
201
+ 'lists',
202
+ 'broadcasts',
203
+ 'blocks',
204
+ 'comments',
205
+ 'currentCostume',
206
+ 'costumes',
207
+ 'sounds',
208
+ 'volume',
209
+ 'layerOrder',
210
+ 'visible',
211
+ 'x',
212
+ 'y',
213
+ 'size',
214
+ 'direction',
215
+ 'draggable',
216
+ 'rotationStyle',
217
+ 'tempo'
218
+ }}
219
+ if self._blocks:
220
+ # 计算blocks
221
+ self.blocks = {k: Block(v, self) for k, v in ColoredTqdm(self._blocks.items(), desc="创建积木块中", unit="积木块") if isinstance(v, dict)}
222
+ # 计算关联
223
+ for block in ColoredTqdm(self.blocks.values(), desc="计算块关联中", unit="积木块"):
224
+ block.compute_relation()
225
+ else:
226
+ self.blocks = {}
227
+
228
+ class Monitor(object):
229
+ def __init__(self, monitor):
230
+ self.id = monitor["id"]
231
+ self.mode = monitor["mode"]
232
+ self.opcode = monitor["opcode"]
233
+ self.params = monitor["params"]
234
+ self.spriteName = monitor["spriteName"]
235
+ self.value = monitor["value"]
236
+ self.width = monitor["width"]
237
+ self.height = monitor["height"]
238
+ self.x = monitor["x"]
239
+ self.y = monitor["y"]
240
+ self.visible = monitor["visible"]
241
+ self.sliderMin = monitor.get("sliderMin")
242
+ self.sliderMax = monitor.get("sliderMax")
243
+ self.isDiscrete = monitor.get("isDiscrete")
244
+
245
+ class Extension(object):
246
+ def __init__(self, extension):
247
+ if extension not in ("pen", ):
248
+ warnings.warn("暂不支持扩展:%s,若警告不会停止则默认继续转换" % extension)
249
+
250
+ class Meta(object):
251
+ def __init__(self, meta):
252
+ self.semver = meta["semver"]
253
+ self.agent = meta["agent"]
254
+ self.vm = meta["vm"]
255
+
256
+ class Project(object):
257
+ def __init__(self, project):
258
+ self.project = project
259
+ self._parse()
260
+
261
+ def _parse(self):
262
+ if self.project.get("extensionURLs"):
263
+ warnings.warn("项目中包含自定义URL扩展,可能来自TurboWarp,当前版本不支持解析这些扩展,相关积木将无法转换!")
264
+ self.meta = Meta(self.project["meta"])
265
+ self.extensions = {}
266
+ if self.project["extensions"]:
267
+ for name in ColoredTqdm(self.project["extensions"], desc="处理扩展中", unit="个"):
268
+ self.extensions[name] = Extension(name)
269
+ self.monitors = {}
270
+ if self.project["monitors"]:
271
+ for monitor in ColoredTqdm(self.project["monitors"], desc="处理变量监视器中", unit="个"):
272
+ self.monitors[monitor["id"]] = Monitor(monitor)
273
+ self.targets = {}
274
+ if self.project["targets"]:
275
+ for target in ColoredTqdm(self.project["targets"], desc="处理角色中", unit="个"):
276
+ self.targets[target["name"]] = Target(target)
277
+
@@ -0,0 +1,235 @@
1
+ from typing import TypedDict, List, Optional, Dict, Any, Union
2
+
3
+ from .translator import WrappedTranslator
4
+
5
+
6
+ class MetaDict(TypedDict):
7
+ semver: str
8
+ vm: str
9
+ agent: str
10
+
11
+ class MonitorParamDict(TypedDict):
12
+ VARIABLE: str
13
+ LIST: str
14
+
15
+ class MonitorDict(TypedDict):
16
+ id: str
17
+ mode: str
18
+ opcode: str
19
+ params: MonitorParamDict
20
+ spriteName: Optional[str]
21
+ value: Any
22
+ width: int
23
+ height: int
24
+ x: float
25
+ y: float
26
+ visible: bool
27
+ sliderMin: Optional[float]
28
+ sliderMax: Optional[float]
29
+ isDiscrete: Optional[bool]
30
+
31
+ ValueInput = List[int, List[int , Any]] # type: ignore
32
+ CodeInput = List[int, str, List[int , Any]] # type: ignore
33
+ FieldInput = List[int, str, ...] # type: ignore
34
+ InputsDict = Dict[str, Union[ValueInput, CodeInput, FieldInput]]
35
+ FieldsDict = Dict[str, List[str, Optional[str]]] # type: ignore
36
+
37
+ class BlockDict(TypedDict):
38
+ opcode: str
39
+ next: Optional[str]
40
+ parent: Optional[str]
41
+ inputs: InputsDict
42
+ fields: FieldsDict
43
+ shadow: bool
44
+ topLevel: bool
45
+ comment: Optional[str]
46
+ mutation: Optional[dict]
47
+
48
+ VariablesDict = Dict[str, List[str, Any]] # type: ignore
49
+ ListsDict = Dict[str, List[str, Any]] # type: ignore
50
+ BroadcastsDict = Dict[str, str]
51
+ BlocksDict = Dict[str, BlockDict]
52
+
53
+ class Comment(TypedDict):
54
+ blockId: Optional[str]
55
+ x: float
56
+ y: float
57
+ width: float
58
+ height: float
59
+ minimized: bool
60
+ text: str
61
+
62
+ CommentsDict = Dict[str, Comment]
63
+
64
+ class CostumeDict(TypedDict):
65
+ name: str
66
+ bitmapResolution: int
67
+ dataFormat: str
68
+ assetId: str
69
+ md5ext: str
70
+ rotationCenterX: float
71
+ rotationCenterY: float
72
+
73
+ class Sound(TypedDict):
74
+ name: str
75
+ assetId: str
76
+ dataFormat: str
77
+ rate: int
78
+ sampleCount: int
79
+ md5ext: str
80
+
81
+ class TargetDict(TypedDict):
82
+ isStage: bool
83
+ name: str
84
+ variables: VariablesDict
85
+ lists: ListsDict
86
+ broadcasts: BroadcastsDict
87
+ blocks: BlocksDict
88
+ comments: CommentsDict
89
+ currentCostume: int
90
+ costumes: List[CostumeDict]
91
+ sounds: List[Sound]
92
+ volume: int
93
+ layerOrder: int
94
+ visible: Optional[bool]
95
+ x: Optional[float]
96
+ y: Optional[float]
97
+ size: Optional[float]
98
+ direction: Optional[float]
99
+ draggable: Optional[bool]
100
+ rotationStyle: Optional[Any]
101
+ tempo: Optional[int]
102
+ ...
103
+
104
+ class ProjectDict(TypedDict):
105
+ targets: List[TargetDict]
106
+ monitors: List[MonitorDict]
107
+ extensions: List[str]
108
+ extensionURLs: Optional[Dict[str, str]]
109
+ meta: MetaDict
110
+
111
+ # =========================
112
+
113
+ class Input(object):
114
+ data: list
115
+ target: Target
116
+ name: str
117
+
118
+ def __init__(self, _input: list, target: Target, name: str):
119
+ ...
120
+
121
+ def toCode(self, translator: WrappedTranslator, func_name: str, procedures_prototypes: dict[str, dict]) -> str:
122
+ ...
123
+
124
+ class Field(object):
125
+ data: list
126
+ target: Target
127
+
128
+ def __init__(self, field: list, target: Target):
129
+ ...
130
+
131
+ def toCode(self, translator: WrappedTranslator) -> str:
132
+ ...
133
+
134
+ class Block(object):
135
+ opcode: str
136
+ _next: Optional[str]
137
+ next: Optional[Block]
138
+ _parent: Optional[str]
139
+ parent: Optional[Block]
140
+ _inputs: InputsDict
141
+ inputs: Dict[str, Input]
142
+ _fields: FieldsDict
143
+ fields: Dict[str, Field]
144
+ shadow: bool
145
+ topLevel: bool
146
+ target: Target
147
+ computed: bool
148
+ comment: Optional[str]
149
+ data: BlockDict
150
+
151
+ def __init__(self, block: BlockDict, target: Target):
152
+ ...
153
+
154
+ def getComment(self, indent: int, uniqueEnv: bool=False) -> str:
155
+ ...
156
+
157
+ def compute_relation(self):
158
+ ...
159
+
160
+ def generateArgs(self, arg_ids: list[str], args: dict[str, Input], translator: WrappedTranslator, func_name: str, procedures_prototypes: dict[str, dict]) -> str:
161
+ ...
162
+
163
+ def toCode(self, translator: WrappedTranslator, indent: int, func_name: str, procedures_prototypes: dict[str, dict]) -> tuple[str, int]:
164
+ ...
165
+
166
+ class Target(object):
167
+ isStage: bool
168
+ name: str
169
+ variables: VariablesDict
170
+ lists: ListsDict
171
+ broadcasts: BroadcastsDict
172
+ _blocks: BlocksDict
173
+ blocks: Dict[str, Block]
174
+ comments: CommentsDict
175
+ currentCostume: int
176
+ costumes: List[CostumeDict]
177
+ sounds: List[Sound]
178
+ volume: int
179
+ layerOrder: int
180
+ visible: Optional[bool]
181
+ x: Optional[float]
182
+ y: Optional[float]
183
+ size: Optional[float]
184
+ direction: Optional[float]
185
+ draggable: Optional[bool]
186
+ rotationStyle: Optional[Any]
187
+ tempo: Optional[int]
188
+ args: Dict[str, Any]
189
+
190
+ def __init__(self, target: TargetDict):
191
+ ...
192
+
193
+ class Monitor(object):
194
+ id: str
195
+ mode: str
196
+ opcode: str
197
+ params: MonitorParamDict
198
+ spriteName: Optional[str]
199
+ value: Any
200
+ width: int
201
+ height: int
202
+ x: float
203
+ y: float
204
+ visible: bool
205
+ sliderMin: Optional[float]
206
+ sliderMax: Optional[float]
207
+ isDiscrete: Optional[bool]
208
+
209
+ def __init__(self, monitor: MonitorDict):
210
+ ...
211
+
212
+ class Extension(object):
213
+ def __init__(self, extension: str):
214
+ ...
215
+
216
+ class Meta(object):
217
+ semver: str
218
+ vm: str
219
+ agent: str
220
+
221
+ def __init__(self, meta: MetaDict):
222
+ ...
223
+
224
+ class Project(object):
225
+ project: ProjectDict
226
+ targets: Dict[str, Target]
227
+ monitors: Dict[str, Monitor]
228
+ extensions: Dict[str, Extension]
229
+ meta: Meta
230
+
231
+ def __init__(self, project: ProjectDict):
232
+ ...
233
+
234
+ def _parse(self) -> None:
235
+ ...