pyscreeps-arena 0.3.6__py3-none-any.whl → 0.5.8.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.
- pyscreeps_arena/__init__.py +59 -2
- pyscreeps_arena/compiler.py +616 -73
- pyscreeps_arena/core/config.py +1 -1
- pyscreeps_arena/core/const.py +6 -5
- pyscreeps_arena/localization.py +10 -0
- pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena/ui/P2PY.py +108 -0
- pyscreeps_arena/ui/__init__.py +12 -0
- pyscreeps_arena/ui/creeplogic_edit.py +14 -0
- pyscreeps_arena/ui/map_render.py +705 -0
- pyscreeps_arena/ui/mapviewer.py +14 -0
- pyscreeps_arena/ui/project_ui.py +215 -0
- pyscreeps_arena/ui/qcreeplogic/__init__.py +3 -0
- pyscreeps_arena/ui/qcreeplogic/model.py +72 -0
- pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +770 -0
- pyscreeps_arena/ui/qmapker/__init__.py +1 -0
- pyscreeps_arena/ui/qmapker/qmapmarker.py +339 -0
- pyscreeps_arena/ui/qmapker/qvariable.py +303 -0
- pyscreeps_arena/ui/qmapker/test_compact_variable.py +61 -0
- pyscreeps_arena/ui/qmapker/test_qmapmarker.py +71 -0
- pyscreeps_arena/ui/qmapker/test_qvariable.py +49 -0
- pyscreeps_arena/ui/qmapker/to_code.py +100 -0
- pyscreeps_arena/ui/qmapv/__init__.py +3 -0
- pyscreeps_arena/ui/qmapv/qcinfo.py +567 -0
- pyscreeps_arena/ui/qmapv/qco.py +441 -0
- pyscreeps_arena/ui/qmapv/qmapv.py +728 -0
- pyscreeps_arena/ui/qmapv/test_array_drag.py +191 -0
- pyscreeps_arena/ui/qmapv/test_drag.py +107 -0
- pyscreeps_arena/ui/qmapv/test_qcinfo.py +169 -0
- pyscreeps_arena/ui/qmapv/test_qco_drag.py +7 -0
- pyscreeps_arena/ui/qmapv/test_qmapv.py +224 -0
- pyscreeps_arena/ui/qmapv/test_simple_array.py +303 -0
- pyscreeps_arena/ui/qrecipe/__init__.py +1 -0
- pyscreeps_arena/ui/qrecipe/model.py +434 -0
- pyscreeps_arena/ui/qrecipe/qrecipe.py +914 -0
- pyscreeps_arena/ui/rs_icon.py +43 -0
- {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.8.0.dist-info}/METADATA +15 -3
- pyscreeps_arena-0.5.8.0.dist-info/RECORD +47 -0
- {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.8.0.dist-info}/WHEEL +1 -1
- pyscreeps_arena-0.5.8.0.dist-info/entry_points.txt +4 -0
- pyscreeps_arena-0.3.6.dist-info/RECORD +0 -17
- pyscreeps_arena-0.3.6.dist-info/entry_points.txt +0 -2
- {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.8.0.dist-info}/top_level.txt +0 -0
pyscreeps_arena/compiler.py
CHANGED
|
@@ -9,6 +9,7 @@ import chardet
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import pyperclip
|
|
11
11
|
from colorama import Fore
|
|
12
|
+
from typing import List, Optional, Tuple, Union
|
|
12
13
|
|
|
13
14
|
WAIT = Fore.YELLOW + ">>>" + Fore.RESET
|
|
14
15
|
GREEN = Fore.GREEN + "{}" + Fore.RESET
|
|
@@ -16,6 +17,32 @@ python_version_info = sys.version_info
|
|
|
16
17
|
python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
class MatchCaseError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def replace_src_prefix(file_list):
|
|
25
|
+
"""
|
|
26
|
+
将列表中以'./src.'开头的字符串替换为'./'
|
|
27
|
+
|
|
28
|
+
参数:
|
|
29
|
+
file_list: 字符串列表
|
|
30
|
+
|
|
31
|
+
返回:
|
|
32
|
+
替换后的新列表
|
|
33
|
+
"""
|
|
34
|
+
_ = []
|
|
35
|
+
|
|
36
|
+
for item in file_list:
|
|
37
|
+
if isinstance(item, str) and item.startswith('./src.'):
|
|
38
|
+
_new = item.replace('./src.', './', 1)
|
|
39
|
+
if _new in file_list:
|
|
40
|
+
continue
|
|
41
|
+
_.append(item)
|
|
42
|
+
|
|
43
|
+
return _
|
|
44
|
+
|
|
45
|
+
|
|
19
46
|
# def InsertPragmaBefore(content:str) -> str:
|
|
20
47
|
# """
|
|
21
48
|
# 在content的开头插入__pragma__('noalias', 'undefined')等内容 |
|
|
@@ -43,7 +70,7 @@ class Compiler_Const:
|
|
|
43
70
|
|
|
44
71
|
TOTAL_INSERT_AT_HEAD = """
|
|
45
72
|
import { createConstructionSite, findClosestByPath, findClosestByRange, findInRange, findPath, getCpuTime, getDirection, getHeapStatistics, getObjectById, getObjects, getObjectsByPrototype, getRange, getTerrainAt, getTicks,} from 'game/utils';
|
|
46
|
-
import { ConstructionSite, Creep, GameObject, OwnedStructure, Resource, Source, Structure, StructureContainer, StructureExtension, StructureRampart, StructureRoad, StructureSpawn, StructureWall, StructureTower} from 'game/prototypes';
|
|
73
|
+
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';
|
|
47
74
|
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';
|
|
48
75
|
|
|
49
76
|
import {arenaInfo} from "game";
|
|
@@ -56,27 +83,40 @@ import {searchPath, CostMatrix} from "game/path-finder"
|
|
|
56
83
|
|
|
57
84
|
TOTAL_APPEND_ATEND = """
|
|
58
85
|
export var sch = Scheduler();
|
|
59
|
-
var monitor = Monitor(
|
|
86
|
+
var monitor = Monitor(1);
|
|
60
87
|
know.now = 0;
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
StageMachineLogicMeta.__types__ = []; // 清空js首次构造时引入的数据
|
|
90
|
+
__init_before_k__();
|
|
91
|
+
let knowCost = 0;
|
|
92
|
+
let monitorCost = 0;
|
|
93
|
+
let stepCost = 0;
|
|
94
|
+
let timeLine = 0;
|
|
65
95
|
export var loop = function () {
|
|
66
|
-
|
|
96
|
+
get.handle();
|
|
97
|
+
know.now = get.now;
|
|
98
|
+
timeLine = get.cpu_us();
|
|
67
99
|
know.handle();
|
|
68
|
-
|
|
100
|
+
knowCost = get.cpu_us() - timeLine;
|
|
101
|
+
|
|
102
|
+
timeLine = get.cpu_us();
|
|
103
|
+
monitor.handle();
|
|
104
|
+
monitorCost = get.cpu_us() - timeLine;
|
|
105
|
+
for (const creep of know.creeps){
|
|
106
|
+
creep.handle();
|
|
107
|
+
}
|
|
108
|
+
if (know.now === 1) {
|
|
69
109
|
std.show_welcome();
|
|
70
110
|
init (know);
|
|
111
|
+
|
|
71
112
|
}
|
|
72
|
-
|
|
73
|
-
monitor.handle();
|
|
74
|
-
for (const creep of know._creeps){
|
|
75
|
-
creep.motion.handle();
|
|
76
|
-
}
|
|
77
113
|
step (know);
|
|
78
|
-
|
|
114
|
+
timeLine = get.cpu_us();
|
|
115
|
+
if (get._SCH_FLAG) sch.handle();
|
|
116
|
+
stepCost = get.cpu_us() - timeLine;
|
|
79
117
|
std.show_usage ();
|
|
118
|
+
print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
|
|
119
|
+
if (know.draw) know.draw();
|
|
80
120
|
};
|
|
81
121
|
"""
|
|
82
122
|
|
|
@@ -114,42 +154,61 @@ export var loop = function () {
|
|
|
114
154
|
|
|
115
155
|
ARENA_IMPORTS_GETTER = {
|
|
116
156
|
const.ARENA_GREEN: lambda: f"""
|
|
117
|
-
|
|
118
|
-
|
|
157
|
+
const ARENA_COLOR_TYPE = "GREEN";
|
|
158
|
+
class GameAreaEffect{{
|
|
159
|
+
constructor(){{
|
|
160
|
+
}}
|
|
161
|
+
}};
|
|
162
|
+
class GameConstructionBoost{{
|
|
163
|
+
constructor(){{
|
|
119
164
|
}}
|
|
120
165
|
}};
|
|
121
|
-
const
|
|
166
|
+
import {{ Portal as GamePortal}} from 'arena/season_{config.season}/{const.ARENA_GREEN}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
|
|
122
167
|
""",
|
|
168
|
+
# import {Portal} from 'arena/season_1/portal_exploration/basic/prototypes';
|
|
169
|
+
|
|
123
170
|
const.ARENA_BLUE: lambda: f"""
|
|
124
|
-
const
|
|
125
|
-
|
|
171
|
+
const ARENA_COLOR_TYPE = "BLUE";
|
|
172
|
+
const GameScoreCollector = GameStructureSpawn;
|
|
173
|
+
class GameAreaEffect{{
|
|
174
|
+
constructor(){{
|
|
175
|
+
}}
|
|
176
|
+
}};
|
|
177
|
+
class GamePortal{{
|
|
178
|
+
constructor(){{
|
|
179
|
+
}}
|
|
180
|
+
}};
|
|
181
|
+
class GameConstructionBoost{{
|
|
182
|
+
constructor(){{
|
|
183
|
+
}}
|
|
184
|
+
}};
|
|
126
185
|
""",
|
|
127
186
|
const.ARENA_RED: lambda: f"""
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
const ARENA_COLOR_TYPE = "RED";
|
|
188
|
+
class GamePortal{{
|
|
189
|
+
constructor(){{
|
|
130
190
|
}}
|
|
131
191
|
}};
|
|
132
|
-
import {{
|
|
192
|
+
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';
|
|
193
|
+
import {{ EFFECT_CONSTRUCTION_BOOST, EFFECT_SLOWDOWN }} from 'arena/season_{config.season}/{const.ARENA_RED}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/constants';
|
|
133
194
|
|
|
134
|
-
import ("arena/season_{config.season}/collect_and_control/basic")
|
|
135
|
-
.then((module) => {{ const RESOURCE_SCORE_X = module.RESOURCE_SCORE_X; const RESOURCE_SCORE_Y = module.RESOURCE_SCORE_Y; const RESOURCE_SCORE_Z = module.RESOURCE_SCORE_Z; }})
|
|
136
|
-
.catch((error) => {{ }});
|
|
137
195
|
""",
|
|
138
196
|
const.ARENA_GRAY: lambda: f"""
|
|
139
|
-
|
|
140
|
-
|
|
197
|
+
const ARENA_COLOR_TYPE = "GRAY";
|
|
198
|
+
class GameAreaEffect{{
|
|
199
|
+
constructor(){{
|
|
200
|
+
}}
|
|
201
|
+
}};
|
|
202
|
+
class GamePortal{{
|
|
203
|
+
constructor(){{
|
|
204
|
+
}}
|
|
205
|
+
}};
|
|
206
|
+
class GameConstructionBoost{{
|
|
207
|
+
constructor(){{
|
|
141
208
|
}}
|
|
142
209
|
}};
|
|
143
|
-
const ScoreCollector = StructureSpawn;
|
|
144
|
-
import("game/prototypes")
|
|
145
|
-
.then((module) => {{ const Flag = module.Flag; }})
|
|
146
|
-
.catch((error) => {{ }});
|
|
147
210
|
""",
|
|
148
211
|
}
|
|
149
|
-
ARENA_IMPORTS_NOT_BLUE = ""
|
|
150
|
-
ARENA_IMPORTS_NOT_BLUE1 = """
|
|
151
|
-
import { StructureTower } from 'game/prototypes'
|
|
152
|
-
"""
|
|
153
212
|
|
|
154
213
|
|
|
155
214
|
class Compiler_Utils(Compiler_Const):
|
|
@@ -165,7 +224,7 @@ class Compiler_Utils(Compiler_Const):
|
|
|
165
224
|
if not Compiler_Utils.last_output:
|
|
166
225
|
Compiler_Utils.last_output = True
|
|
167
226
|
print()
|
|
168
|
-
core.warn('Compiler_Utils.auto_read', core.lformat(LOC_FILE_NOT_EXISTS, [fpath]), end='', head='\n', ln=config.language)
|
|
227
|
+
core.warn('Compiler_Utils.auto_read', core.lformat(LOC_FILE_NOT_EXISTS, ["", fpath]), end='', head='\n', ln=config.language)
|
|
169
228
|
return ""
|
|
170
229
|
|
|
171
230
|
try:
|
|
@@ -177,12 +236,16 @@ class Compiler_Utils(Compiler_Const):
|
|
|
177
236
|
return f.read()
|
|
178
237
|
except UnicodeDecodeError:
|
|
179
238
|
# 如果使用检测到的编码读取失败,尝试使用chardet检测编码
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
239
|
+
try:
|
|
240
|
+
with open(fpath, 'rb') as f: # 以二进制模式打开文件
|
|
241
|
+
raw_data = f.read() # 读取文件的原始数据
|
|
242
|
+
result = chardet.detect(raw_data) # 使用chardet检测编码
|
|
243
|
+
encoding = result['encoding'] # 获取检测到的编码
|
|
244
|
+
with open(fpath, 'r', encoding=encoding) as f: # 使用检测到的编码打开文件
|
|
245
|
+
return f.read()
|
|
246
|
+
except UnicodeDecodeError as e:
|
|
247
|
+
core.error('Compiler_Utils.auto_read', core.lformat(LOC_FILE_READ_FAILED, [fpath, "UnicodeError", e]), end='', head='\n', ln=config.language)
|
|
248
|
+
quit(-1)
|
|
186
249
|
|
|
187
250
|
def copy_to(self) -> list:
|
|
188
251
|
"""
|
|
@@ -283,8 +346,102 @@ class Compiler_Utils(Compiler_Const):
|
|
|
283
346
|
|
|
284
347
|
return '\n'.join(result) # 将处理后的所有代码行连接成一个字符串,并返回最终结果 | join all processed lines into a string and return
|
|
285
348
|
|
|
286
|
-
def
|
|
349
|
+
def expand_folder_imports(self, fpath: str, project_path: str = None):
|
|
287
350
|
"""
|
|
351
|
+
扩展文件夹导入语句:将 `from folder import *` 替换为 `from folder.module import *`
|
|
352
|
+
仅在文件夹没有 __init__.py 时执行此操作
|
|
353
|
+
|
|
354
|
+
:param fpath: 要处理的文件路径
|
|
355
|
+
:param project_path: 项目根路径,用于解析相对导入,默认为 None(使用文件所在目录)
|
|
356
|
+
"""
|
|
357
|
+
if not os.path.exists(fpath):
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
content = self.auto_read(fpath)
|
|
361
|
+
lines = content.split('\n')
|
|
362
|
+
new_lines = []
|
|
363
|
+
changed = False
|
|
364
|
+
|
|
365
|
+
for line in lines:
|
|
366
|
+
m = self.PY_IMPORT_PAT.match(line)
|
|
367
|
+
if not m:
|
|
368
|
+
new_lines.append(line)
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
original_target = m.group(1)
|
|
372
|
+
target = original_target
|
|
373
|
+
target_path = project_path or os.path.dirname(fpath)
|
|
374
|
+
|
|
375
|
+
# 处理相对路径(向前定位)
|
|
376
|
+
if target.startswith('.'):
|
|
377
|
+
target_path = os.path.dirname(fpath)
|
|
378
|
+
count = 0
|
|
379
|
+
for c in target:
|
|
380
|
+
if c == '.':
|
|
381
|
+
count += 1
|
|
382
|
+
else:
|
|
383
|
+
break
|
|
384
|
+
|
|
385
|
+
# 向上移动目录
|
|
386
|
+
if count > 1:
|
|
387
|
+
for _ in range(count - 1):
|
|
388
|
+
target_path = os.path.dirname(target_path)
|
|
389
|
+
|
|
390
|
+
# 移除开头的点
|
|
391
|
+
target = target[count:]
|
|
392
|
+
|
|
393
|
+
# 如果 target 为空,跳过
|
|
394
|
+
if not target:
|
|
395
|
+
new_lines.append(line)
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
# 向后定位,构建完整路径
|
|
399
|
+
temp_target = target
|
|
400
|
+
while (_idx := temp_target.find('.')) != -1:
|
|
401
|
+
part = temp_target[:_idx]
|
|
402
|
+
target_path = os.path.join(target_path, part)
|
|
403
|
+
temp_target = temp_target[_idx + 1:]
|
|
404
|
+
|
|
405
|
+
# 最终的文件夹路径
|
|
406
|
+
final_dir_path = os.path.join(target_path, temp_target) if temp_target else target_path
|
|
407
|
+
|
|
408
|
+
# 检查是否是文件夹且没有 __init__.py
|
|
409
|
+
if os.path.isdir(final_dir_path):
|
|
410
|
+
init_path = os.path.join(final_dir_path, '__init__.py')
|
|
411
|
+
if not os.path.exists(init_path):
|
|
412
|
+
# 找到所有 .py 文件(排除 __init__.py)| 如果包含子目录,产生一个警告
|
|
413
|
+
# try:
|
|
414
|
+
# py_files = [f for f in os.listdir(final_dir_path) if f.endswith('.py') and f != '__init__.py']
|
|
415
|
+
# except (FileNotFoundError, PermissionError):
|
|
416
|
+
py_files = []
|
|
417
|
+
for item in os.listdir(final_dir_path):
|
|
418
|
+
_path = os.path.join(final_dir_path, item)
|
|
419
|
+
if os.path.isfile(_path) and item.endswith('.py') and item != '__init__.py':
|
|
420
|
+
py_files.append(item)
|
|
421
|
+
elif os.path.isdir(_path):
|
|
422
|
+
rel = os.path.relpath(final_dir_path, project_path)
|
|
423
|
+
core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language)
|
|
424
|
+
|
|
425
|
+
# 为每个 .py 文件生成导入语句
|
|
426
|
+
if py_files:
|
|
427
|
+
for py_file in py_files:
|
|
428
|
+
module_name = py_file[:-3]
|
|
429
|
+
new_import = f"from {original_target}.{module_name} import *"
|
|
430
|
+
new_lines.append(new_import)
|
|
431
|
+
changed = True
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
# 保留原行
|
|
435
|
+
new_lines.append(line)
|
|
436
|
+
|
|
437
|
+
# 如果文件有修改,写回
|
|
438
|
+
if changed:
|
|
439
|
+
new_content = '\n'.join(new_lines)
|
|
440
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
441
|
+
f.write(new_content)
|
|
442
|
+
|
|
443
|
+
def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
|
|
444
|
+
r"""
|
|
288
445
|
查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
|
|
289
446
|
PY_IMPORT_PAT: re.compile(r'\s+from\s+(.+)(?=\s+import)\s+import\s+\*')
|
|
290
447
|
:param fpath: str 目标文件路径 | target file path
|
|
@@ -296,7 +453,7 @@ class Compiler_Utils(Compiler_Const):
|
|
|
296
453
|
if records is None:
|
|
297
454
|
records = {}
|
|
298
455
|
if not os.path.exists(fpath):
|
|
299
|
-
core.error('Compiler.find_chain_import', core.lformat(LOC_FILE_NOT_EXISTS, [fpath]), head='\n', ln=config.language)
|
|
456
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_FILE_NOT_EXISTS, ["py", fpath]), head='\n', ln=config.language)
|
|
300
457
|
imps = []
|
|
301
458
|
content = self.auto_read(fpath)
|
|
302
459
|
project_path = project_path or os.path.dirname(fpath)
|
|
@@ -342,14 +499,89 @@ class Compiler_Utils(Compiler_Const):
|
|
|
342
499
|
|
|
343
500
|
return imps
|
|
344
501
|
|
|
502
|
+
def find_chain_import2(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
|
|
503
|
+
r"""
|
|
504
|
+
查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
|
|
505
|
+
PY_IMPORT_PAT: re.compile(r'\s+from\s+(.+)(?=\s+import)\s+import\s+\*')
|
|
506
|
+
:param fpath: str 目标文件路径 | target file path
|
|
507
|
+
:param search_dirs: list[str] 搜索目录 | search directories
|
|
508
|
+
:param project_path=None: str python项目中的概念,指根文件所在的目录。如果不指定,默认使用第一次调用时给定的fpath,并且稍后的递归会全部使用此路径 |
|
|
509
|
+
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
|
|
510
|
+
:param records=None: dict[str, None] 记录已经查找过的文件路径,避免重复查找 | record the file paths that have been searched to avoid duplicate searches
|
|
511
|
+
"""
|
|
512
|
+
if records is None:
|
|
513
|
+
records = {}
|
|
514
|
+
if not os.path.exists(fpath):
|
|
515
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_FILE_NOT_EXISTS, [fpath]), head='\n', ln=config.language)
|
|
516
|
+
imps = []
|
|
517
|
+
content = self.auto_read(fpath)
|
|
518
|
+
project_path = project_path or os.path.dirname(fpath)
|
|
519
|
+
|
|
520
|
+
# 添加根目录和 src 目录到 search_dirs
|
|
521
|
+
root_dir = os.path.dirname(project_path) # 根目录
|
|
522
|
+
src_dir = os.path.join(root_dir, 'src') # src 目录
|
|
523
|
+
if root_dir not in search_dirs:
|
|
524
|
+
search_dirs = [root_dir] + search_dirs
|
|
525
|
+
if src_dir not in search_dirs:
|
|
526
|
+
search_dirs = [src_dir] + search_dirs
|
|
527
|
+
|
|
528
|
+
for no, line in enumerate(content.split('\n')):
|
|
529
|
+
m = self.PY_IMPORT_PAT.match(line)
|
|
530
|
+
if m:
|
|
531
|
+
target = m.group(1)
|
|
532
|
+
target_path = project_path
|
|
533
|
+
|
|
534
|
+
## 向前定位 | locate forward
|
|
535
|
+
if target.startswith('.'):
|
|
536
|
+
target_path = os.path.dirname(fpath) # 因为使用了相对路径,所以需要先定位到当前文件所在的目录 |
|
|
537
|
+
# because relative path is used, need to locate the directory where the current file is located first
|
|
538
|
+
count = 0
|
|
539
|
+
for c in target:
|
|
540
|
+
if c == '.':
|
|
541
|
+
count += 1
|
|
542
|
+
else:
|
|
543
|
+
break
|
|
544
|
+
if count > 1:
|
|
545
|
+
for _ in range(count - 1):
|
|
546
|
+
target_path = os.path.dirname(target_path)
|
|
547
|
+
|
|
548
|
+
## 向后定位 | locate backward
|
|
549
|
+
while (_idx := target.find('.')) != -1:
|
|
550
|
+
first_name = target[:_idx]
|
|
551
|
+
target_path = os.path.join(target_path, first_name)
|
|
552
|
+
target = target[_idx + 1:]
|
|
553
|
+
|
|
554
|
+
## 检查是否存在 | check if exists
|
|
555
|
+
this_path = os.path.join(target_path, target)
|
|
556
|
+
if os.path.isdir(this_path):
|
|
557
|
+
this_path = os.path.join(this_path, '__init__.py')
|
|
558
|
+
else:
|
|
559
|
+
this_path += '.py'
|
|
560
|
+
|
|
561
|
+
if not os.path.exists(this_path):
|
|
562
|
+
# 如果当前路径不存在,尝试在 search_dirs 中查找
|
|
563
|
+
for search_dir in search_dirs:
|
|
564
|
+
search_path = os.path.join(search_dir, target.replace('.', os.sep)) + ('.py' if not os.path.isdir(this_path) else os.sep + '__init__.py')
|
|
565
|
+
if os.path.exists(search_path):
|
|
566
|
+
this_path = search_path
|
|
567
|
+
break
|
|
568
|
+
else:
|
|
569
|
+
core.error('Compiler.find_chain_import', core.lformat(LOC_CHAIN_FILE_NOT_EXISTS, [fpath, no + 1, this_path]), head='\n', ln=config.language)
|
|
570
|
+
if this_path not in records:
|
|
571
|
+
records[this_path] = None
|
|
572
|
+
tmp = self.find_chain_import(this_path, search_dirs, project_path, records) + [this_path]
|
|
573
|
+
imps.extend(tmp)
|
|
574
|
+
|
|
575
|
+
return imps
|
|
576
|
+
|
|
345
577
|
@staticmethod
|
|
346
|
-
def relist_pyimports_to_jsimports(base_dir:str, pyimps:list[str]) -> list[str]:
|
|
578
|
+
def relist_pyimports_to_jsimports(base_dir: str, pyimps: list[str]) -> list[str]:
|
|
347
579
|
"""
|
|
348
580
|
将python的imports路径列表转换为js的imports路径列表 | convert a list of python imports paths to a list of js imports paths
|
|
349
581
|
"""
|
|
350
582
|
jsimps = []
|
|
351
583
|
for pyimp in pyimps:
|
|
352
|
-
rel_path_nodes:list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
|
|
584
|
+
rel_path_nodes: list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
|
|
353
585
|
if rel_path_nodes[-1] == '__init__.py':
|
|
354
586
|
rel_path_nodes.pop()
|
|
355
587
|
else:
|
|
@@ -358,21 +590,276 @@ class Compiler_Utils(Compiler_Const):
|
|
|
358
590
|
return jsimps
|
|
359
591
|
|
|
360
592
|
# ---------- 自定义函数 ---------- #
|
|
593
|
+
|
|
361
594
|
@staticmethod
|
|
362
|
-
def stage_recursive_replace(content:str) -> str:
|
|
595
|
+
def stage_recursive_replace(content: str) -> str:
|
|
596
|
+
"""
|
|
597
|
+
移除 '@recursive' 装饰器行,并在文末添加对应的 _recursiveLogin 调用。
|
|
598
|
+
|
|
599
|
+
对于类方法: _recursiveLogin("ClassName", "method_name")
|
|
600
|
+
对于普通函数: _recursiveLogin("", "function_name")
|
|
363
601
|
"""
|
|
364
|
-
|
|
365
|
-
|
|
602
|
+
calls_to_add = []
|
|
603
|
+
deletions = []
|
|
604
|
+
|
|
605
|
+
# 1. 收集所有类定义的位置和缩进
|
|
606
|
+
class_pattern = re.compile(r'^(\s*)class\s+(\w+)', re.MULTILINE)
|
|
607
|
+
classes = [(m.start(), len(m.group(1)), m.group(2))
|
|
608
|
+
for m in class_pattern.finditer(content)]
|
|
366
609
|
|
|
367
|
-
|
|
610
|
+
# 2. 查找所有 @recursive 装饰器
|
|
611
|
+
decorator_pattern = re.compile(r'^\s*@\s*recursive\s*$\n?', re.MULTILINE)
|
|
612
|
+
|
|
613
|
+
for dec_match in decorator_pattern.finditer(content):
|
|
614
|
+
dec_end = dec_match.end()
|
|
615
|
+
|
|
616
|
+
# 查找接下来的函数定义(跳过可能的空行)
|
|
617
|
+
after_decorator = content[dec_end:]
|
|
618
|
+
func_match = re.search(r'^(\s*)def\s+([^\s\(]+)', after_decorator, re.MULTILINE)
|
|
619
|
+
|
|
620
|
+
if not func_match:
|
|
621
|
+
continue
|
|
622
|
+
|
|
623
|
+
func_indent_len = len(func_match.group(1))
|
|
624
|
+
func_name = func_match.group(2)
|
|
625
|
+
|
|
626
|
+
# 3. 确定类名:查找装饰器前最近的、缩进小于函数缩进的类
|
|
627
|
+
class_name = ""
|
|
628
|
+
for cls_pos, cls_indent_len, cls_name in reversed(classes):
|
|
629
|
+
if cls_pos < dec_match.start() and func_indent_len > cls_indent_len:
|
|
630
|
+
class_name = cls_name
|
|
631
|
+
break
|
|
632
|
+
|
|
633
|
+
# 4. 记录删除位置和调用信息
|
|
634
|
+
deletions.append((dec_match.start(), dec_end))
|
|
635
|
+
calls_to_add.append(f'_recursiveLogin("{class_name}", "{func_name}")')
|
|
636
|
+
|
|
637
|
+
# 5. 应用删除(倒序避免位置偏移)
|
|
638
|
+
if not deletions:
|
|
639
|
+
return content
|
|
640
|
+
|
|
641
|
+
result = content
|
|
642
|
+
for start, end in sorted(deletions, key=lambda x: x[0], reverse=True):
|
|
643
|
+
result = result[:start] + result[end:]
|
|
644
|
+
|
|
645
|
+
# 6. 在文末添加调用
|
|
646
|
+
if calls_to_add:
|
|
647
|
+
result = '\n'.join(calls_to_add) + '\n' + result
|
|
648
|
+
|
|
649
|
+
return result
|
|
650
|
+
|
|
651
|
+
@staticmethod
|
|
652
|
+
def process_mate_code(code):
|
|
653
|
+
# 用于存储匹配到的信息
|
|
654
|
+
mate_assignments = []
|
|
655
|
+
# 匹配变量赋值为Mate()的正则表达式,允许变量定义中包含或不包含类型注解
|
|
656
|
+
assign_pattern = re.compile(r'(\w+)\s*(?:\:\s*\w*)?\s*=\s*Mate\s*\(')
|
|
657
|
+
# 匹配类定义的正则表达式
|
|
658
|
+
class_pattern = re.compile(r'class\s+(\w+)')
|
|
659
|
+
# 用于记录当前所在的类名
|
|
660
|
+
current_class = None
|
|
661
|
+
# 将代码按行分割
|
|
662
|
+
lines = code.split('\n')
|
|
663
|
+
# 遍历每一行
|
|
664
|
+
for i, line in enumerate(lines):
|
|
665
|
+
# 匹配类定义
|
|
666
|
+
class_match = class_pattern.match(line)
|
|
667
|
+
if class_match:
|
|
668
|
+
current_class = class_match.group(1)
|
|
669
|
+
# 匹配变量赋值为Mate()
|
|
670
|
+
assign_match = assign_pattern.search(line)
|
|
671
|
+
if assign_match:
|
|
672
|
+
# 检查group(1)前面同一行内是否有#,如果有则忽略
|
|
673
|
+
comment = re.search(r'#', line[:assign_match.start()])
|
|
674
|
+
if comment:
|
|
675
|
+
continue
|
|
676
|
+
variable_name = assign_match.group(1)
|
|
677
|
+
# 存储匹配到的信息
|
|
678
|
+
mate_assignments += [(variable_name, current_class)]
|
|
679
|
+
|
|
680
|
+
output_strings = []
|
|
681
|
+
for variable_name, class_name in mate_assignments:
|
|
682
|
+
output_string = f"# > insert Object.defineProperty ({class_name}, '{variable_name}', property.call ({class_name}, {class_name}.{variable_name}._MateGet_, {class_name}.{variable_name}._MateSet_));"
|
|
683
|
+
output_strings.append(output_string)
|
|
684
|
+
|
|
685
|
+
return code + '\n'.join(output_strings)
|
|
686
|
+
|
|
687
|
+
@staticmethod
|
|
688
|
+
def remove_long_docstring(content: str) -> str:
|
|
368
689
|
"""
|
|
369
|
-
|
|
690
|
+
移除长注释 | remove long docstring
|
|
691
|
+
"""
|
|
692
|
+
code = re.sub(r'"""[^"]*"""', '', content)
|
|
693
|
+
code = re.sub(r"'''[^']*'''", '', code)
|
|
694
|
+
return code
|
|
695
|
+
|
|
696
|
+
@classmethod
|
|
697
|
+
def _collect_logical_line(cls, lines: List[str], start_idx: int) -> Tuple[str, int]:
|
|
698
|
+
"""收集从start_idx开始的逻辑行(处理多行语句,直到遇到:结尾)"""
|
|
699
|
+
if start_idx >= len(lines):
|
|
700
|
+
return "", start_idx
|
|
701
|
+
|
|
702
|
+
parts = [lines[start_idx].rstrip()]
|
|
703
|
+
i = start_idx
|
|
704
|
+
|
|
705
|
+
# 持续收集直到找到以:结尾的行
|
|
706
|
+
while i < len(lines) and not parts[-1].endswith(':'):
|
|
707
|
+
i += 1
|
|
708
|
+
if i < len(lines):
|
|
709
|
+
parts.append(lines[i].rstrip())
|
|
710
|
+
|
|
711
|
+
return " ".join(parts), i
|
|
712
|
+
|
|
713
|
+
@classmethod
|
|
714
|
+
def _convert_block(cls, lines: List[str], match_counter: Optional[List[int]] = None) -> List[str]:
|
|
715
|
+
if match_counter is None:
|
|
716
|
+
match_counter = [0]
|
|
717
|
+
|
|
718
|
+
result = []
|
|
719
|
+
i = 0
|
|
720
|
+
|
|
721
|
+
while i < len(lines):
|
|
722
|
+
line = lines[i].rstrip()
|
|
723
|
+
|
|
724
|
+
# 检测match语句(支持多行)
|
|
725
|
+
if re.match(r'^\s*match\s+', line):
|
|
726
|
+
full_match, end_idx = cls._collect_logical_line(lines, i)
|
|
727
|
+
|
|
728
|
+
match_stmt = re.match(r'^(\s*)match\s+(.+?)\s*:', full_match)
|
|
729
|
+
if not match_stmt:
|
|
730
|
+
result.append(line)
|
|
731
|
+
i += 1
|
|
732
|
+
continue
|
|
733
|
+
|
|
734
|
+
indent = match_stmt.group(1)
|
|
735
|
+
subject = match_stmt.group(2).strip()
|
|
736
|
+
i = end_idx + 1 # 跳过match语句
|
|
737
|
+
|
|
738
|
+
# 生成临时变量
|
|
739
|
+
var_name = f"__MATCH_{match_counter[0]}__"
|
|
740
|
+
match_counter[0] += 1
|
|
741
|
+
result.append(f"{indent}{var_name} = {subject}")
|
|
742
|
+
|
|
743
|
+
# 解析case语句
|
|
744
|
+
cases: List[Tuple[str, List[str]]] = []
|
|
745
|
+
case_indent = None
|
|
746
|
+
|
|
747
|
+
while i < len(lines):
|
|
748
|
+
case_line = lines[i].rstrip()
|
|
749
|
+
|
|
750
|
+
# 缩进检查 - case必须比match缩进更多
|
|
751
|
+
if not case_line.startswith(indent + ' ') and case_line.strip():
|
|
752
|
+
if re.match(r'^\s*case\s+', case_line):
|
|
753
|
+
raise MatchCaseError(f"第 {i + 1} 行: case 缩进必须大于 match")
|
|
754
|
+
break
|
|
755
|
+
|
|
756
|
+
# 检测case语句(不再使用_collect_logical_line,而是单独处理每一行)
|
|
757
|
+
case_match = re.match(r'^(\s+)case\s+(.+?)\s*:', case_line)
|
|
758
|
+
if case_match:
|
|
759
|
+
curr_case_indent = case_match.group(1)
|
|
760
|
+
case_val = case_match.group(2).strip()
|
|
761
|
+
|
|
762
|
+
# 验证缩进 - 允许不同的case缩进(用于嵌套)
|
|
763
|
+
if len(curr_case_indent) <= len(indent):
|
|
764
|
+
raise MatchCaseError(f"第 {i + 1} 行: case 缩进必须大于 match")
|
|
765
|
+
|
|
766
|
+
# 不再强制要求所有case缩进一致,允许嵌套情况下的不同缩进
|
|
767
|
+
if case_indent is None:
|
|
768
|
+
case_indent = curr_case_indent
|
|
769
|
+
|
|
770
|
+
# 提取内联代码(如果有)
|
|
771
|
+
inline_code = ""
|
|
772
|
+
if ':' in case_line:
|
|
773
|
+
after_colon = case_line.split(':', 1)[1].strip()
|
|
774
|
+
if after_colon:
|
|
775
|
+
inline_code = after_colon
|
|
776
|
+
|
|
777
|
+
i += 1
|
|
778
|
+
|
|
779
|
+
# 收集case块
|
|
780
|
+
block_lines = []
|
|
781
|
+
if inline_code:
|
|
782
|
+
block_lines.append(f"{curr_case_indent} {inline_code}")
|
|
783
|
+
|
|
784
|
+
while i < len(lines):
|
|
785
|
+
block_line = lines[i].rstrip()
|
|
786
|
+
if not block_line.strip():
|
|
787
|
+
block_lines.append(block_line)
|
|
788
|
+
i += 1
|
|
789
|
+
continue
|
|
790
|
+
|
|
791
|
+
# 检查是否是下一个case或者缩进回到当前match级别
|
|
792
|
+
if re.match(r'^\s*case\s+', block_line):
|
|
793
|
+
# 检查这个case是否属于当前match还是父级match
|
|
794
|
+
next_case_indent = re.match(r'^\s*', block_line).group(0)
|
|
795
|
+
if len(next_case_indent) <= len(indent):
|
|
796
|
+
# 属于父级match,退出当前match的处理
|
|
797
|
+
break
|
|
798
|
+
# 仍然属于当前match,继续收集
|
|
799
|
+
if block_line.startswith(curr_case_indent + ' '):
|
|
800
|
+
block_lines.append(block_line)
|
|
801
|
+
i += 1
|
|
802
|
+
continue
|
|
803
|
+
else:
|
|
804
|
+
break
|
|
805
|
+
|
|
806
|
+
if block_line.startswith(indent) and not block_line.startswith(curr_case_indent):
|
|
807
|
+
break
|
|
808
|
+
|
|
809
|
+
if block_line.startswith(curr_case_indent + ' '):
|
|
810
|
+
block_lines.append(block_line)
|
|
811
|
+
i += 1
|
|
812
|
+
continue
|
|
813
|
+
|
|
814
|
+
break
|
|
370
815
|
|
|
816
|
+
cases.append((case_val, block_lines))
|
|
817
|
+
else:
|
|
818
|
+
break
|
|
819
|
+
|
|
820
|
+
# 验证case
|
|
821
|
+
seen = set()
|
|
822
|
+
for idx, (val, _) in enumerate(cases):
|
|
823
|
+
if val == '_':
|
|
824
|
+
if idx != len(cases) - 1:
|
|
825
|
+
raise MatchCaseError(f"第 {i + 1} 行附近: case _ 必须在最后")
|
|
826
|
+
else:
|
|
827
|
+
if val in seen:
|
|
828
|
+
raise MatchCaseError(f"第 {i + 1} 行附近: 重复的 case 值 '{val}'")
|
|
829
|
+
seen.add(val)
|
|
830
|
+
|
|
831
|
+
# 生成if/elif/else
|
|
832
|
+
for idx, (case_val, blk_lines) in enumerate(cases):
|
|
833
|
+
keyword = "else" if case_val == '_' else ("if" if idx == 0 else "elif")
|
|
834
|
+
if keyword == "else":
|
|
835
|
+
result.append(f"{indent}else:")
|
|
836
|
+
else:
|
|
837
|
+
result.append(f"{indent}{keyword} {var_name} == {case_val}:")
|
|
838
|
+
|
|
839
|
+
if blk_lines:
|
|
840
|
+
# 递归处理block_lines,以支持嵌套match
|
|
841
|
+
converted_blk_lines = cls._convert_block(blk_lines, match_counter)
|
|
842
|
+
result.extend(converted_blk_lines)
|
|
843
|
+
|
|
844
|
+
continue
|
|
845
|
+
|
|
846
|
+
result.append(line)
|
|
847
|
+
i += 1
|
|
848
|
+
|
|
849
|
+
return result
|
|
850
|
+
|
|
851
|
+
@classmethod
|
|
852
|
+
def convert_match_to_if(cls, code: str) -> str:
|
|
853
|
+
lines = code.split('\n')
|
|
854
|
+
converted_lines = cls._convert_block(lines, [0])
|
|
855
|
+
return '\n'.join(converted_lines)
|
|
371
856
|
|
|
372
857
|
|
|
373
858
|
class CompilerBase(Compiler_Utils):
|
|
374
859
|
|
|
375
|
-
def __init__(self
|
|
860
|
+
def __init__(self):
|
|
861
|
+
src_dir = "src"
|
|
862
|
+
build_dir = "build"
|
|
376
863
|
# check
|
|
377
864
|
if not os.path.exists(src_dir):
|
|
378
865
|
core.error('Compiler.__init__', core.lformat(LOC_FILE_NOT_EXISTS, ['src', src_dir]), head='\n', ln=config.language)
|
|
@@ -435,6 +922,7 @@ class Compiler(CompilerBase):
|
|
|
435
922
|
# 将PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "")插入到文件开头
|
|
436
923
|
content = self.auto_read(fpath)
|
|
437
924
|
content = self.PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "") + content
|
|
925
|
+
# content = self.remove_long_docstring(content) # 移除长注释 | remove long docstring
|
|
438
926
|
|
|
439
927
|
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
|
|
440
928
|
f.write(content)
|
|
@@ -468,6 +956,8 @@ class Compiler(CompilerBase):
|
|
|
468
956
|
if m and (not m.group(2) or m.group(2)[0] != '*'):
|
|
469
957
|
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)
|
|
470
958
|
|
|
959
|
+
self.expand_folder_imports(fpath, self.build_dir)
|
|
960
|
+
|
|
471
961
|
# -------------------------------- EXPAND IMPORT * -------------------------------- #
|
|
472
962
|
_imports = self.find_chain_import(self.target_py, [os.path.dirname(self.src_dir), self.src_dir])
|
|
473
963
|
_js_imports = self.relist_pyimports_to_jsimports(self.build_dir, _imports)
|
|
@@ -506,6 +996,14 @@ class Compiler(CompilerBase):
|
|
|
506
996
|
else:
|
|
507
997
|
_pre_sort_[fname] = 65535
|
|
508
998
|
|
|
999
|
+
# ------------------------------------ 自定义:mate & match ------------------------------------ #
|
|
1000
|
+
for fpath in py_fpath:
|
|
1001
|
+
content = self.auto_read(fpath)
|
|
1002
|
+
content = self.process_mate_code(content) # 调用process_mate_code
|
|
1003
|
+
content = self.convert_match_to_if(content) # 调用convert_match_to_if
|
|
1004
|
+
with open(fpath, 'w', encoding='utf-8') as f:
|
|
1005
|
+
f.write(content)
|
|
1006
|
+
|
|
509
1007
|
# ------------------------------------ DEFINE ------------------------------------ #
|
|
510
1008
|
# 扫描所有# > define的内容,然后在.py中移除这些行,并记录下来
|
|
511
1009
|
# | get all # > define in .py files, and record them
|
|
@@ -579,11 +1077,10 @@ class Compiler(CompilerBase):
|
|
|
579
1077
|
with open(fpath, 'w', encoding='utf-8') as f:
|
|
580
1078
|
f.write(new_content)
|
|
581
1079
|
|
|
582
|
-
# ------------------------------------
|
|
583
|
-
# 调用stage_recursive_replace
|
|
1080
|
+
# ------------------------------------ 自定义:调用stage_recursive_replace ------------------------------------ #
|
|
584
1081
|
for fpath in py_fpath:
|
|
585
1082
|
content = self.auto_read(fpath)
|
|
586
|
-
content = self.stage_recursive_replace(content)
|
|
1083
|
+
content = self.stage_recursive_replace(content) # 调用stage_recursive_replace
|
|
587
1084
|
with open(fpath, 'w', encoding='utf-8') as f:
|
|
588
1085
|
f.write(content)
|
|
589
1086
|
|
|
@@ -591,9 +1088,9 @@ class Compiler(CompilerBase):
|
|
|
591
1088
|
return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
|
|
592
1089
|
|
|
593
1090
|
def transcrypt_cmd(self):
|
|
594
|
-
# 执行cmd命令: transcrypt -b -m -n -s -e 6 target | execute cmd: transcrypt -b -m -n -s -e 6 target
|
|
1091
|
+
# 执行cmd命令: python -m transcrypt -b -m -n -s -e 6 target | execute cmd: python -m transcrypt -b -m -n -s -e 6 target
|
|
595
1092
|
# 并获取cmd得到的输出 | and get the output of the cmd
|
|
596
|
-
cmd = 'transcrypt -b -m -n -s -e 6 %s' % self.target_py
|
|
1093
|
+
cmd = 'python -m transcrypt -b -m -n -s -e 6 %s' % self.target_py
|
|
597
1094
|
core.lprint(WAIT, core.lformat(LOC_TRANSCRYPTING, [cmd]), end="", ln=config.language)
|
|
598
1095
|
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
|
599
1096
|
stdout, stderr = p.communicate()
|
|
@@ -657,7 +1154,7 @@ class Compiler(CompilerBase):
|
|
|
657
1154
|
|
|
658
1155
|
content = self.auto_read(self.target_js)
|
|
659
1156
|
if modules is None: modules = []
|
|
660
|
-
new_modules, new_content = [],""
|
|
1157
|
+
new_modules, new_content = [], ""
|
|
661
1158
|
for line in content.split('\n'):
|
|
662
1159
|
m = re.search(self.JS_IMPORT_PAT, line)
|
|
663
1160
|
if not m:
|
|
@@ -703,7 +1200,7 @@ class Compiler(CompilerBase):
|
|
|
703
1200
|
"""
|
|
704
1201
|
return re.sub(r'import[^\n]*\n', '', raw)
|
|
705
1202
|
|
|
706
|
-
def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces) -> str:
|
|
1203
|
+
def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces, min_js_files=None) -> str:
|
|
707
1204
|
"""
|
|
708
1205
|
生成总的main.js
|
|
709
1206
|
按照如下顺序组合:
|
|
@@ -717,13 +1214,20 @@ class Compiler(CompilerBase):
|
|
|
717
1214
|
:param f_sorts: dict{module_name: sort_priority}
|
|
718
1215
|
:param f_replaces: dict{module_name: dict{old: new}}
|
|
719
1216
|
:param g_replaces: dict{old: new}
|
|
1217
|
+
:param min_js_files: list[str] # .min.js文件路径列表
|
|
720
1218
|
:return: str
|
|
721
1219
|
"""
|
|
722
1220
|
arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES['green']) # like green -> spawn_and_swamp
|
|
723
1221
|
self.TOTAL_INSERT_AT_HEAD += self.ARENA_IMPORTS_GETTER[arena_name]() # add arena imports
|
|
724
|
-
if config.arena != "blue":
|
|
725
|
-
self.TOTAL_INSERT_AT_HEAD += self.ARENA_IMPORTS_NOT_BLUE
|
|
726
1222
|
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"
|
|
1223
|
+
total_js += f"const __AUTHOR__ = '{const.AUTHOR}';\nconst __AUTHOR_CN__ = '{const.BILIBILI_NAME}';"
|
|
1224
|
+
|
|
1225
|
+
# 添加.min.js文件的import语句
|
|
1226
|
+
if min_js_files:
|
|
1227
|
+
for min_js_path in min_js_files:
|
|
1228
|
+
min_js_filename = os.path.basename(min_js_path)
|
|
1229
|
+
total_js += f"\nimport \"./{min_js_filename}\";"
|
|
1230
|
+
total_js += "\n"
|
|
727
1231
|
|
|
728
1232
|
core.lprint(WAIT, LOC_GENERATING_TOTAL_MAIN_JS, end="", ln=config.language)
|
|
729
1233
|
|
|
@@ -790,19 +1294,29 @@ class Compiler(CompilerBase):
|
|
|
790
1294
|
def find_add_pure_js_files(self, sorts, modules):
|
|
791
1295
|
"""
|
|
792
1296
|
找到所有的纯js文件,并添加到modules中
|
|
1297
|
+
忽略.min.js文件,这些文件会被单独处理
|
|
793
1298
|
:param sorts:
|
|
794
1299
|
:param modules:
|
|
795
|
-
:return:
|
|
1300
|
+
:return: list 返回所有.min.js文件的列表
|
|
796
1301
|
"""
|
|
1302
|
+
min_js_files = []
|
|
797
1303
|
for root, dirs, files in os.walk(self.lib_dir):
|
|
798
1304
|
for file in files:
|
|
799
1305
|
if file.endswith('.js') and file not in modules:
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1306
|
+
# 如果是.min.js文件,不拷贝到target,而是记录到单独列表
|
|
1307
|
+
if file.endswith('.min.js'):
|
|
1308
|
+
fpath = str(os.path.join(root, file))
|
|
1309
|
+
min_js_files.append(fpath)
|
|
1310
|
+
else:
|
|
1311
|
+
# 普通js文件,按原逻辑处理
|
|
1312
|
+
fpath = str(os.path.join(root, file))
|
|
1313
|
+
fname = file.replace('\\', '/')
|
|
1314
|
+
# copy file to target
|
|
1315
|
+
shutil.copy(fpath, os.path.join(self.target_dir, fname))
|
|
1316
|
+
sorts[fname] = self.__parse_js_file_sort(fpath)
|
|
1317
|
+
modules.append("./" + fname)
|
|
1318
|
+
|
|
1319
|
+
return min_js_files
|
|
806
1320
|
|
|
807
1321
|
def compile(self, paste=False):
|
|
808
1322
|
"""
|
|
@@ -813,8 +1327,7 @@ class Compiler(CompilerBase):
|
|
|
813
1327
|
imps, jimps, sorts, defs, reps = self.pre_compile()
|
|
814
1328
|
self.transcrypt_cmd()
|
|
815
1329
|
imports, modules = self.analyze_rebuild_main_js(defs, jimps)
|
|
816
|
-
self.find_add_pure_js_files(sorts, modules)
|
|
817
|
-
total_js = imports + "\n" + self.generate_total_js(modules, imps, sorts, self.FILE_STRONG_REPLACE, reps)
|
|
1330
|
+
min_js_files = self.find_add_pure_js_files(sorts, modules)
|
|
818
1331
|
|
|
819
1332
|
core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
|
|
820
1333
|
|
|
@@ -825,12 +1338,28 @@ class Compiler(CompilerBase):
|
|
|
825
1338
|
if not mjs_path.endswith('js'):
|
|
826
1339
|
mjs_path = os.path.join(mjs_path, 'main.mjs')
|
|
827
1340
|
|
|
1341
|
+
# 获取目标目录路径
|
|
1342
|
+
dir_path = os.path.dirname(mjs_path)
|
|
1343
|
+
build_dir_path = os.path.dirname(build_main_mjs)
|
|
1344
|
+
|
|
1345
|
+
# 复制.min.js文件到目标目录
|
|
1346
|
+
for min_js_path in min_js_files:
|
|
1347
|
+
min_js_filename = os.path.basename(min_js_path)
|
|
1348
|
+
# 复制到build目录
|
|
1349
|
+
shutil.copy(min_js_path, os.path.join(build_dir_path, min_js_filename))
|
|
1350
|
+
# 复制到最终导出目录
|
|
1351
|
+
shutil.copy(min_js_path, os.path.join(dir_path, min_js_filename))
|
|
1352
|
+
|
|
1353
|
+
# 生成total_js,传入.min.js文件列表
|
|
1354
|
+
total_js = imports + "\n" + self.generate_total_js(
|
|
1355
|
+
replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps, min_js_files
|
|
1356
|
+
)
|
|
1357
|
+
|
|
828
1358
|
# write main.mjs
|
|
829
1359
|
with open(build_main_mjs, 'w', encoding='utf-8') as f:
|
|
830
1360
|
f.write(total_js)
|
|
831
1361
|
|
|
832
1362
|
# export main.mjs
|
|
833
|
-
dir_path = os.path.dirname(mjs_path)
|
|
834
1363
|
if not os.path.exists(dir_path):
|
|
835
1364
|
core.error('Compiler.compile', core.lformat(LOC_EXPORT_DIR_PATH_NOT_EXISTS, [dir_path]), head='\n', ln=config.language)
|
|
836
1365
|
with open(mjs_path, 'w', encoding='utf-8') as f:
|
|
@@ -889,6 +1418,20 @@ class Compiler(CompilerBase):
|
|
|
889
1418
|
|
|
890
1419
|
|
|
891
1420
|
if __name__ == '__main__':
|
|
892
|
-
compiler = Compiler('src', 'library', 'build')
|
|
893
|
-
compiler.compile()
|
|
894
|
-
compiler.clean()
|
|
1421
|
+
# compiler = Compiler('src', 'library', 'build')
|
|
1422
|
+
# compiler.compile()
|
|
1423
|
+
# compiler.clean()
|
|
1424
|
+
test = """
|
|
1425
|
+
|
|
1426
|
+
def patrolling(self, c: Creep):
|
|
1427
|
+
e = self.center.nearest(k.civilian.enemies, 5)
|
|
1428
|
+
if e:
|
|
1429
|
+
match c.test(e,
|
|
1430
|
+
int(c.hpPer <= 0.9) * 2,
|
|
1431
|
+
int(c.info.melee) * -3
|
|
1432
|
+
):
|
|
1433
|
+
case True: c.move(e, SWAMP_MOTION)
|
|
1434
|
+
case False: c.move(self.bpos, SWAMP_MOTION)
|
|
1435
|
+
|
|
1436
|
+
"""
|
|
1437
|
+
print(f"res=\n{Compiler.convert_match_to_if(test)}")
|