ScratchAnalyzer 0.1.0__tar.gz
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.
- scratchanalyzer-0.1.0/LICENSE +21 -0
- scratchanalyzer-0.1.0/PKG-INFO +90 -0
- scratchanalyzer-0.1.0/README.md +76 -0
- scratchanalyzer-0.1.0/pyproject.toml +34 -0
- scratchanalyzer-0.1.0/setup.cfg +4 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/Cast.py +12 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/Project.py +277 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/Project.pyi +235 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/Scratch.py +416 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/Scratch.pyi +95 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/__init__.py +6 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/__main__.py +5 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/assets/codeMain.python.tpl +8 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/assets/progMain.python.tpl +29 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/assets/translator.python.json +126 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/errors.py +2 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/iostream.py +103 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/iostream.pyi +116 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/main.py +78 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/public.py +41 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer/translator.py +20 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer.egg-info/PKG-INFO +90 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer.egg-info/SOURCES.txt +25 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer.egg-info/dependency_links.txt +1 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer.egg-info/entry_points.txt +2 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer.egg-info/requires.txt +2 -0
- scratchanalyzer-0.1.0/src/ScratchAnalyzer.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PerfectMYGHY
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ScratchAnalyzer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Scratch parser that can generate Python code, ensuring that the logic remains as it is, only unable to run.
|
|
5
|
+
Author-email: PerfectMYGHY <916881890@qq.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PerfectMYGHY/scratch-analyzer
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: colorama==0.4.6
|
|
12
|
+
Requires-Dist: tqdm==4.67.3
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# scratch-analyzer
|
|
16
|
+
|
|
17
|
+
English version README.md: [README.english.md](README.english.md)
|
|
18
|
+
|
|
19
|
+
Scratch 解析库。一个使用Python制作的能够分析Python代码的软件包。
|
|
20
|
+
|
|
21
|
+
## 介绍
|
|
22
|
+
|
|
23
|
+
### 历史
|
|
24
|
+
|
|
25
|
+
基础代码再2025年(发布仓库的1年前)编写,当时我有一个梦想,将Scratch翻译成一个Python上能够运行的程序,于是我写下了这个程序,原名`Scratch2Python`。
|
|
26
|
+
|
|
27
|
+
我~~凭借着我惊人的智慧~~写出了`Scratch2Python`的Scratch转换Python代码功能,虽然代码有一丢烂,但是转换相当成功。
|
|
28
|
+
|
|
29
|
+
然而,接下来的问题是,Scratch运行时阻挠了我。从代码历史你能看到,本项目最终使用`Scratch4Python`作为运行时库,但是很可惜,我跟他耗了几个月,最终失败。
|
|
30
|
+
|
|
31
|
+
### 那么此库为何而在
|
|
32
|
+
|
|
33
|
+
因为我的这个翻译器还是太好了,理论上还可以做到翻译成各种语言,虽然目前仅保证转换为Python是正常的。
|
|
34
|
+
|
|
35
|
+
我认为这个功能很不错,于是建立存储库并单独提取其代码转换功能。
|
|
36
|
+
|
|
37
|
+
### 那Scratch4Python呢
|
|
38
|
+
|
|
39
|
+
我不打算完全放弃他,但是请等我深造几年,吃透OpenGL后,再管它吧。我想到时候将他作为独立库发布,同时更名`python-scratch-vm`。到时候可以两个库结合使用。
|
|
40
|
+
|
|
41
|
+
### 目录结构
|
|
42
|
+
|
|
43
|
+
```folder
|
|
44
|
+
├── docs # 文档注释文件夹
|
|
45
|
+
│ ├── README.chinese.md # 中文文档
|
|
46
|
+
│ └── README.english.md # 英文文档
|
|
47
|
+
├── LICENSE # MIT
|
|
48
|
+
├── pyproject.toml # 项目配置
|
|
49
|
+
├── README.md -> docs/README.chinese.md # 自述文件符号链接
|
|
50
|
+
├── requirements.txt # 需求文件
|
|
51
|
+
└── src # 源代码
|
|
52
|
+
└── ScratchAnalyzer # 软件包目录
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 使用
|
|
56
|
+
|
|
57
|
+
首先下载软件包。注意使用时软件包名为`ScratchAnalyzer`。
|
|
58
|
+
|
|
59
|
+
### 准备
|
|
60
|
+
|
|
61
|
+
首先,请准备好Scratch项目的`project.json`,将Scratch文件的后缀名更改为`.zip`,然后解压,即可得到素材文件和`project.json`。分析文件只需要`project.json`,但是如果需要运行等,请加上素材文件。
|
|
62
|
+
|
|
63
|
+
### 分析
|
|
64
|
+
|
|
65
|
+
假设已经准备好环境,使用方法如下:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from ScratchAnalyzer import Project, Scratch
|
|
69
|
+
import json
|
|
70
|
+
from pathlib import Path
|
|
71
|
+
|
|
72
|
+
# 1.读取文件,需要解析为字典
|
|
73
|
+
with open("project.json", "r", encoding="utf-8") as file: # 使用UTF-8确保不会编码错误
|
|
74
|
+
data = json.load(file)
|
|
75
|
+
|
|
76
|
+
# 2.创建Project对象,自动建立代码分析树,并存储项目元数据
|
|
77
|
+
project = Project(data)
|
|
78
|
+
|
|
79
|
+
# 3.创建Scratch对象,自动分析素材列表等元数据
|
|
80
|
+
scratch = Scratch(project) # 要传入project,这样就能得到所有数据
|
|
81
|
+
|
|
82
|
+
# 4.生产,由于生产的是多文件,所以要指定输出目录
|
|
83
|
+
scratch.generate(Path("output"), language="python") # 第一个参数为输出目录,language为可选参数,默认为"python",暂不支持其他语言,在此留个TODO
|
|
84
|
+
|
|
85
|
+
# 5.output下即生产结果,对象析构安全,不需要手动管理
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 注意
|
|
89
|
+
|
|
90
|
+
目前我还没有修改软件包接口,因此用起来有些困难。请等待我重构接口,以让这个软件包更好使用。
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# scratch-analyzer
|
|
2
|
+
|
|
3
|
+
English version README.md: [README.english.md](README.english.md)
|
|
4
|
+
|
|
5
|
+
Scratch 解析库。一个使用Python制作的能够分析Python代码的软件包。
|
|
6
|
+
|
|
7
|
+
## 介绍
|
|
8
|
+
|
|
9
|
+
### 历史
|
|
10
|
+
|
|
11
|
+
基础代码再2025年(发布仓库的1年前)编写,当时我有一个梦想,将Scratch翻译成一个Python上能够运行的程序,于是我写下了这个程序,原名`Scratch2Python`。
|
|
12
|
+
|
|
13
|
+
我~~凭借着我惊人的智慧~~写出了`Scratch2Python`的Scratch转换Python代码功能,虽然代码有一丢烂,但是转换相当成功。
|
|
14
|
+
|
|
15
|
+
然而,接下来的问题是,Scratch运行时阻挠了我。从代码历史你能看到,本项目最终使用`Scratch4Python`作为运行时库,但是很可惜,我跟他耗了几个月,最终失败。
|
|
16
|
+
|
|
17
|
+
### 那么此库为何而在
|
|
18
|
+
|
|
19
|
+
因为我的这个翻译器还是太好了,理论上还可以做到翻译成各种语言,虽然目前仅保证转换为Python是正常的。
|
|
20
|
+
|
|
21
|
+
我认为这个功能很不错,于是建立存储库并单独提取其代码转换功能。
|
|
22
|
+
|
|
23
|
+
### 那Scratch4Python呢
|
|
24
|
+
|
|
25
|
+
我不打算完全放弃他,但是请等我深造几年,吃透OpenGL后,再管它吧。我想到时候将他作为独立库发布,同时更名`python-scratch-vm`。到时候可以两个库结合使用。
|
|
26
|
+
|
|
27
|
+
### 目录结构
|
|
28
|
+
|
|
29
|
+
```folder
|
|
30
|
+
├── docs # 文档注释文件夹
|
|
31
|
+
│ ├── README.chinese.md # 中文文档
|
|
32
|
+
│ └── README.english.md # 英文文档
|
|
33
|
+
├── LICENSE # MIT
|
|
34
|
+
├── pyproject.toml # 项目配置
|
|
35
|
+
├── README.md -> docs/README.chinese.md # 自述文件符号链接
|
|
36
|
+
├── requirements.txt # 需求文件
|
|
37
|
+
└── src # 源代码
|
|
38
|
+
└── ScratchAnalyzer # 软件包目录
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 使用
|
|
42
|
+
|
|
43
|
+
首先下载软件包。注意使用时软件包名为`ScratchAnalyzer`。
|
|
44
|
+
|
|
45
|
+
### 准备
|
|
46
|
+
|
|
47
|
+
首先,请准备好Scratch项目的`project.json`,将Scratch文件的后缀名更改为`.zip`,然后解压,即可得到素材文件和`project.json`。分析文件只需要`project.json`,但是如果需要运行等,请加上素材文件。
|
|
48
|
+
|
|
49
|
+
### 分析
|
|
50
|
+
|
|
51
|
+
假设已经准备好环境,使用方法如下:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from ScratchAnalyzer import Project, Scratch
|
|
55
|
+
import json
|
|
56
|
+
from pathlib import Path
|
|
57
|
+
|
|
58
|
+
# 1.读取文件,需要解析为字典
|
|
59
|
+
with open("project.json", "r", encoding="utf-8") as file: # 使用UTF-8确保不会编码错误
|
|
60
|
+
data = json.load(file)
|
|
61
|
+
|
|
62
|
+
# 2.创建Project对象,自动建立代码分析树,并存储项目元数据
|
|
63
|
+
project = Project(data)
|
|
64
|
+
|
|
65
|
+
# 3.创建Scratch对象,自动分析素材列表等元数据
|
|
66
|
+
scratch = Scratch(project) # 要传入project,这样就能得到所有数据
|
|
67
|
+
|
|
68
|
+
# 4.生产,由于生产的是多文件,所以要指定输出目录
|
|
69
|
+
scratch.generate(Path("output"), language="python") # 第一个参数为输出目录,language为可选参数,默认为"python",暂不支持其他语言,在此留个TODO
|
|
70
|
+
|
|
71
|
+
# 5.output下即生产结果,对象析构安全,不需要手动管理
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 注意
|
|
75
|
+
|
|
76
|
+
目前我还没有修改软件包接口,因此用起来有些困难。请等待我重构接口,以让这个软件包更好使用。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ScratchAnalyzer"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Scratch parser that can generate Python code, ensuring that the logic remains as it is, only unable to run."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "PerfectMYGHY", email = "916881890@qq.com"}
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"colorama==0.4.6",
|
|
17
|
+
"tqdm==4.67.3"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/PerfectMYGHY/scratch-analyzer"
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
analyze-scratch = "ScratchAnalyzer.__main__:main" # 命令行入口
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
ScratchAnalyzer = [
|
|
28
|
+
"assets/*.json", # 包含所有 JSON 文件
|
|
29
|
+
"assets/*.tpl" # 包含模板文件
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
|
34
|
+
include = ["ScratchAnalyzer*"]
|
|
@@ -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
|
+
|