pyscreeps-arena 0.5.6.6__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.
- pyscreeps_arena/__init__.py +51 -0
- pyscreeps_arena/build.py +7 -0
- pyscreeps_arena/compiler.py +1221 -0
- pyscreeps_arena/core/__init__.py +1 -0
- pyscreeps_arena/core/basic.py +72 -0
- pyscreeps_arena/core/config.py +19 -0
- pyscreeps_arena/core/const.py +28 -0
- pyscreeps_arena/core/core.py +132 -0
- pyscreeps_arena/core/main.py +9 -0
- pyscreeps_arena/core/utils.py +11 -0
- pyscreeps_arena/localization.py +146 -0
- pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena/ui/P2PY.py +108 -0
- pyscreeps_arena/ui/__init__.py +10 -0
- pyscreeps_arena/ui/project_ui.py +214 -0
- pyscreeps_arena/ui/rs_icon.py +43 -0
- pyscreeps_arena-0.5.6.6.dist-info/METADATA +50 -0
- pyscreeps_arena-0.5.6.6.dist-info/RECORD +21 -0
- pyscreeps_arena-0.5.6.6.dist-info/WHEEL +5 -0
- pyscreeps_arena-0.5.6.6.dist-info/entry_points.txt +4 -0
- pyscreeps_arena-0.5.6.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
|
|
3
|
+
from pyscreeps_arena.core import *
|
|
4
|
+
from pyscreeps_arena.localization import *
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import chardet
|
|
9
|
+
import subprocess
|
|
10
|
+
import pyperclip
|
|
11
|
+
from colorama import Fore
|
|
12
|
+
|
|
13
|
+
WAIT = Fore.YELLOW + ">>>" + Fore.RESET
|
|
14
|
+
GREEN = Fore.GREEN + "{}" + Fore.RESET
|
|
15
|
+
python_version_info = sys.version_info
|
|
16
|
+
python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def replace_src_prefix(file_list):
|
|
20
|
+
"""
|
|
21
|
+
将列表中以'./src.'开头的字符串替换为'./'
|
|
22
|
+
|
|
23
|
+
参数:
|
|
24
|
+
file_list: 字符串列表
|
|
25
|
+
|
|
26
|
+
返回:
|
|
27
|
+
替换后的新列表
|
|
28
|
+
"""
|
|
29
|
+
_ = []
|
|
30
|
+
|
|
31
|
+
for item in file_list:
|
|
32
|
+
if isinstance(item, str) and item.startswith('./src.'):
|
|
33
|
+
_new = item.replace('./src.', './', 1)
|
|
34
|
+
if _new in file_list:
|
|
35
|
+
continue
|
|
36
|
+
_.append(item)
|
|
37
|
+
|
|
38
|
+
return _
|
|
39
|
+
|
|
40
|
+
# def InsertPragmaBefore(content:str) -> str:
|
|
41
|
+
# """
|
|
42
|
+
# 在content的开头插入__pragma__('noalias', 'undefined')等内容 |
|
|
43
|
+
# Insert __pragma__('noalias', 'undefined') at the beginning of content
|
|
44
|
+
# :param content: str
|
|
45
|
+
# :return: str
|
|
46
|
+
# """
|
|
47
|
+
# return PYFILE_PRAGMA_INSERTS + "\n" + content
|
|
48
|
+
class Compiler_Const:
|
|
49
|
+
PROTO_DEFINES_DIRS = ["builtin", "library"]
|
|
50
|
+
FILE_STRONG_REPLACE = {
|
|
51
|
+
"std": {
|
|
52
|
+
"==": "===",
|
|
53
|
+
"!=": "!==",
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
PYFILE_IGNORE_CHECK_FNAMES = ['builtin/const.py', 'builtin/proto.py', 'builtin/utils.py']
|
|
57
|
+
|
|
58
|
+
PYFILE_PRAGMA_INSERTS = """
|
|
59
|
+
# __pragma__('noalias', 'undefined')
|
|
60
|
+
# __pragma__('noalias', 'Infinity')
|
|
61
|
+
# __pragma__('noalias', 'clear')
|
|
62
|
+
# __pragma__('noalias', 'get')
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
TOTAL_INSERT_AT_HEAD = """
|
|
66
|
+
import { createConstructionSite, findClosestByPath, findClosestByRange, findInRange, findPath, getCpuTime, getDirection, getHeapStatistics, getObjectById, getObjects, getObjectsByPrototype, getRange, getTerrainAt, getTicks,} from 'game/utils';
|
|
67
|
+
import { ConstructionSite as GameConstructionSite, Creep as GameCreep, GameObject as GameObjectProto, OwnedStructure, Resource as GameResource, Source as GameSource, Structure as GameStructure, StructureContainer as GameStructureContainer, StructureExtension as GameStructureExtension, StructureRampart as GameStructureRampart, StructureRoad as GameStructureRoad, StructureSpawn as GameStructureSpawn, StructureWall as GameStructureWall, StructureTower as GameStructureTower, Flag as GameFlag} from 'game/prototypes';
|
|
68
|
+
import { ATTACK, ATTACK_POWER, BODYPART_COST, BODYPART_HITS, BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT, BUILD_POWER, CARRY, CARRY_CAPACITY, CONSTRUCTION_COST, CONSTRUCTION_COST_ROAD_SWAMP_RATIO, CONSTRUCTION_COST_ROAD_WALL_RATIO, CONTAINER_CAPACITY, CONTAINER_HITS, CREEP_SPAWN_TIME, DISMANTLE_COST, DISMANTLE_POWER, ERR_BUSY, ERR_FULL, ERR_INVALID_ARGS, ERR_INVALID_TARGET, ERR_NAME_EXISTS, ERR_NOT_ENOUGH_ENERGY, ERR_NOT_ENOUGH_EXTENSIONS, ERR_NOT_ENOUGH_RESOURCES, ERR_NOT_FOUND, ERR_NOT_IN_RANGE, ERR_NOT_OWNER, ERR_NO_BODYPART, ERR_NO_PATH, ERR_TIRED, EXTENSION_ENERGY_CAPACITY, EXTENSION_HITS, HARVEST_POWER, HEAL, HEAL_POWER, LEFT, MAX_CONSTRUCTION_SITES, MAX_CREEP_SIZE, MOVE, OBSTACLE_OBJECT_TYPES, OK, RAMPART_HITS, RAMPART_HITS_MAX, RANGED_ATTACK, RANGED_ATTACK_DISTANCE_RATE, RANGED_ATTACK_POWER, RANGED_HEAL_POWER, REPAIR_COST, REPAIR_POWER, RESOURCES_ALL, RESOURCE_DECAY, RESOURCE_ENERGY, RIGHT, ROAD_HITS, ROAD_WEAROUT, SOURCE_ENERGY_REGEN, SPAWN_ENERGY_CAPACITY, SPAWN_HITS, STRUCTURE_PROTOTYPES, TERRAIN_PLAIN, TERRAIN_SWAMP, TERRAIN_WALL, TOP, TOP_LEFT, TOP_RIGHT, TOUGH, TOWER_CAPACITY, TOWER_COOLDOWN, TOWER_ENERGY_COST, TOWER_FALLOFF, TOWER_FALLOFF_RANGE, TOWER_HITS, TOWER_OPTIMAL_RANGE, TOWER_POWER_ATTACK, TOWER_POWER_HEAL, TOWER_POWER_REPAIR, TOWER_RANGE, WALL_HITS, WALL_HITS_MAX, WORK} from 'game/constants';
|
|
69
|
+
|
|
70
|
+
import {arenaInfo} from "game";
|
|
71
|
+
import {Visual} from "game/visual"
|
|
72
|
+
import {searchPath, CostMatrix} from "game/path-finder"
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
TOTAL_INSERT_BEFORE_MAIN = """
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
TOTAL_APPEND_ATEND = """
|
|
79
|
+
export var sch = Scheduler();
|
|
80
|
+
var monitor = Monitor(1);
|
|
81
|
+
know.now = 0;
|
|
82
|
+
|
|
83
|
+
StageMachineLogicMeta.__types__ = []; // 清空js首次构造时引入的数据
|
|
84
|
+
__init_before_k__();
|
|
85
|
+
let knowCost = 0;
|
|
86
|
+
let monitorCost = 0;
|
|
87
|
+
let stepCost = 0;
|
|
88
|
+
let timeLine = 0;
|
|
89
|
+
export var loop = function () {
|
|
90
|
+
get.handle();
|
|
91
|
+
know.now = get.now;
|
|
92
|
+
timeLine = get.cpu_us();
|
|
93
|
+
know.handle();
|
|
94
|
+
knowCost = get.cpu_us() - timeLine;
|
|
95
|
+
if (know.now === 1) {
|
|
96
|
+
std.show_welcome();
|
|
97
|
+
init (know);
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
timeLine = get.cpu_us();
|
|
102
|
+
monitor.handle();
|
|
103
|
+
monitorCost = get.cpu_us() - timeLine;
|
|
104
|
+
for (const creep of know.creeps){
|
|
105
|
+
creep.handle();
|
|
106
|
+
}
|
|
107
|
+
step (know);
|
|
108
|
+
timeLine = get.cpu_us();
|
|
109
|
+
if (get._SCH_FLAG) sch.handle();
|
|
110
|
+
stepCost = get.cpu_us() - timeLine;
|
|
111
|
+
std.show_usage ();
|
|
112
|
+
print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
|
|
113
|
+
if (know.draw) know.draw();
|
|
114
|
+
};
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
TOTAL_SIMPLE_REPLACE_WITH = {
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
PYFILE_WORD_WARNING_CHECK = {
|
|
121
|
+
r"\.\s*get\s*\(": LOC_PYFILE_WORD_WARNING_CHECK_GET,
|
|
122
|
+
r"import\s+math\s*": LOC_PYFILE_WORD_WARNING_CHECK_MATH,
|
|
123
|
+
r"\.\s*clear\s*\(": LOC_PYFILE_WORD_WARNING_CHECK_CLEAR,
|
|
124
|
+
r"\[\s*-\s*1\s*\]": LOC_PYFILE_WORD_WARNING_INDEX_MINUS_ONE,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
PYFILE_EXIST_WARNING_CHECK = {
|
|
128
|
+
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]undefined['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'undefined')'.",
|
|
129
|
+
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]Infinity['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'Infinity')'.",
|
|
130
|
+
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]clear['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'clear')'.",
|
|
131
|
+
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]get['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'get')'.",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
JS_VM = "org.transcrypt.__runtime__.js"
|
|
135
|
+
|
|
136
|
+
BUILTIN_TRANS = ["engine.js", "stage.js"] # 记录buildin中会被transcrypt的文件
|
|
137
|
+
OTHER_IGNORE_WITH = "./builtin"
|
|
138
|
+
|
|
139
|
+
JS_IMPORT_PAT = re.compile(r'from\s+[\'\"]([^\']+)[\'\"]')
|
|
140
|
+
JS_EXPORT_PAT = re.compile(r'export\s+{([^}]+)}')
|
|
141
|
+
PY_IMPORT_PAT = re.compile(r'from\s+(.+)(?=\s+import)\s+import\s+\*')
|
|
142
|
+
INSERT_PAT = re.compile(r'#\s*insert\s+([^\n]*)') # 因为被判定的string为单line,所以不需要考虑多行的情况
|
|
143
|
+
|
|
144
|
+
TRANSCRYPT_ERROR_REPLACE = {
|
|
145
|
+
# 由于transcrypt的问题,导致编译后的js代码中存在一些错误,需要进行替换
|
|
146
|
+
r"new\s+set\s*\(": r"set(",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
ARENA_IMPORTS_GETTER = {
|
|
150
|
+
const.ARENA_GREEN: lambda: f"""
|
|
151
|
+
const ARENA_COLOR_TYPE = "GREEN";
|
|
152
|
+
class GameAreaEffect{{
|
|
153
|
+
constructor(){{
|
|
154
|
+
}}
|
|
155
|
+
}};
|
|
156
|
+
class GameConstructionBoost{{
|
|
157
|
+
constructor(){{
|
|
158
|
+
}}
|
|
159
|
+
}};
|
|
160
|
+
import {{ Portal as GamePortal}} from 'arena/season_{config.season}/{const.ARENA_GREEN}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
|
|
161
|
+
|
|
162
|
+
""",
|
|
163
|
+
const.ARENA_BLUE: lambda: f"""
|
|
164
|
+
const ARENA_COLOR_TYPE = "BLUE";
|
|
165
|
+
const GameScoreCollector = GameStructureSpawn;
|
|
166
|
+
class GameAreaEffect{{
|
|
167
|
+
constructor(){{
|
|
168
|
+
}}
|
|
169
|
+
}};
|
|
170
|
+
class GamePortal{{
|
|
171
|
+
constructor(){{
|
|
172
|
+
}}
|
|
173
|
+
}};
|
|
174
|
+
class GameConstructionBoost{{
|
|
175
|
+
constructor(){{
|
|
176
|
+
}}
|
|
177
|
+
}};
|
|
178
|
+
""",
|
|
179
|
+
const.ARENA_RED: lambda: f"""
|
|
180
|
+
const ARENA_COLOR_TYPE = "RED";
|
|
181
|
+
class GamePortal{{
|
|
182
|
+
constructor(){{
|
|
183
|
+
}}
|
|
184
|
+
}};
|
|
185
|
+
import {{ ConstructionBoost as GameConstructionBoost, AreaEffect as GameAreaEffect }} from 'arena/season_{config.season}/{const.ARENA_RED}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
|
|
186
|
+
import {{ EFFECT_CONSTRUCTION_BOOST, EFFECT_SLOWDOWN }} from 'arena/season_{config.season}/{const.ARENA_RED}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/constants';
|
|
187
|
+
|
|
188
|
+
""",
|
|
189
|
+
const.ARENA_GRAY: lambda: f"""
|
|
190
|
+
const ARENA_COLOR_TYPE = "GRAY";
|
|
191
|
+
class GameAreaEffect{{
|
|
192
|
+
constructor(){{
|
|
193
|
+
}}
|
|
194
|
+
}};
|
|
195
|
+
class GamePortal{{
|
|
196
|
+
constructor(){{
|
|
197
|
+
}}
|
|
198
|
+
}};
|
|
199
|
+
class GameConstructionBoost{{
|
|
200
|
+
constructor(){{
|
|
201
|
+
}}
|
|
202
|
+
}};
|
|
203
|
+
""",
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Compiler_Utils(Compiler_Const):
|
|
208
|
+
last_output = False # 一个小标志位,我只想输出一次此类告警信息
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def auto_read(fpath: str) -> str:
|
|
212
|
+
"""
|
|
213
|
+
读取文件内容,自动应用编码
|
|
214
|
+
:param fpath: str 文件路径
|
|
215
|
+
"""
|
|
216
|
+
if not os.path.exists(fpath):
|
|
217
|
+
if not Compiler_Utils.last_output:
|
|
218
|
+
Compiler_Utils.last_output = True
|
|
219
|
+
print()
|
|
220
|
+
core.warn('Compiler_Utils.auto_read', core.lformat(LOC_FILE_NOT_EXISTS, ["", fpath]), end='', head='\n', ln=config.language)
|
|
221
|
+
return ""
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
with open(fpath, 'r', encoding='utf-8') as f:
|
|
225
|
+
return f.read()
|
|
226
|
+
except UnicodeDecodeError:
|
|
227
|
+
try:
|
|
228
|
+
with open(fpath, 'r', encoding='gbk') as f:
|
|
229
|
+
return f.read()
|
|
230
|
+
except UnicodeDecodeError:
|
|
231
|
+
# 如果使用检测到的编码读取失败,尝试使用chardet检测编码
|
|
232
|
+
with open(fpath, 'rb') as f: # 以二进制模式打开文件
|
|
233
|
+
raw_data = f.read() # 读取文件的原始数据
|
|
234
|
+
result = chardet.detect(raw_data) # 使用chardet检测编码
|
|
235
|
+
encoding = result['encoding'] # 获取检测到的编码
|
|
236
|
+
with open(fpath, 'r', encoding=encoding) as f: # 使用检测到的编码打开文件
|
|
237
|
+
return f.read()
|
|
238
|
+
|
|
239
|
+
def copy_to(self) -> list:
|
|
240
|
+
"""
|
|
241
|
+
复制src到build目录 | copy all files in src to build
|
|
242
|
+
* 注意到src下的文件应当全部为py文件 | all files in src should be py files
|
|
243
|
+
"""
|
|
244
|
+
# copy to build dir
|
|
245
|
+
# print(Fore.YELLOW + '>>> ' + Fore.RESET + ' copying to build dir: %s ...' % self.build_dir, end='')
|
|
246
|
+
# LOC_COPYING_TO_BUILD_DIR
|
|
247
|
+
|
|
248
|
+
core.lprint(WAIT, core.lformat(LOC_COPYING_TO_BUILD_DIR, [self.build_dir]), end="", ln=config.language)
|
|
249
|
+
|
|
250
|
+
if os.path.exists(self.build_dir):
|
|
251
|
+
shutil.rmtree(self.build_dir)
|
|
252
|
+
shutil.copytree(self.src_dir, self.build_dir)
|
|
253
|
+
shutil.copytree(self.src_dir, os.path.join(self.build_dir, "src"))
|
|
254
|
+
srcs = [] # src下所有python文件的路径 | paths of all python files under src
|
|
255
|
+
for root, dirs, files in os.walk(self.build_dir):
|
|
256
|
+
for file in files:
|
|
257
|
+
if file.endswith('.py'):
|
|
258
|
+
srcs.append(os.path.join(root, file))
|
|
259
|
+
# add libs
|
|
260
|
+
for lib in self.PROTO_DEFINES_DIRS:
|
|
261
|
+
shutil.copytree(lib, os.path.join(self.build_dir, lib))
|
|
262
|
+
|
|
263
|
+
# overwrite last to [Done]
|
|
264
|
+
# print(Fore.GREEN + '\r[1/6][Done]' + Fore.RESET + ' copying to build dir: %s' % self.build_dir)
|
|
265
|
+
|
|
266
|
+
core.lprint(GREEN.format('[1/6]'), LOC_DONE, " ", core.lformat(LOC_COPYING_TO_BUILD_DIR_FINISH, [self.build_dir]), sep="", head="\r", ln=config.language)
|
|
267
|
+
return srcs
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def potential_check(fpath: str, fname: str) -> bool:
|
|
271
|
+
"""
|
|
272
|
+
检查某个py文件内是否有潜在问题 | check if there are potential problems in a py file
|
|
273
|
+
|
|
274
|
+
如果有的话,输出[Warn][{file_name}/{line_io}]{detail} | if there are, output [Warn][{file_name}/{line_io}]{detail}
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
bool: 是否有警告
|
|
278
|
+
"""
|
|
279
|
+
# 文件路径检查
|
|
280
|
+
# if fpath.endswith('__init__.py') and fpath.find("builtin") == -1:
|
|
281
|
+
# core.error("potential_check", LOC_NOT_SUPPORT_PYFILE_INIT, ln=config.language, ecode=-1, head='\n')
|
|
282
|
+
if fname in Compiler.PYFILE_IGNORE_CHECK_FNAMES:
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
# # 文件内容检查
|
|
286
|
+
content = Compiler.auto_read(fpath)
|
|
287
|
+
warn_flag = False
|
|
288
|
+
# # 内容关键字检查
|
|
289
|
+
for pat, detail in Compiler.PYFILE_WORD_WARNING_CHECK.items():
|
|
290
|
+
for i, line in enumerate(content.split('\n')):
|
|
291
|
+
m = re.search(pat, line)
|
|
292
|
+
if m:
|
|
293
|
+
# 检查m前面同一行内是否有#,如果有则忽略
|
|
294
|
+
comment = re.search(r'#', line[:m.start()])
|
|
295
|
+
|
|
296
|
+
# 检查m后面同一行内是否有#\s*ignore;,如果有则忽略
|
|
297
|
+
ignore = re.search(r'#\s*>\s*ignore', line[m.end():])
|
|
298
|
+
|
|
299
|
+
if not comment and not ignore:
|
|
300
|
+
warn_flag = True
|
|
301
|
+
core.warn('Compiler.potential_check', f'[{os.path.basename(os.path.dirname(fpath))}/{fname} line:{i + 1}]:', detail, end='', head='\n', ln=config.language)
|
|
302
|
+
return warn_flag
|
|
303
|
+
|
|
304
|
+
@staticmethod
|
|
305
|
+
def preprocess_if_block(source_code: str, variables: dict[str, object]) -> str:
|
|
306
|
+
"""
|
|
307
|
+
预处理if块,将 # > if, # > elif, # > else, # > endif 替换为实际的程序内容 |
|
|
308
|
+
pre-process if blocks by replacing # > if, # > elif, # > else, # > endif with actual code.
|
|
309
|
+
"""
|
|
310
|
+
lines = source_code.split('\n') # 按行分割源代码 | split source code into lines
|
|
311
|
+
stack = [] # 初始化一个栈,用于跟踪if条件 | initialize a stack to track if conditions
|
|
312
|
+
result = [] # 初始化一个列表,用于存储处理后的代码行 | initialize a list to store processed code lines
|
|
313
|
+
|
|
314
|
+
for i, line in enumerate(lines): # 遍历源代码的每一行 | iterate over each line of source code
|
|
315
|
+
striped = line.strip() # 去掉行首尾的空格和换行符 | strip leading and trailing whitespace
|
|
316
|
+
# 使用正则表达式匹配不同的条件语句 | use regex to match different conditional statements
|
|
317
|
+
if_match = re.match(r'#\s*>\s*if\s+([^:.]*)$', striped) # 匹配 '# > if' 语句 | match '# > if' statement
|
|
318
|
+
elif_match = re.match(r'#\s*>\s*elif\s+([^:.]*)$', striped) # 匹配 '# > elif' 语句 | match '# > elif' statement
|
|
319
|
+
else_match = re.match(r'#\s*>\s*else$', striped) # 匹配 '# > else' 语句 | match '# > else' statement
|
|
320
|
+
endif_match = re.match(r'#\s*>\s*endif$', striped) # 匹配 '# > endif' 语句 | match '# > endif' statement
|
|
321
|
+
|
|
322
|
+
if if_match: # 如果当前行是 '# > if' 语句 | if it's a '# > if' statement
|
|
323
|
+
condition = if_match.group(1) # 提取条件表达式 | extract the condition expression
|
|
324
|
+
stack.append(eval(condition, variables)) # 评估条件表达式并将其结果压入栈中 | evaluate condition and push result onto stack
|
|
325
|
+
elif elif_match and stack: # 如果当前行是 '# > elif' 语句,并且栈不为空 | if it's a '# > elif' and stack isn't empty
|
|
326
|
+
condition = elif_match.group(1) # 提取条件表达式 | extract the condition expression
|
|
327
|
+
stack[-1] = eval(condition, variables) # 评估条件表达式并更新栈顶 | evaluate condition and update the top of the stack
|
|
328
|
+
elif else_match and stack: # 如果当前行是 '# > else' 语句,并且栈不为空 | if it's a '# > else' and stack isn't empty
|
|
329
|
+
stack[-1] = not stack[-1] # 将栈顶元素取反 | negate the top of the stack
|
|
330
|
+
elif endif_match: # 如果当前行是 '# > endif' 语句 | if it's a '# > endif' statement
|
|
331
|
+
stack.pop() # 弹出栈顶元素 | pop the top of the stack
|
|
332
|
+
else: # 如果当前行不是条件语句 | if it's not a conditional statement
|
|
333
|
+
if not stack or all(stack): # 如果栈为空,或者栈中所有元素均为真 | if stack is empty or all elements are True
|
|
334
|
+
result.append(line) # 将当前行加入结果列表中 | add the current line to the result
|
|
335
|
+
|
|
336
|
+
return '\n'.join(result) # 将处理后的所有代码行连接成一个字符串,并返回最终结果 | join all processed lines into a string and return
|
|
337
|
+
|
|
338
|
+
def expand_folder_imports(self, fpath: str, project_path: str = None):
|
|
339
|
+
"""
|
|
340
|
+
扩展文件夹导入语句:将 `from folder import *` 替换为 `from folder.module import *`
|
|
341
|
+
仅在文件夹没有 __init__.py 时执行此操作
|
|
342
|
+
|
|
343
|
+
:param fpath: 要处理的文件路径
|
|
344
|
+
:param project_path: 项目根路径,用于解析相对导入,默认为 None(使用文件所在目录)
|
|
345
|
+
"""
|
|
346
|
+
if not os.path.exists(fpath):
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
content = self.auto_read(fpath)
|
|
350
|
+
lines = content.split('\n')
|
|
351
|
+
new_lines = []
|
|
352
|
+
changed = False
|
|
353
|
+
|
|
354
|
+
for line in lines:
|
|
355
|
+
m = self.PY_IMPORT_PAT.match(line)
|
|
356
|
+
if not m:
|
|
357
|
+
new_lines.append(line)
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
original_target = m.group(1)
|
|
361
|
+
target = original_target
|
|
362
|
+
target_path = project_path or os.path.dirname(fpath)
|
|
363
|
+
|
|
364
|
+
# 处理相对路径(向前定位)
|
|
365
|
+
if target.startswith('.'):
|
|
366
|
+
target_path = os.path.dirname(fpath)
|
|
367
|
+
count = 0
|
|
368
|
+
for c in target:
|
|
369
|
+
if c == '.':
|
|
370
|
+
count += 1
|
|
371
|
+
else:
|
|
372
|
+
break
|
|
373
|
+
|
|
374
|
+
# 向上移动目录
|
|
375
|
+
if count > 1:
|
|
376
|
+
for _ in range(count - 1):
|
|
377
|
+
target_path = os.path.dirname(target_path)
|
|
378
|
+
|
|
379
|
+
# 移除开头的点
|
|
380
|
+
target = target[count:]
|
|
381
|
+
|
|
382
|
+
# 如果 target 为空,跳过
|
|
383
|
+
if not target:
|
|
384
|
+
new_lines.append(line)
|
|
385
|
+
continue
|
|
386
|
+
|
|
387
|
+
# 向后定位,构建完整路径
|
|
388
|
+
temp_target = target
|
|
389
|
+
while (_idx := temp_target.find('.')) != -1:
|
|
390
|
+
part = temp_target[:_idx]
|
|
391
|
+
target_path = os.path.join(target_path, part)
|
|
392
|
+
temp_target = temp_target[_idx + 1:]
|
|
393
|
+
|
|
394
|
+
# 最终的文件夹路径
|
|
395
|
+
final_dir_path = os.path.join(target_path, temp_target) if temp_target else target_path
|
|
396
|
+
|
|
397
|
+
# 检查是否是文件夹且没有 __init__.py
|
|
398
|
+
if os.path.isdir(final_dir_path):
|
|
399
|
+
init_path = os.path.join(final_dir_path, '__init__.py')
|
|
400
|
+
if not os.path.exists(init_path):
|
|
401
|
+
# 找到所有 .py 文件(排除 __init__.py)| 如果包含子目录,产生一个警告
|
|
402
|
+
# try:
|
|
403
|
+
# py_files = [f for f in os.listdir(final_dir_path) if f.endswith('.py') and f != '__init__.py']
|
|
404
|
+
# except (FileNotFoundError, PermissionError):
|
|
405
|
+
py_files = []
|
|
406
|
+
for item in os.listdir(final_dir_path):
|
|
407
|
+
_path = os.path.join(final_dir_path, item)
|
|
408
|
+
if os.path.isfile(_path) and item.endswith('.py') and item != '__init__.py':
|
|
409
|
+
py_files.append(item)
|
|
410
|
+
elif os.path.isdir(_path):
|
|
411
|
+
rel = os.path.relpath(final_dir_path, project_path)
|
|
412
|
+
core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language )
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
# 为每个 .py 文件生成导入语句
|
|
416
|
+
if py_files:
|
|
417
|
+
for py_file in py_files:
|
|
418
|
+
module_name = py_file[:-3]
|
|
419
|
+
new_import = f"from {original_target}.{module_name} import *"
|
|
420
|
+
new_lines.append(new_import)
|
|
421
|
+
changed = True
|
|
422
|
+
continue
|
|
423
|
+
|
|
424
|
+
# 保留原行
|
|
425
|
+
new_lines.append(line)
|
|
426
|
+
|
|
427
|
+
# 如果文件有修改,写回
|
|
428
|
+
if changed:
|
|
429
|
+
new_content = '\n'.join(new_lines)
|
|
430
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
431
|
+
f.write(new_content)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
|
|
435
|
+
r"""
|
|
436
|
+
查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
|
|
437
|
+
PY_IMPORT_PAT: re.compile(r'\s+from\s+(.+)(?=\s+import)\s+import\s+\*')
|
|
438
|
+
:param fpath: str 目标文件路径 | target file path
|
|
439
|
+
:param search_dirs: list[str] 搜索目录 | search directories
|
|
440
|
+
:param project_path=None: str python项目中的概念,指根文件所在的目录。如果不指定,默认使用第一次调用时给定的fpath,并且稍后的递归会全部使用此路径 |
|
|
441
|
+
concept in python-project, refers to the directory where the root file is located. If not specified, the fpath given at the first call is used by default, and all subsequent recursions will use this path
|
|
442
|
+
:param records=None: dict[str, None] 记录已经查找过的文件路径,避免重复查找 | record the file paths that have been searched to avoid duplicate searches
|
|
443
|
+
"""
|
|
444
|
+
if records is None:
|
|
445
|
+
records = {}
|
|
446
|
+
if not os.path.exists(fpath):
|
|
447
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_FILE_NOT_EXISTS, ["py", fpath]), head='\n', ln=config.language)
|
|
448
|
+
imps = []
|
|
449
|
+
content = self.auto_read(fpath)
|
|
450
|
+
project_path = project_path or os.path.dirname(fpath)
|
|
451
|
+
for no, line in enumerate(content.split('\n')):
|
|
452
|
+
m = self.PY_IMPORT_PAT.match(line)
|
|
453
|
+
if m:
|
|
454
|
+
target = m.group(1)
|
|
455
|
+
target_path = project_path
|
|
456
|
+
|
|
457
|
+
## 向前定位 | locate forward
|
|
458
|
+
if target.startswith('.'):
|
|
459
|
+
target_path = os.path.dirname(fpath) # 因为使用了相对路径,所以需要先定位到当前文件所在的目录 |
|
|
460
|
+
# because relative path is used, need to locate the directory where the current file is located first
|
|
461
|
+
count = 0
|
|
462
|
+
for c in target:
|
|
463
|
+
if c == '.':
|
|
464
|
+
count += 1
|
|
465
|
+
else:
|
|
466
|
+
break
|
|
467
|
+
if count > 1:
|
|
468
|
+
for _ in range(count - 1):
|
|
469
|
+
target_path = os.path.dirname(target_path)
|
|
470
|
+
|
|
471
|
+
## 向后定位 | locate backward
|
|
472
|
+
while (_idx := target.find('.')) != -1:
|
|
473
|
+
first_name = target[:_idx]
|
|
474
|
+
target_path = os.path.join(target_path, first_name)
|
|
475
|
+
target = target[_idx + 1:]
|
|
476
|
+
|
|
477
|
+
## 检查是否存在 | check if exists
|
|
478
|
+
this_path = os.path.join(target_path, target)
|
|
479
|
+
if os.path.isdir(this_path):
|
|
480
|
+
this_path = os.path.join(this_path, '__init__.py')
|
|
481
|
+
else:
|
|
482
|
+
this_path += '.py'
|
|
483
|
+
|
|
484
|
+
if not os.path.exists(this_path):
|
|
485
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_CHAIN_FILE_NOT_EXISTS, [fpath, no + 1, this_path]), head='\n', ln=config.language)
|
|
486
|
+
if this_path not in records:
|
|
487
|
+
records[this_path] = None
|
|
488
|
+
tmp = self.find_chain_import(this_path, search_dirs, project_path, records) + [this_path]
|
|
489
|
+
imps.extend(tmp)
|
|
490
|
+
|
|
491
|
+
return imps
|
|
492
|
+
|
|
493
|
+
def find_chain_import2(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
|
|
494
|
+
r"""
|
|
495
|
+
查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
|
|
496
|
+
PY_IMPORT_PAT: re.compile(r'\s+from\s+(.+)(?=\s+import)\s+import\s+\*')
|
|
497
|
+
:param fpath: str 目标文件路径 | target file path
|
|
498
|
+
:param search_dirs: list[str] 搜索目录 | search directories
|
|
499
|
+
:param project_path=None: str python项目中的概念,指根文件所在的目录。如果不指定,默认使用第一次调用时给定的fpath,并且稍后的递归会全部使用此路径 |
|
|
500
|
+
concept in python-project, refers to the directory where the root file is located. If not specified, the fpath given at the first call is used by default, and all subsequent recursions will use this path
|
|
501
|
+
:param records=None: dict[str, None] 记录已经查找过的文件路径,避免重复查找 | record the file paths that have been searched to avoid duplicate searches
|
|
502
|
+
"""
|
|
503
|
+
if records is None:
|
|
504
|
+
records = {}
|
|
505
|
+
if not os.path.exists(fpath):
|
|
506
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_FILE_NOT_EXISTS, [fpath]), head='\n', ln=config.language)
|
|
507
|
+
imps = []
|
|
508
|
+
content = self.auto_read(fpath)
|
|
509
|
+
project_path = project_path or os.path.dirname(fpath)
|
|
510
|
+
|
|
511
|
+
# 添加根目录和 src 目录到 search_dirs
|
|
512
|
+
root_dir = os.path.dirname(project_path) # 根目录
|
|
513
|
+
src_dir = os.path.join(root_dir, 'src') # src 目录
|
|
514
|
+
if root_dir not in search_dirs:
|
|
515
|
+
search_dirs = [root_dir] + search_dirs
|
|
516
|
+
if src_dir not in search_dirs:
|
|
517
|
+
search_dirs = [src_dir] + search_dirs
|
|
518
|
+
|
|
519
|
+
for no, line in enumerate(content.split('\n')):
|
|
520
|
+
m = self.PY_IMPORT_PAT.match(line)
|
|
521
|
+
if m:
|
|
522
|
+
target = m.group(1)
|
|
523
|
+
target_path = project_path
|
|
524
|
+
|
|
525
|
+
## 向前定位 | locate forward
|
|
526
|
+
if target.startswith('.'):
|
|
527
|
+
target_path = os.path.dirname(fpath) # 因为使用了相对路径,所以需要先定位到当前文件所在的目录 |
|
|
528
|
+
# because relative path is used, need to locate the directory where the current file is located first
|
|
529
|
+
count = 0
|
|
530
|
+
for c in target:
|
|
531
|
+
if c == '.':
|
|
532
|
+
count += 1
|
|
533
|
+
else:
|
|
534
|
+
break
|
|
535
|
+
if count > 1:
|
|
536
|
+
for _ in range(count - 1):
|
|
537
|
+
target_path = os.path.dirname(target_path)
|
|
538
|
+
|
|
539
|
+
## 向后定位 | locate backward
|
|
540
|
+
while (_idx := target.find('.')) != -1:
|
|
541
|
+
first_name = target[:_idx]
|
|
542
|
+
target_path = os.path.join(target_path, first_name)
|
|
543
|
+
target = target[_idx + 1:]
|
|
544
|
+
|
|
545
|
+
## 检查是否存在 | check if exists
|
|
546
|
+
this_path = os.path.join(target_path, target)
|
|
547
|
+
if os.path.isdir(this_path):
|
|
548
|
+
this_path = os.path.join(this_path, '__init__.py')
|
|
549
|
+
else:
|
|
550
|
+
this_path += '.py'
|
|
551
|
+
|
|
552
|
+
if not os.path.exists(this_path):
|
|
553
|
+
# 如果当前路径不存在,尝试在 search_dirs 中查找
|
|
554
|
+
for search_dir in search_dirs:
|
|
555
|
+
search_path = os.path.join(search_dir, target.replace('.', os.sep)) + ('.py' if not os.path.isdir(this_path) else os.sep + '__init__.py')
|
|
556
|
+
if os.path.exists(search_path):
|
|
557
|
+
this_path = search_path
|
|
558
|
+
break
|
|
559
|
+
else:
|
|
560
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_CHAIN_FILE_NOT_EXISTS, [fpath, no + 1, this_path]), head='\n', ln=config.language)
|
|
561
|
+
if this_path not in records:
|
|
562
|
+
records[this_path] = None
|
|
563
|
+
tmp = self.find_chain_import(this_path, search_dirs, project_path, records) + [this_path]
|
|
564
|
+
imps.extend(tmp)
|
|
565
|
+
|
|
566
|
+
return imps
|
|
567
|
+
|
|
568
|
+
@staticmethod
|
|
569
|
+
def relist_pyimports_to_jsimports(base_dir:str, pyimps:list[str]) -> list[str]:
|
|
570
|
+
"""
|
|
571
|
+
将python的imports路径列表转换为js的imports路径列表 | convert a list of python imports paths to a list of js imports paths
|
|
572
|
+
"""
|
|
573
|
+
jsimps = []
|
|
574
|
+
for pyimp in pyimps:
|
|
575
|
+
rel_path_nodes:list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
|
|
576
|
+
if rel_path_nodes[-1] == '__init__.py':
|
|
577
|
+
rel_path_nodes.pop()
|
|
578
|
+
else:
|
|
579
|
+
rel_path_nodes[-1] = rel_path_nodes[-1][:-3]
|
|
580
|
+
jsimps.append('./' + '.'.join(rel_path_nodes) + '.js')
|
|
581
|
+
return jsimps
|
|
582
|
+
|
|
583
|
+
# ---------- 自定义函数 ---------- #
|
|
584
|
+
|
|
585
|
+
@staticmethod
|
|
586
|
+
def stage_recursive_replace(content: str) -> str:
|
|
587
|
+
"""
|
|
588
|
+
移除 '@recursive' 装饰器行,并在文末添加对应的 _recursiveLogin 调用。
|
|
589
|
+
|
|
590
|
+
对于类方法: _recursiveLogin("ClassName", "method_name")
|
|
591
|
+
对于普通函数: _recursiveLogin("", "function_name")
|
|
592
|
+
"""
|
|
593
|
+
calls_to_add = []
|
|
594
|
+
deletions = []
|
|
595
|
+
|
|
596
|
+
# 1. 收集所有类定义的位置和缩进
|
|
597
|
+
class_pattern = re.compile(r'^(\s*)class\s+(\w+)', re.MULTILINE)
|
|
598
|
+
classes = [(m.start(), len(m.group(1)), m.group(2))
|
|
599
|
+
for m in class_pattern.finditer(content)]
|
|
600
|
+
|
|
601
|
+
# 2. 查找所有 @recursive 装饰器
|
|
602
|
+
decorator_pattern = re.compile(r'^\s*@\s*recursive\s*$\n?', re.MULTILINE)
|
|
603
|
+
|
|
604
|
+
for dec_match in decorator_pattern.finditer(content):
|
|
605
|
+
dec_end = dec_match.end()
|
|
606
|
+
|
|
607
|
+
# 查找接下来的函数定义(跳过可能的空行)
|
|
608
|
+
after_decorator = content[dec_end:]
|
|
609
|
+
func_match = re.search(r'^(\s*)def\s+([^\s\(]+)', after_decorator, re.MULTILINE)
|
|
610
|
+
|
|
611
|
+
if not func_match:
|
|
612
|
+
continue
|
|
613
|
+
|
|
614
|
+
func_indent_len = len(func_match.group(1))
|
|
615
|
+
func_name = func_match.group(2)
|
|
616
|
+
|
|
617
|
+
# 3. 确定类名:查找装饰器前最近的、缩进小于函数缩进的类
|
|
618
|
+
class_name = ""
|
|
619
|
+
for cls_pos, cls_indent_len, cls_name in reversed(classes):
|
|
620
|
+
if cls_pos < dec_match.start() and func_indent_len > cls_indent_len:
|
|
621
|
+
class_name = cls_name
|
|
622
|
+
break
|
|
623
|
+
|
|
624
|
+
# 4. 记录删除位置和调用信息
|
|
625
|
+
deletions.append((dec_match.start(), dec_end))
|
|
626
|
+
calls_to_add.append(f'_recursiveLogin("{class_name}", "{func_name}")')
|
|
627
|
+
|
|
628
|
+
# 5. 应用删除(倒序避免位置偏移)
|
|
629
|
+
if not deletions:
|
|
630
|
+
return content
|
|
631
|
+
|
|
632
|
+
result = content
|
|
633
|
+
for start, end in sorted(deletions, key=lambda x: x[0], reverse=True):
|
|
634
|
+
result = result[:start] + result[end:]
|
|
635
|
+
|
|
636
|
+
# 6. 在文末添加调用
|
|
637
|
+
if calls_to_add:
|
|
638
|
+
result = '\n'.join(calls_to_add) + '\n' + result
|
|
639
|
+
|
|
640
|
+
return result
|
|
641
|
+
|
|
642
|
+
@staticmethod
|
|
643
|
+
def process_mate_code(code):
|
|
644
|
+
# 用于存储匹配到的信息
|
|
645
|
+
mate_assignments = []
|
|
646
|
+
# 匹配变量赋值为Mate()的正则表达式,允许变量定义中包含或不包含类型注解
|
|
647
|
+
assign_pattern = re.compile(r'(\w+)\s*(?:\:\s*\w*)?\s*=\s*Mate\s*\(')
|
|
648
|
+
# 匹配类定义的正则表达式
|
|
649
|
+
class_pattern = re.compile(r'class\s+(\w+)')
|
|
650
|
+
# 用于记录当前所在的类名
|
|
651
|
+
current_class = None
|
|
652
|
+
# 将代码按行分割
|
|
653
|
+
lines = code.split('\n')
|
|
654
|
+
# 遍历每一行
|
|
655
|
+
for i, line in enumerate(lines):
|
|
656
|
+
# 匹配类定义
|
|
657
|
+
class_match = class_pattern.match(line)
|
|
658
|
+
if class_match:
|
|
659
|
+
current_class = class_match.group(1)
|
|
660
|
+
# 匹配变量赋值为Mate()
|
|
661
|
+
assign_match = assign_pattern.search(line)
|
|
662
|
+
if assign_match:
|
|
663
|
+
# 检查group(1)前面同一行内是否有#,如果有则忽略
|
|
664
|
+
comment = re.search(r'#', line[:assign_match.start()])
|
|
665
|
+
if comment:
|
|
666
|
+
continue
|
|
667
|
+
variable_name = assign_match.group(1)
|
|
668
|
+
# 存储匹配到的信息
|
|
669
|
+
mate_assignments += [(variable_name, current_class)]
|
|
670
|
+
|
|
671
|
+
output_strings = []
|
|
672
|
+
for variable_name, class_name in mate_assignments:
|
|
673
|
+
output_string = f"# > insert Object.defineProperty ({class_name}, '{variable_name}', property.call ({class_name}, {class_name}.{variable_name}._MateGet_, {class_name}.{variable_name}._MateSet_));"
|
|
674
|
+
output_strings.append(output_string)
|
|
675
|
+
|
|
676
|
+
return code + '\n'.join(output_strings)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
@staticmethod
|
|
680
|
+
def remove_long_docstring(content:str) -> str:
|
|
681
|
+
"""
|
|
682
|
+
移除长注释 | remove long docstring
|
|
683
|
+
"""
|
|
684
|
+
code = re.sub(r'"""[^"]*"""', '', content)
|
|
685
|
+
code = re.sub(r"'''[^']*'''", '', code)
|
|
686
|
+
return code
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class CompilerBase(Compiler_Utils):
|
|
690
|
+
|
|
691
|
+
def __init__(self):
|
|
692
|
+
src_dir = "src"
|
|
693
|
+
build_dir = "build"
|
|
694
|
+
# check
|
|
695
|
+
if not os.path.exists(src_dir):
|
|
696
|
+
core.error('Compiler.__init__', core.lformat(LOC_FILE_NOT_EXISTS, ['src', src_dir]), head='\n', ln=config.language)
|
|
697
|
+
|
|
698
|
+
src_dir = os.path.abspath(src_dir)
|
|
699
|
+
build_dir = os.path.abspath(build_dir)
|
|
700
|
+
base_dir = os.path.dirname(src_dir)
|
|
701
|
+
lib_dir = os.path.join(base_dir, 'library')
|
|
702
|
+
built_dir = os.path.join(base_dir, 'builtin')
|
|
703
|
+
|
|
704
|
+
# 在builtin文件下搜索需要跳过的文件,计入到PYFILE_IGNORE_CHECK_FNAMES中
|
|
705
|
+
for fname in os.listdir(os.path.join(base_dir, "builtin")):
|
|
706
|
+
if fname.endswith('.py') and fname not in ['const.py', 'proto.py', 'utils.py']:
|
|
707
|
+
self.PYFILE_IGNORE_CHECK_FNAMES.append(f'builtin/{fname}')
|
|
708
|
+
|
|
709
|
+
self.src_dir = os.path.abspath(src_dir)
|
|
710
|
+
self.lib_dir = os.path.abspath(lib_dir)
|
|
711
|
+
self.build_dir = os.path.abspath(build_dir)
|
|
712
|
+
self.built_dir = os.path.abspath(built_dir)
|
|
713
|
+
self.target_dir = os.path.join(self.build_dir, '__target__')
|
|
714
|
+
self.build_name = os.path.basename(self.build_dir)
|
|
715
|
+
|
|
716
|
+
@property
|
|
717
|
+
def builtin_py(self) -> str:
|
|
718
|
+
"""
|
|
719
|
+
返回builtin目录下的__init__.py的路径 | return the path of __init__.py in builtin
|
|
720
|
+
"""
|
|
721
|
+
return os.path.join(self.built_dir, '__init__.py')
|
|
722
|
+
|
|
723
|
+
@property
|
|
724
|
+
def target_py(self) -> str:
|
|
725
|
+
"""
|
|
726
|
+
返回build下的main.py的路径 | return the path of main.py in build
|
|
727
|
+
"""
|
|
728
|
+
return os.path.join(self.build_dir, 'main.py')
|
|
729
|
+
|
|
730
|
+
@property
|
|
731
|
+
def target_js(self):
|
|
732
|
+
"""
|
|
733
|
+
返回build下的main.js的路径 | return the path of main.js in build
|
|
734
|
+
"""
|
|
735
|
+
return os.path.join(self.target_dir, 'main.js')
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class Compiler(CompilerBase):
|
|
739
|
+
def pre_compile(self):
|
|
740
|
+
"""
|
|
741
|
+
预编译 | Precompile
|
|
742
|
+
"""
|
|
743
|
+
src_paths: list[str] = self.copy_to() # 复制src到build目录 | copy all files in src to build
|
|
744
|
+
# 获取src目录下的所有.py文件的路径 | get the paths of all .py files under src
|
|
745
|
+
|
|
746
|
+
core.lprint(WAIT, LOC_PREPROCESSING, end="", ln=config.language)
|
|
747
|
+
py_fpath, py_names, warn_flag = [], [], False
|
|
748
|
+
for root, dirs, files in os.walk(self.build_dir):
|
|
749
|
+
for file in files:
|
|
750
|
+
if file.endswith('.py'):
|
|
751
|
+
fpath: str = str(os.path.join(root, file))
|
|
752
|
+
|
|
753
|
+
# 将PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "")插入到文件开头
|
|
754
|
+
content = self.auto_read(fpath)
|
|
755
|
+
content = self.PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "") + content
|
|
756
|
+
# content = self.remove_long_docstring(content) # 移除长注释 | remove long docstring
|
|
757
|
+
|
|
758
|
+
with open(fpath, 'w', encoding='utf-8') as f: # 注意,这里修改的是build目录下的文件,不是源文件 | Note that the file under the build directory is modified here, not the source file
|
|
759
|
+
f.write(content)
|
|
760
|
+
|
|
761
|
+
# 得到src目录后面的内容
|
|
762
|
+
rel_name = os.path.relpath(fpath, self.build_dir).replace('\\', '/')
|
|
763
|
+
py_names.append(rel_name.replace('/', '.'))
|
|
764
|
+
py_fpath.append(fpath)
|
|
765
|
+
warn_flag |= self.potential_check(fpath, rel_name)
|
|
766
|
+
if warn_flag:
|
|
767
|
+
print() # 换行
|
|
768
|
+
|
|
769
|
+
_usubs_ = [] # update_subclass
|
|
770
|
+
_pre_import_, _pre_imp_detail_ = [], {} # > import
|
|
771
|
+
_imports = [] # chain import
|
|
772
|
+
_pre_sort_ = {} # > sort
|
|
773
|
+
_pre_define_ = {} # > define
|
|
774
|
+
_js_replace_, _insert_id_ = {}, 0 # > insert
|
|
775
|
+
|
|
776
|
+
# -------------------------------- ONLY IMPORT * -------------------------------- #
|
|
777
|
+
# 只允许from xxx import *的情况 | only allow from xxx import *
|
|
778
|
+
for i, fpath in enumerate(py_fpath):
|
|
779
|
+
content = self.auto_read(fpath)
|
|
780
|
+
for line in content.split('\n'):
|
|
781
|
+
# 1. 检查 import xxx的情况 | check import xxx
|
|
782
|
+
m = re.match(r'\s*import\s+([^\s]+)', line)
|
|
783
|
+
if m:
|
|
784
|
+
core.error('Compiler.pre_compile', core.lformat(LOC_IMPORT_STAR_ERROR, [m.group(1), m.group(1)]), head='\n', ln=config.language)
|
|
785
|
+
# 2. 检查 from xxx import yyys的情况(yyys不能是*) | check from xxx import yyys(yyys can't be *)
|
|
786
|
+
m = re.match(r'\n\s*from\s+([^\s]+)\s+import\s+([^\s]+)', line)
|
|
787
|
+
if m and (not m.group(2) or m.group(2)[0] != '*'):
|
|
788
|
+
core.error('Compiler.pre_compile', core.lformat(LOC_IMPORT_STAR2_ERROR, [m.group(1), m.group(2), m.group(1)]), head='\n', ln=config.language)
|
|
789
|
+
|
|
790
|
+
self.expand_folder_imports(fpath, self.build_dir)
|
|
791
|
+
|
|
792
|
+
# -------------------------------- EXPAND IMPORT * -------------------------------- #
|
|
793
|
+
_imports = self.find_chain_import(self.target_py, [os.path.dirname(self.src_dir), self.src_dir])
|
|
794
|
+
_js_imports = self.relist_pyimports_to_jsimports(self.build_dir, _imports)
|
|
795
|
+
|
|
796
|
+
# ----------------------------------- REMOVE ----------------------------------- #
|
|
797
|
+
# 移除所有# > remove所在行的内容
|
|
798
|
+
# | remove all # > remove in .py files
|
|
799
|
+
for fpath in py_fpath:
|
|
800
|
+
content = self.auto_read(fpath)
|
|
801
|
+
new_content = ""
|
|
802
|
+
for line in content.split('\n'):
|
|
803
|
+
if not re.search(r'#\s*>\s*remove', line):
|
|
804
|
+
new_content += line + '\n'
|
|
805
|
+
|
|
806
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
807
|
+
f.write(new_content)
|
|
808
|
+
|
|
809
|
+
# ------------------------------------ SORT ------------------------------------ #
|
|
810
|
+
# 获取所有.py文件中的所有# > sort的内容,并记录下来(不存在则默认为65535)
|
|
811
|
+
# | get all # > sort in .py files, and record them (default 65535 if not exists)
|
|
812
|
+
for i, fpath in enumerate(py_fpath):
|
|
813
|
+
fname = py_names[i]
|
|
814
|
+
if fname.endswith('__init__.py'):
|
|
815
|
+
fname = fname[:-12] + '.js'
|
|
816
|
+
else:
|
|
817
|
+
fname = fname[:-3] + '.js'
|
|
818
|
+
content = self.auto_read(fpath)
|
|
819
|
+
m = re.search(r'#\s*>\s*sort\s+(\d+)', content)
|
|
820
|
+
if m:
|
|
821
|
+
try:
|
|
822
|
+
sort_num = int(m.group(1))
|
|
823
|
+
except ValueError:
|
|
824
|
+
core.warn('Compiler.pre_compile', core.lformat(LOC_SORT_NUMBER_ERROR, [m.group(1)]), end='', head='\n', ln=config.language)
|
|
825
|
+
sort_num = 65535
|
|
826
|
+
_pre_sort_[fname] = sort_num
|
|
827
|
+
else:
|
|
828
|
+
_pre_sort_[fname] = 65535
|
|
829
|
+
|
|
830
|
+
# ------------------------------------ 自定义:调用process_mate_code ------------------------------------ #
|
|
831
|
+
for fpath in py_fpath:
|
|
832
|
+
content = self.auto_read(fpath)
|
|
833
|
+
content = self.process_mate_code(content) # 调用process_mate_code
|
|
834
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
835
|
+
f.write(content)
|
|
836
|
+
|
|
837
|
+
# ------------------------------------ DEFINE ------------------------------------ #
|
|
838
|
+
# 扫描所有# > define的内容,然后在.py中移除这些行,并记录下来
|
|
839
|
+
# | get all # > define in .py files, and record them
|
|
840
|
+
for fpath in py_fpath:
|
|
841
|
+
content = self.auto_read(fpath)
|
|
842
|
+
new_content = ""
|
|
843
|
+
for line in content.split('\n'):
|
|
844
|
+
# re.compile(r'#\s*define\s+([^\s]+)\s+([^\n]*)')
|
|
845
|
+
m = re.search(r'#\s*>\s*define\s+([^\s]+)\s+([^\n]*)', line)
|
|
846
|
+
if m:
|
|
847
|
+
_pre_define_[m.group(1)] = m.group(2)
|
|
848
|
+
new_content += '\n'
|
|
849
|
+
else:
|
|
850
|
+
new_content += line + '\n'
|
|
851
|
+
|
|
852
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
853
|
+
f.write(new_content)
|
|
854
|
+
|
|
855
|
+
# 按照keys的顺序,先用前面的key对应的内容去依次替换后面的key对应的value中
|
|
856
|
+
# | replace the value of the key with the content of the previous key in order
|
|
857
|
+
_def_keys = list(_pre_define_.keys())
|
|
858
|
+
_keys_len = len(_def_keys)
|
|
859
|
+
for i in range(_keys_len - 1):
|
|
860
|
+
for j in range(i + 1, _keys_len):
|
|
861
|
+
_pre_define_[_def_keys[j]] = _pre_define_[_def_keys[j]].replace(_def_keys[i], _pre_define_[_def_keys[i]])
|
|
862
|
+
|
|
863
|
+
# ------------------------------------ DEFINE:REPLACE ------------------------------------ #
|
|
864
|
+
# 将刚才记录的define替换到.py中(注意优先替换更长的串)(因此先排序)
|
|
865
|
+
# | replace the defined content to .py files (replace the longer string first)
|
|
866
|
+
_def_keys.sort(key=lambda x: len(x), reverse=True)
|
|
867
|
+
for fpath in py_fpath:
|
|
868
|
+
content = self.auto_read(fpath)
|
|
869
|
+
|
|
870
|
+
for key in _def_keys:
|
|
871
|
+
content = re.sub(r'[^_A-Za-z0-9]' + key, self._kfc_wrapper(_pre_define_[key]), content)
|
|
872
|
+
|
|
873
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
874
|
+
f.write(content)
|
|
875
|
+
|
|
876
|
+
# ------------------------------------ IF BLOCK ------------------------------------ #
|
|
877
|
+
# 预处理if块,将 # > if, # > elif, # > else, # > endif 替换为实际的程序内容
|
|
878
|
+
# | preprocess if block, replace # > if, # > elif, # > else, # > endif to actual code
|
|
879
|
+
for fpath in py_fpath:
|
|
880
|
+
content = self.auto_read(fpath)
|
|
881
|
+
|
|
882
|
+
content = self.preprocess_if_block(content, _pre_define_)
|
|
883
|
+
|
|
884
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
885
|
+
f.write(content)
|
|
886
|
+
|
|
887
|
+
# ------------------------------------ INSERT ------------------------------------ #
|
|
888
|
+
# 扫描所有# > insert的内容,然后在.py中将整行替换为# __pragma__("js", __JS_INSERT_{id})
|
|
889
|
+
# | get all # > insert in .py files, and replace the whole line with # __pragma__("js", __JS_INSERT_{id})
|
|
890
|
+
for fpath in py_fpath:
|
|
891
|
+
content = self.auto_read(fpath)
|
|
892
|
+
new_content = ""
|
|
893
|
+
for line in content.split('\n'):
|
|
894
|
+
# re.compile(r'#\s*insert\s*([^\n]*)')
|
|
895
|
+
# '# > insert if(obj && obj.body) for(var p of obj.body) if (p.type == MOVE) return true;'
|
|
896
|
+
m = re.search(r'#\s*>\s*insert\s+([^\n]*)', line)
|
|
897
|
+
if m:
|
|
898
|
+
_sign_index_ = line.find('#') # 必然存在
|
|
899
|
+
_js_key_ = f"__JS_INSERT_{_insert_id_:08d}"
|
|
900
|
+
_js_replace_[_js_key_] = m.group(1)
|
|
901
|
+
|
|
902
|
+
new_content += line[:_sign_index_] + f'# __pragma__("js", "{_js_key_}")\n'
|
|
903
|
+
_insert_id_ += 1
|
|
904
|
+
else:
|
|
905
|
+
new_content += line + '\n'
|
|
906
|
+
|
|
907
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
908
|
+
f.write(new_content)
|
|
909
|
+
|
|
910
|
+
# ------------------------------------ 自定义:调用stage_recursive_replace ------------------------------------ #
|
|
911
|
+
for fpath in py_fpath:
|
|
912
|
+
content = self.auto_read(fpath)
|
|
913
|
+
content = self.stage_recursive_replace(content) # 调用stage_recursive_replace
|
|
914
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
915
|
+
f.write(content)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
core.lprint(GREEN.format('[2/6]'), LOC_DONE, " ", LOC_PREPROCESSING_FINISH, sep="", head="\r", ln=config.language)
|
|
919
|
+
return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
|
|
920
|
+
|
|
921
|
+
def transcrypt_cmd(self):
|
|
922
|
+
# 执行cmd命令: python -m transcrypt -b -m -n -s -e 6 target | execute cmd: python -m transcrypt -b -m -n -s -e 6 target
|
|
923
|
+
# 并获取cmd得到的输出 | and get the output of the cmd
|
|
924
|
+
cmd = 'python -m transcrypt -b -m -n -s -e 6 %s' % self.target_py
|
|
925
|
+
core.lprint(WAIT, core.lformat(LOC_TRANSCRYPTING, [cmd]), end="", ln=config.language)
|
|
926
|
+
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
|
927
|
+
stdout, stderr = p.communicate()
|
|
928
|
+
if 'Error while compiling' in stdout.decode():
|
|
929
|
+
print('\r' + stdout.decode())
|
|
930
|
+
core.error('Compiler.transcrypt_cmd', LOC_TRANSCRYPTING_ERROR, indent=1, head='\n', ln=config.language)
|
|
931
|
+
core.lprint(GREEN.format('[3/6]'), LOC_DONE, " ", LOC_TRANSCRYPTING_FINISH, sep="", head="\r", ln=config.language)
|
|
932
|
+
|
|
933
|
+
@staticmethod
|
|
934
|
+
def _keep_lbracket(matched) -> str:
|
|
935
|
+
"""
|
|
936
|
+
如果第一个字符是{, 则返回'{',否则返回'' | if the first char is {, return '{', else return ''
|
|
937
|
+
:param matched:
|
|
938
|
+
:return:
|
|
939
|
+
"""
|
|
940
|
+
return '{' if matched.group(0)[0] == '{' else ''
|
|
941
|
+
|
|
942
|
+
@staticmethod
|
|
943
|
+
def _keep_first_char(matched: re.Match) -> str:
|
|
944
|
+
"""
|
|
945
|
+
保留第一个字符 | keep the first char
|
|
946
|
+
:param matched: re.match object | re.match对象
|
|
947
|
+
:return:
|
|
948
|
+
"""
|
|
949
|
+
return matched.group(0)[0]
|
|
950
|
+
|
|
951
|
+
@staticmethod
|
|
952
|
+
def _kfc_wrapper(replace: str) -> callable:
|
|
953
|
+
"""
|
|
954
|
+
获取一个保留第一个字符的函数 | get a function to keep the first char
|
|
955
|
+
:param replace: str
|
|
956
|
+
:return: function
|
|
957
|
+
"""
|
|
958
|
+
|
|
959
|
+
def _kfc(matched) -> str:
|
|
960
|
+
return matched.group(0)[0] + replace
|
|
961
|
+
|
|
962
|
+
return _kfc
|
|
963
|
+
|
|
964
|
+
def analyze_rebuild_main_js(self, defs: dict[str, object], modules=None) -> tuple[str, list[str]]:
|
|
965
|
+
"""
|
|
966
|
+
分析main.js中导入的模块名称和先后顺序, 并重新生成main.js | analyze the module names and order imported in main.js, and rebuild main.js
|
|
967
|
+
* 主要移除非SYSTEM_MODULES_IGNORE中的模块导入语句 | mainly remove the module import statements that are not in SYSTEM_MODULES_IGNORE
|
|
968
|
+
:param defs: dict{define: value} 定义的变量 | defined variables
|
|
969
|
+
:return: imports : str, modules (names: str)
|
|
970
|
+
imports是一段用于放在js主体开头的import语句 | imports is a string of import statements to be placed at the beginning of the js body
|
|
971
|
+
modules是一个list,包含了所有的模块名称 | modules is a list containing all module names
|
|
972
|
+
其中的内容可能是这样的: ['./game.utils.js', './game.proto.js', './game.const.js', ...]
|
|
973
|
+
"""
|
|
974
|
+
|
|
975
|
+
# create undefined
|
|
976
|
+
imports = ""
|
|
977
|
+
|
|
978
|
+
# if defs.get('USE_TUTORIAL_FLAG', '0') == '0' and defs.get('USE_ARENA_FLAG', '0') == '0':
|
|
979
|
+
# imports += 'var Flag = undefined;\n'
|
|
980
|
+
# if defs.get('USE_SCORE_COLLECTOR', '0') == '0':
|
|
981
|
+
# imports += 'var ScoreController = undefined;\nvar RESOURCE_SCORE = undefined;\n'
|
|
982
|
+
# imports += '\n'
|
|
983
|
+
|
|
984
|
+
core.lprint(WAIT, LOC_ANALYZING_AND_REBUILDING_MAIN_JS, end="", ln=config.language)
|
|
985
|
+
|
|
986
|
+
content = self.auto_read(self.target_js)
|
|
987
|
+
if modules is None: modules = []
|
|
988
|
+
new_modules, new_content = [],""
|
|
989
|
+
for line in content.split('\n'):
|
|
990
|
+
m = re.search(self.JS_IMPORT_PAT, line)
|
|
991
|
+
if not m:
|
|
992
|
+
new_content += line + '\n'
|
|
993
|
+
continue
|
|
994
|
+
# remove ignore if in SYSTEM_MODULES_IGNORE
|
|
995
|
+
module = m.group(1)
|
|
996
|
+
|
|
997
|
+
_ignore = False
|
|
998
|
+
if module in modules: _ignore = True
|
|
999
|
+
if module in new_modules: _ignore = True
|
|
1000
|
+
if not _ignore: new_modules.append(module)
|
|
1001
|
+
|
|
1002
|
+
# conbine modules
|
|
1003
|
+
modules = new_modules + modules
|
|
1004
|
+
new_modules = []
|
|
1005
|
+
for module in modules:
|
|
1006
|
+
_ignore = False
|
|
1007
|
+
if not _ignore and module.startswith(self.OTHER_IGNORE_WITH): _ignore = True
|
|
1008
|
+
for keeps in self.BUILTIN_TRANS:
|
|
1009
|
+
if module.endswith(keeps): _ignore = False
|
|
1010
|
+
if not _ignore: new_modules.append(module)
|
|
1011
|
+
modules = new_modules
|
|
1012
|
+
|
|
1013
|
+
# save raw main.js
|
|
1014
|
+
with open(self.target_js[:-3] + ".raw.js", 'w', encoding='utf-8') as f:
|
|
1015
|
+
f.write(content)
|
|
1016
|
+
|
|
1017
|
+
# write rebuild main.js
|
|
1018
|
+
with open(self.target_js, 'w', encoding='utf-8') as f:
|
|
1019
|
+
f.write(new_content)
|
|
1020
|
+
|
|
1021
|
+
core.lprint(GREEN.format('[4/6]'), LOC_DONE, " ", LOC_ANALYZING_AND_REBUILDING_MAIN_JS_FINISH, sep="", head="\r", ln=config.language)
|
|
1022
|
+
|
|
1023
|
+
return imports, modules
|
|
1024
|
+
|
|
1025
|
+
@staticmethod
|
|
1026
|
+
def remove_js_import(raw) -> str:
|
|
1027
|
+
"""
|
|
1028
|
+
移除js中的import行
|
|
1029
|
+
:param raw:
|
|
1030
|
+
:return:
|
|
1031
|
+
"""
|
|
1032
|
+
return re.sub(r'import[^\n]*\n', '', raw)
|
|
1033
|
+
|
|
1034
|
+
def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces) -> str:
|
|
1035
|
+
"""
|
|
1036
|
+
生成总的main.js
|
|
1037
|
+
按照如下顺序组合:
|
|
1038
|
+
./org.transcrypt.__runtime__.js
|
|
1039
|
+
./game.const.js # IGNORE
|
|
1040
|
+
./game.proto.js # IGNORE
|
|
1041
|
+
./game.utils.js # IGNORE
|
|
1042
|
+
{usr_modules}
|
|
1043
|
+
:param usr_modules: list[str] # js vm + 用户自定义模块
|
|
1044
|
+
:param t_imps: list[str] # main前需要导入的模块
|
|
1045
|
+
:param f_sorts: dict{module_name: sort_priority}
|
|
1046
|
+
:param f_replaces: dict{module_name: dict{old: new}}
|
|
1047
|
+
:param g_replaces: dict{old: new}
|
|
1048
|
+
:return: str
|
|
1049
|
+
"""
|
|
1050
|
+
arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES['green']) # like green -> spawn_and_swamp
|
|
1051
|
+
self.TOTAL_INSERT_AT_HEAD += self.ARENA_IMPORTS_GETTER[arena_name]() # add arena imports
|
|
1052
|
+
total_js = f"const __VERSION__ = '{const.VERSION}';\nconst __PYTHON_VERSION__ = '{python_version_info}';" + self.TOTAL_INSERT_AT_HEAD + f"\nexport var LANGUAGE = '{config.language}';\n"
|
|
1053
|
+
total_js += f"const __AUTHOR__ = '{const.AUTHOR}';\nconst __AUTHOR_CN__ = '{const.BILIBILI_NAME}';"
|
|
1054
|
+
|
|
1055
|
+
core.lprint(WAIT, LOC_GENERATING_TOTAL_MAIN_JS, end="", ln=config.language)
|
|
1056
|
+
|
|
1057
|
+
# TODO: IMPS donot work
|
|
1058
|
+
|
|
1059
|
+
# resort modules
|
|
1060
|
+
f_sorts[self.JS_VM] = -1
|
|
1061
|
+
|
|
1062
|
+
for i in range(len(usr_modules)):
|
|
1063
|
+
for j in range(i + 1, len(usr_modules)):
|
|
1064
|
+
if f_sorts[usr_modules[i][2:]] > f_sorts[usr_modules[j][2:]]:
|
|
1065
|
+
usr_modules[i], usr_modules[j] = usr_modules[j], usr_modules[i]
|
|
1066
|
+
|
|
1067
|
+
# write modules
|
|
1068
|
+
for module in usr_modules:
|
|
1069
|
+
content = self.auto_read(os.path.join(self.target_dir, module))
|
|
1070
|
+
content = self.remove_js_import(content)
|
|
1071
|
+
for old, new in f_replaces.get(module, {}).items():
|
|
1072
|
+
content = re.sub(old, new, content)
|
|
1073
|
+
for old, new in self.TRANSCRYPT_ERROR_REPLACE.items():
|
|
1074
|
+
content = re.sub(old, new, content)
|
|
1075
|
+
total_js += f"\n// ---------------------------------------- Module:{module} "
|
|
1076
|
+
total_js += "----------------------------------------\n\n"
|
|
1077
|
+
total_js += content + '\n'
|
|
1078
|
+
|
|
1079
|
+
total_js += self.TOTAL_INSERT_BEFORE_MAIN
|
|
1080
|
+
|
|
1081
|
+
# write main.js
|
|
1082
|
+
content = self.auto_read(self.target_js)
|
|
1083
|
+
for old, new in self.TRANSCRYPT_ERROR_REPLACE.items():
|
|
1084
|
+
content = re.sub(old, new, content)
|
|
1085
|
+
total_js += content
|
|
1086
|
+
|
|
1087
|
+
# TOTAL_APPEND_ATEND
|
|
1088
|
+
total_js += self.TOTAL_APPEND_ATEND
|
|
1089
|
+
|
|
1090
|
+
# replace export-pat
|
|
1091
|
+
total_js = re.sub(self.JS_EXPORT_PAT, "", total_js)
|
|
1092
|
+
|
|
1093
|
+
# global replace
|
|
1094
|
+
for old, new in g_replaces.items():
|
|
1095
|
+
total_js = re.sub(old, new, total_js)
|
|
1096
|
+
|
|
1097
|
+
core.lprint(GREEN.format('[5/6]'), LOC_DONE, " ", LOC_GENERATING_TOTAL_MAIN_JS_FINISH, sep="", head="\r", ln=config.language)
|
|
1098
|
+
|
|
1099
|
+
# REPACE
|
|
1100
|
+
for old, new in self.TOTAL_SIMPLE_REPLACE_WITH.items():
|
|
1101
|
+
total_js = total_js.replace(old, new)
|
|
1102
|
+
|
|
1103
|
+
return total_js
|
|
1104
|
+
|
|
1105
|
+
def __parse_js_file_sort(self, fpath):
|
|
1106
|
+
"""
|
|
1107
|
+
解析js文件中的sort
|
|
1108
|
+
:param fpath:
|
|
1109
|
+
:return:
|
|
1110
|
+
"""
|
|
1111
|
+
content = self.auto_read(fpath)
|
|
1112
|
+
m = re.search(r'//\s*>\s*sort\s+(\d+)', content)
|
|
1113
|
+
if m:
|
|
1114
|
+
return int(m.group(1))
|
|
1115
|
+
return 65535
|
|
1116
|
+
|
|
1117
|
+
def find_add_pure_js_files(self, sorts, modules):
|
|
1118
|
+
"""
|
|
1119
|
+
找到所有的纯js文件,并添加到modules中
|
|
1120
|
+
:param sorts:
|
|
1121
|
+
:param modules:
|
|
1122
|
+
:return:
|
|
1123
|
+
"""
|
|
1124
|
+
for root, dirs, files in os.walk(self.lib_dir):
|
|
1125
|
+
for file in files:
|
|
1126
|
+
if file.endswith('.js') and file not in modules:
|
|
1127
|
+
fpath = str(os.path.join(root, file))
|
|
1128
|
+
fname = file.replace('\\', '/')
|
|
1129
|
+
# copy file to target
|
|
1130
|
+
shutil.copy(fpath, os.path.join(self.target_dir, fname))
|
|
1131
|
+
sorts[fname] = self.__parse_js_file_sort(fpath)
|
|
1132
|
+
modules.append("./" + fname)
|
|
1133
|
+
|
|
1134
|
+
def compile(self, paste=False):
|
|
1135
|
+
"""
|
|
1136
|
+
编译
|
|
1137
|
+
:param paste: 是否复制到剪贴板
|
|
1138
|
+
:return:
|
|
1139
|
+
"""
|
|
1140
|
+
imps, jimps, sorts, defs, reps = self.pre_compile()
|
|
1141
|
+
self.transcrypt_cmd()
|
|
1142
|
+
imports, modules = self.analyze_rebuild_main_js(defs, jimps)
|
|
1143
|
+
self.find_add_pure_js_files(sorts, modules)
|
|
1144
|
+
total_js = imports + "\n" + self.generate_total_js(replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps)
|
|
1145
|
+
|
|
1146
|
+
core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
|
|
1147
|
+
|
|
1148
|
+
# ensure exported main.mjs path
|
|
1149
|
+
build_main_mjs = os.path.join(self.build_dir, 'main.mjs')
|
|
1150
|
+
|
|
1151
|
+
mjs_path = config.target if config.target is not None else config.TARGET_GETTER()
|
|
1152
|
+
if not mjs_path.endswith('js'):
|
|
1153
|
+
mjs_path = os.path.join(mjs_path, 'main.mjs')
|
|
1154
|
+
|
|
1155
|
+
# write main.mjs
|
|
1156
|
+
with open(build_main_mjs, 'w', encoding='utf-8') as f:
|
|
1157
|
+
f.write(total_js)
|
|
1158
|
+
|
|
1159
|
+
# export main.mjs
|
|
1160
|
+
dir_path = os.path.dirname(mjs_path)
|
|
1161
|
+
if not os.path.exists(dir_path):
|
|
1162
|
+
core.error('Compiler.compile', core.lformat(LOC_EXPORT_DIR_PATH_NOT_EXISTS, [dir_path]), head='\n', ln=config.language)
|
|
1163
|
+
with open(mjs_path, 'w', encoding='utf-8') as f:
|
|
1164
|
+
f.write(total_js)
|
|
1165
|
+
|
|
1166
|
+
core.lprint(GREEN.format('[6/6]'), LOC_DONE, " ", LOC_EXPORTING_TOTAL_MAIN_JS_FINISH, sep="", head="\r", ln=config.language)
|
|
1167
|
+
|
|
1168
|
+
if mjs_path != build_main_mjs:
|
|
1169
|
+
core.lprint(Fore.LIGHTBLUE_EX + '[Info] ' + Fore.RESET, core.lformat(LOC_USR_EXPORT_INFO, [mjs_path]), ln=config.language)
|
|
1170
|
+
|
|
1171
|
+
# copy to clipboard
|
|
1172
|
+
if paste:
|
|
1173
|
+
pyperclip.copy(total_js)
|
|
1174
|
+
core.lprint(LOC_DONE, " ", LOC_COPY_TO_CLIPBOARD, ln=config.language)
|
|
1175
|
+
|
|
1176
|
+
def clean(self):
|
|
1177
|
+
"""
|
|
1178
|
+
清除build目录下除了main.mjs之外的所有文件和目录
|
|
1179
|
+
* 先复制main.mjs到src目录下,然后删除build目录,再将main.mjs剪切回build目录
|
|
1180
|
+
:return:
|
|
1181
|
+
"""
|
|
1182
|
+
core.lprint(WAIT, LOC_CLEAN_BUILD_DIR, end="", ln=config.language)
|
|
1183
|
+
if not os.path.exists(self.build_dir):
|
|
1184
|
+
core.error('Compiler.clean', LOC_BUILD_DIR_NOT_EXISTS, indent=1, head='\n', ln=config.language)
|
|
1185
|
+
|
|
1186
|
+
if not os.path.exists(os.path.join(self.build_dir, 'main.mjs')):
|
|
1187
|
+
core.error('Compiler.clean', LOC_MAIN_MJS_NOT_EXISTS, indent=1, head='\n', ln=config.language)
|
|
1188
|
+
|
|
1189
|
+
# copy main.mjs to src
|
|
1190
|
+
shutil.copy(os.path.join(self.build_dir, 'main.mjs'), os.path.join(self.src_dir, 'main.mjs'))
|
|
1191
|
+
|
|
1192
|
+
# remove build dir
|
|
1193
|
+
shutil.rmtree(self.build_dir)
|
|
1194
|
+
|
|
1195
|
+
# create build dir
|
|
1196
|
+
os.makedirs(self.build_dir)
|
|
1197
|
+
|
|
1198
|
+
# move main.mjs to build dir
|
|
1199
|
+
shutil.move(os.path.join(self.src_dir, 'main.mjs'), os.path.join(self.build_dir, 'main.mjs'))
|
|
1200
|
+
|
|
1201
|
+
core.lprint(GREEN.format('[Done]'), LOC_CLEAN_BUILD_DIR_FINISH, head="\r", ln=config.language)
|
|
1202
|
+
|
|
1203
|
+
def clear(self):
|
|
1204
|
+
"""
|
|
1205
|
+
清除build目录下所有文件和目录
|
|
1206
|
+
:return:
|
|
1207
|
+
"""
|
|
1208
|
+
core.lprint(WAIT, LOC_CLEAN_BUILD_DIR, end="", ln=config.language)
|
|
1209
|
+
if not os.path.exists(self.build_dir):
|
|
1210
|
+
core.lprint(LOC_BUILD_DIR_NOT_EXISTS, ln=config.language)
|
|
1211
|
+
|
|
1212
|
+
shutil.rmtree(self.build_dir)
|
|
1213
|
+
os.makedirs(self.build_dir)
|
|
1214
|
+
|
|
1215
|
+
core.lprint(GREEN.format('[Done]'), LOC_CLEAN_BUILD_DIR_FINISH, head="\r", ln=config.language)
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
if __name__ == '__main__':
|
|
1219
|
+
compiler = Compiler('src', 'library', 'build')
|
|
1220
|
+
compiler.compile()
|
|
1221
|
+
compiler.clean()
|