codecin 5.4.2__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.
- codecin/__init__.py +27 -0
- codecin/aot.py +229 -0
- codecin/assembler.py +548 -0
- codecin/cache.py +71 -0
- codecin/cin.py +2558 -0
- codecin/cli.py +282 -0
- codecin/config.py +58 -0
- codecin/console.py +183 -0
- codecin/cpu.py +1654 -0
- codecin/crom.py +222 -0
- codecin/debugger.py +659 -0
- codecin/disasm.py +86 -0
- codecin/errors.py +58 -0
- codecin/isa.py +457 -0
- codecin/jit.py +270 -0
- codecin/libcodecin_native.so +0 -0
- codecin/logger.py +145 -0
- codecin/memory.py +348 -0
- codecin/native/aot/aot.go +70 -0
- codecin/native/aot/build.go +219 -0
- codecin/native/aot/stub_main.go.txt +22 -0
- codecin/native/build.ps1 +33 -0
- codecin/native/build.sh +48 -0
- codecin/native/cmd/codecin/main.go +287 -0
- codecin/native/compiler/codegen.go +1938 -0
- codecin/native/compiler/compile_test.go +164 -0
- codecin/native/compiler/parser.go +1071 -0
- codecin/native/compiler/syscalls.go +86 -0
- codecin/native/compiler/tokenizer.go +336 -0
- codecin/native/compiler/types.go +213 -0
- codecin/native/engine/audio.go +152 -0
- codecin/native/engine/audio_other.go +46 -0
- codecin/native/engine/audio_windows.go +43 -0
- codecin/native/engine/canvas.go +271 -0
- codecin/native/engine/crom.go +86 -0
- codecin/native/engine/encode.go +93 -0
- codecin/native/engine/engine_test.go +198 -0
- codecin/native/engine/isa_gen.go +445 -0
- codecin/native/engine/system.go +190 -0
- codecin/native/engine/termux.go +113 -0
- codecin/native/engine/version_gen.go +7 -0
- codecin/native/engine/vm.go +1157 -0
- codecin/native/go.mod +3 -0
- codecin/native/ir/ir.go +48 -0
- codecin/native/main.go +225 -0
- codecin/native.py +336 -0
- codecin/registers.py +130 -0
- codecin/stats.py +235 -0
- codecin-5.4.2.dist-info/METADATA +985 -0
- codecin-5.4.2.dist-info/RECORD +54 -0
- codecin-5.4.2.dist-info/WHEEL +5 -0
- codecin-5.4.2.dist-info/entry_points.txt +2 -0
- codecin-5.4.2.dist-info/licenses/LICENSE +7 -0
- codecin-5.4.2.dist-info/top_level.txt +1 -0
codecin/cli.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""命令行入口: argparse 参数解析 -> 加载程序 -> 运行。
|
|
2
|
+
|
|
3
|
+
建议 3: 采用 argparse 统一参数表 (不再三份手写解析), 全部选项由
|
|
4
|
+
build_parser() 单一来源定义, --help 与解析行为自动保持一致。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from .config import Config
|
|
13
|
+
from .console import Colors, Console, Panel
|
|
14
|
+
|
|
15
|
+
HELP_INTRO = f"""{Colors.colorize('Code CIN v5.3', Colors.CYAN, True)}
|
|
16
|
+
|
|
17
|
+
{Colors.colorize('Usage:', Colors.YELLOW)}
|
|
18
|
+
python cpu.py <program.[cin|pl|asm|bin]> [options]
|
|
19
|
+
|
|
20
|
+
{Colors.colorize('Supported formats:', Colors.YELLOW)}
|
|
21
|
+
.cin CIN 高级语言 (函数/struct/数组/浮点/字符串)
|
|
22
|
+
.pl Code CIN 汇编语言 (PL 关键字风格)
|
|
23
|
+
.asm Code CIN 汇编
|
|
24
|
+
.bin UCBC 字节码 (由 --compile 生成)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
29
|
+
"""参数表单一来源 (与 Config 字段一一对应)。"""
|
|
30
|
+
p = argparse.ArgumentParser(
|
|
31
|
+
prog='cpu.py',
|
|
32
|
+
description='Code CIN - 高级语言与运行时 (CIN/PL/ASM 工具链 + 多执行路径)',
|
|
33
|
+
add_help=False, # 帮助由 main 以彩色形式打印 (见 --help)
|
|
34
|
+
)
|
|
35
|
+
p.add_argument('program', nargs='?', default=None,
|
|
36
|
+
metavar='program.[cin|pl|asm|bin]',
|
|
37
|
+
help='程序源文件/字节码/镜像')
|
|
38
|
+
p.add_argument('--help', '-h', action='store_true', dest='show_help',
|
|
39
|
+
help='显示帮助')
|
|
40
|
+
from . import __version__
|
|
41
|
+
p.add_argument('--version', '-V', action='version',
|
|
42
|
+
version=f'Code CIN {__version__}',
|
|
43
|
+
help='显示版本号并退出')
|
|
44
|
+
|
|
45
|
+
# 执行路径
|
|
46
|
+
p.add_argument('--no-native', action='store_true',
|
|
47
|
+
help='禁用 Go 原生库, 强制纯 Python 解释执行')
|
|
48
|
+
p.add_argument('--jit', action='store_true', dest='enable_jit',
|
|
49
|
+
help='启用 Python JIT (基本块动态编译)')
|
|
50
|
+
p.add_argument('--no-jit', action='store_true', dest='disable_jit',
|
|
51
|
+
help=argparse.SUPPRESS)
|
|
52
|
+
|
|
53
|
+
# 日志与调试
|
|
54
|
+
p.add_argument('--debug', action='store_true',
|
|
55
|
+
help='调试模式 (超详细 rich 追踪: 逐指令/寄存器/内存/栈/缓存)')
|
|
56
|
+
p.add_argument('--step', action='store_true',
|
|
57
|
+
help='交互式单步执行 (step> 命令集, 与断点调试一致)')
|
|
58
|
+
p.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR',
|
|
59
|
+
'CRITICAL'],
|
|
60
|
+
help='日志级别 (默认 INFO)')
|
|
61
|
+
p.add_argument('--log-file', metavar='FILE', help='日志输出到文件')
|
|
62
|
+
p.add_argument('--sandbox', action='store_true',
|
|
63
|
+
help='沙箱模式 (限制宿主访问)')
|
|
64
|
+
p.add_argument('--no-io', action='store_true',
|
|
65
|
+
help='禁止 IN/OUT 与宿主 I/O')
|
|
66
|
+
|
|
67
|
+
# 性能分析
|
|
68
|
+
p.add_argument('--profile', action='store_true',
|
|
69
|
+
help='执行后输出性能统计')
|
|
70
|
+
p.add_argument('--cache-size', type=int, metavar='N',
|
|
71
|
+
help='缓存行数 (默认 64)')
|
|
72
|
+
p.add_argument('--cache-assoc', type=int, metavar='N',
|
|
73
|
+
help='缓存关联度 (默认 4)')
|
|
74
|
+
p.add_argument('--mem-size', type=int, metavar='BYTES',
|
|
75
|
+
help='内存大小 (默认 65536)')
|
|
76
|
+
p.add_argument('--max-instructions', type=int, metavar='N',
|
|
77
|
+
help='指令数上限 (默认 100000000)')
|
|
78
|
+
p.add_argument('--execution-interval', type=float, metavar='SEC',
|
|
79
|
+
help='每指令间隔秒数 (演示减速用)')
|
|
80
|
+
|
|
81
|
+
# 编译 / CROM
|
|
82
|
+
p.add_argument('--compile', action='store_true', dest='compile_to_bin',
|
|
83
|
+
help='编译为 .bin 字节码后继续执行')
|
|
84
|
+
p.add_argument('--compile-only', action='store_true',
|
|
85
|
+
help='仅编译为 .bin 不执行')
|
|
86
|
+
p.add_argument('-o', '--output', metavar='FILE',
|
|
87
|
+
help='指定输出文件 (.crom/.bin)')
|
|
88
|
+
p.add_argument('--crom', metavar='FILE', help='加载指定 .crom 内存镜像')
|
|
89
|
+
p.add_argument('--save', action='store_true', dest='auto_save_crom',
|
|
90
|
+
help='执行后保存 .crom 内存镜像')
|
|
91
|
+
p.add_argument('--no-compress', action='store_false', dest='compress_crom',
|
|
92
|
+
help='.crom 不压缩 (默认压缩)')
|
|
93
|
+
|
|
94
|
+
# 语言/汇编
|
|
95
|
+
p.add_argument('--optimize', type=int, metavar='0-3',
|
|
96
|
+
help='优化级别 (0-3, 默认 0)')
|
|
97
|
+
p.add_argument('--strict', action='store_true', help='严格汇编模式')
|
|
98
|
+
|
|
99
|
+
# 运行时行为 (A1/A2/A4)
|
|
100
|
+
p.add_argument('--seed', type=int, default=None, metavar='N',
|
|
101
|
+
help='随机种子 (确定性执行; 默认随机)')
|
|
102
|
+
p.add_argument('--bounds-check', action='store_true',
|
|
103
|
+
help='CIN 数组越界运行时检查 (强制解释执行)')
|
|
104
|
+
p.add_argument('--mmu', action='store_true',
|
|
105
|
+
help='启用 MMU 分页 (identity 页表; 未映射页触发缺页错误)')
|
|
106
|
+
p.add_argument('--debug-server', type=int, default=None, metavar='PORT',
|
|
107
|
+
help='启动 TCP 远程调试服务 (连接后驱动式调试: step/continue/'
|
|
108
|
+
'break/regs/mem/history)')
|
|
109
|
+
p.add_argument('--disasm', action='store_true',
|
|
110
|
+
help='反汇编 .bin 字节码为文本清单后退出')
|
|
111
|
+
|
|
112
|
+
# AOT: 编译成独立静态可执行文件
|
|
113
|
+
p.add_argument('--build-exe', nargs='?', const='', default=None,
|
|
114
|
+
metavar='OUT',
|
|
115
|
+
help='编译成独立静态可执行文件 (不依赖 Python/动态库) 后退出; '
|
|
116
|
+
'省略路径时输出到 <程序名>[.exe]')
|
|
117
|
+
p.add_argument('--build-target', default=None, metavar='OS/ARCH',
|
|
118
|
+
help='AOT 交叉编译目标 (默认当前平台): '
|
|
119
|
+
'windows/amd64, linux/amd64, linux/arm64, '
|
|
120
|
+
'darwin/amd64, darwin/arm64')
|
|
121
|
+
p.add_argument('--build-keep-temp', action='store_true',
|
|
122
|
+
help='AOT 构建时保留临时目录 (排查 go build 失败)')
|
|
123
|
+
return p
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _apply_namespace(config: Config, ns: argparse.Namespace) -> None:
|
|
127
|
+
"""把 argparse Namespace 映射到 Config 字段。"""
|
|
128
|
+
config.step_mode = ns.step
|
|
129
|
+
config.debug_mode = ns.debug
|
|
130
|
+
if ns.step or ns.debug:
|
|
131
|
+
config.interactive_mode = True
|
|
132
|
+
config.use_native = not ns.no_native
|
|
133
|
+
config.enable_jit = ns.enable_jit and not ns.disable_jit
|
|
134
|
+
config.sandbox_mode = ns.sandbox
|
|
135
|
+
config.profile = ns.profile
|
|
136
|
+
config.auto_save_crom = ns.auto_save_crom
|
|
137
|
+
config.compress_crom = ns.compress_crom
|
|
138
|
+
config.compile_to_bin = ns.compile_to_bin
|
|
139
|
+
config.compile_only = ns.compile_only
|
|
140
|
+
config.output_file = ns.output
|
|
141
|
+
config.allow_io = not ns.no_io
|
|
142
|
+
config.strict_mode = ns.strict
|
|
143
|
+
config.seed = ns.seed
|
|
144
|
+
config.bounds_check = ns.bounds_check
|
|
145
|
+
config.mmu = ns.mmu
|
|
146
|
+
config.debug_server_port = ns.debug_server
|
|
147
|
+
|
|
148
|
+
if ns.log_level is not None:
|
|
149
|
+
config.log_level = ns.log_level
|
|
150
|
+
if ns.log_file is not None:
|
|
151
|
+
config.log_file = ns.log_file
|
|
152
|
+
if ns.mem_size is not None:
|
|
153
|
+
config.mem_size = ns.mem_size
|
|
154
|
+
if ns.cache_size is not None:
|
|
155
|
+
config.cache_size = ns.cache_size
|
|
156
|
+
if ns.cache_assoc is not None:
|
|
157
|
+
config.cache_assoc = ns.cache_assoc
|
|
158
|
+
if ns.max_instructions is not None:
|
|
159
|
+
config.max_instructions = ns.max_instructions
|
|
160
|
+
if ns.execution_interval is not None:
|
|
161
|
+
config.execution_interval = ns.execution_interval
|
|
162
|
+
if ns.optimize is not None:
|
|
163
|
+
config.optimize = ns.optimize
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _run_aot_build(ns: argparse.Namespace, console, program_file: str) -> int:
|
|
167
|
+
"""AOT: 把 CIN 程序编译成独立静态可执行文件 (--build-exe)。"""
|
|
168
|
+
from . import aot
|
|
169
|
+
from .errors import CPUSimulatorError
|
|
170
|
+
|
|
171
|
+
target = ns.build_target or aot.host_target()
|
|
172
|
+
try:
|
|
173
|
+
goos, _goarch = aot.parse_target(target)
|
|
174
|
+
except aot.AotError as e:
|
|
175
|
+
console.print(Panel(str(e), title='Build Error', border_style='red'))
|
|
176
|
+
return 1
|
|
177
|
+
|
|
178
|
+
out = ns.build_exe or (os.path.splitext(program_file)[0]
|
|
179
|
+
+ aot.exe_suffix(goos))
|
|
180
|
+
console.print(Colors.colorize(
|
|
181
|
+
f"AOT build: {program_file} -> {out} (静态链接, 目标 {target})",
|
|
182
|
+
Colors.CYAN))
|
|
183
|
+
try:
|
|
184
|
+
built = aot.build_program(program_file, out=out, target=target,
|
|
185
|
+
keep_temp=ns.build_keep_temp)
|
|
186
|
+
except (aot.AotError, CPUSimulatorError) as e:
|
|
187
|
+
console.print(Panel(str(e), title='Build Error', border_style='red'))
|
|
188
|
+
return 1
|
|
189
|
+
console.print(Colors.colorize(f"AOT build 完成: {built}", Colors.GREEN))
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
194
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
195
|
+
|
|
196
|
+
if '--help' in args or '-h' in args:
|
|
197
|
+
sys.stdout.write(HELP_INTRO + "\n")
|
|
198
|
+
sys.stdout.write(build_parser().format_help())
|
|
199
|
+
return 0
|
|
200
|
+
|
|
201
|
+
console = Console()
|
|
202
|
+
parser = build_parser()
|
|
203
|
+
try:
|
|
204
|
+
ns = parser.parse_args(args)
|
|
205
|
+
except SystemExit as e:
|
|
206
|
+
# argparse 错误 (未知选项/非法数值): 已打印 usage, 返回其退出码
|
|
207
|
+
return int(e.code) if e.code is not None else 2
|
|
208
|
+
|
|
209
|
+
if ns.program is None:
|
|
210
|
+
console.print(Panel("No program file specified.\n"
|
|
211
|
+
"Use --help for usage information.",
|
|
212
|
+
title="Error", border_style='red'))
|
|
213
|
+
return 1
|
|
214
|
+
|
|
215
|
+
config = Config()
|
|
216
|
+
_apply_namespace(config, ns)
|
|
217
|
+
config.validate()
|
|
218
|
+
|
|
219
|
+
program_file = ns.program
|
|
220
|
+
crom_file = ns.crom
|
|
221
|
+
output_file = (config.output_file
|
|
222
|
+
or os.path.splitext(program_file)[0] + '.bin')
|
|
223
|
+
|
|
224
|
+
if ns.disasm:
|
|
225
|
+
from .disasm import disassemble_file
|
|
226
|
+
try:
|
|
227
|
+
lines = disassemble_file(program_file)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
from .errors import CPUSimulatorError
|
|
230
|
+
title = ("Disasm Error" if isinstance(e, CPUSimulatorError)
|
|
231
|
+
else "Error")
|
|
232
|
+
console.print(Panel(str(e), title=title, border_style='red'))
|
|
233
|
+
return 1
|
|
234
|
+
sys.stdout.write("\n".join(lines))
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
if ns.build_exe is not None:
|
|
238
|
+
return _run_aot_build(ns, console, program_file)
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
from .cpu import CPU
|
|
242
|
+
cpu = CPU(config, program_file, crom_file=crom_file)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
from .errors import CPUSimulatorError
|
|
245
|
+
if isinstance(e, CPUSimulatorError):
|
|
246
|
+
console.print(Panel(str(e), title="Load Error", border_style='red'))
|
|
247
|
+
else:
|
|
248
|
+
console.print(Panel(str(e), title="Error", border_style='red'))
|
|
249
|
+
if config.debug_mode:
|
|
250
|
+
# 超详细: rich 彩色完整堆栈
|
|
251
|
+
console.print_exception()
|
|
252
|
+
return 1
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
if config.compile_to_bin or config.compile_only:
|
|
256
|
+
from . import crom as crom_mod
|
|
257
|
+
crom_mod.save_bin(cpu, output_file, logger=cpu.logger)
|
|
258
|
+
console.print(Colors.colorize(
|
|
259
|
+
f"Compiled to {output_file}", Colors.GREEN))
|
|
260
|
+
if config.compile_only:
|
|
261
|
+
return 0
|
|
262
|
+
|
|
263
|
+
cpu.run()
|
|
264
|
+
except KeyboardInterrupt:
|
|
265
|
+
pass
|
|
266
|
+
except Exception as e:
|
|
267
|
+
from .errors import CPUSimulatorError
|
|
268
|
+
if isinstance(e, CPUSimulatorError):
|
|
269
|
+
console.print(Panel(str(e), title="Error", border_style='red'))
|
|
270
|
+
else:
|
|
271
|
+
console.print(Panel(str(e), title="Unexpected Error",
|
|
272
|
+
border_style='red'))
|
|
273
|
+
console.print_exception()
|
|
274
|
+
return 1
|
|
275
|
+
# CPU.run 内部会捕获执行期异常并记录日志, 这里据失败标志决定退出码
|
|
276
|
+
if getattr(cpu, 'execution_failed', False):
|
|
277
|
+
return 1
|
|
278
|
+
return 0
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if __name__ == '__main__':
|
|
282
|
+
sys.exit(main())
|
codecin/config.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""运行配置与命令行参数解析。"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Config:
|
|
9
|
+
mem_size: int = 64 * 1024
|
|
10
|
+
stack_size: int = 1024
|
|
11
|
+
step_mode: bool = False
|
|
12
|
+
debug_mode: bool = False
|
|
13
|
+
auto_save_crom: bool = False
|
|
14
|
+
execution_interval: float = 0.0
|
|
15
|
+
max_execution_time: float = 60.0
|
|
16
|
+
interactive_mode: bool = True
|
|
17
|
+
sandbox_mode: bool = False
|
|
18
|
+
max_instructions: int = 100_000_000
|
|
19
|
+
allow_io: bool = True
|
|
20
|
+
log_level: str = 'INFO'
|
|
21
|
+
log_file: Optional[str] = None
|
|
22
|
+
show_memory_bytes: int = 32
|
|
23
|
+
show_vector_regs: bool = True
|
|
24
|
+
show_timings: bool = True
|
|
25
|
+
strict_mode: bool = False
|
|
26
|
+
output_file: Optional[str] = None
|
|
27
|
+
optimize: int = 0
|
|
28
|
+
enable_jit: bool = False
|
|
29
|
+
use_native: bool = True # 允许使用 Go 原生库加速
|
|
30
|
+
cache_size: int = 64
|
|
31
|
+
cache_assoc: int = 4
|
|
32
|
+
profile: bool = False
|
|
33
|
+
compress_crom: bool = True
|
|
34
|
+
compile_to_bin: bool = False
|
|
35
|
+
compile_only: bool = False
|
|
36
|
+
# A2: 确定性随机种子 (None = 随机)
|
|
37
|
+
seed: Optional[int] = None
|
|
38
|
+
# A1: CIN 运行时断言/边界检查开关
|
|
39
|
+
bounds_check: bool = False
|
|
40
|
+
# B1: MMU/分页 (默认 identity 映射; unmap/protect 需测试或宿主侧设置)
|
|
41
|
+
mmu: bool = False
|
|
42
|
+
# A4: 远程调试服务端口 (None = 不启动)
|
|
43
|
+
debug_server_port: Optional[int] = None
|
|
44
|
+
|
|
45
|
+
# 命令行解析请使用 codecin/cli.py build_parser() (argparse, 单一来源)。
|
|
46
|
+
# Config 仅承载运行配置, 由 cli._apply_namespace 填充。
|
|
47
|
+
|
|
48
|
+
def validate(self) -> None:
|
|
49
|
+
if self.mem_size < 256:
|
|
50
|
+
self.mem_size = 256
|
|
51
|
+
if self.max_instructions < 1:
|
|
52
|
+
self.max_instructions = 1
|
|
53
|
+
if self.optimize < 0:
|
|
54
|
+
self.optimize = 0
|
|
55
|
+
if self.optimize > 3:
|
|
56
|
+
self.optimize = 3
|
|
57
|
+
if self.cache_size < 8:
|
|
58
|
+
self.cache_size = 8
|
codecin/console.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from rich import box as _box_mod
|
|
5
|
+
from rich.console import Console as RichConsole
|
|
6
|
+
from rich.panel import Panel as RichPanel
|
|
7
|
+
from rich.table import Table as RichTable
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
10
|
+
_ANSI_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _to_renderable(obj: Any) -> Any:
|
|
14
|
+
"""将任意对象转换为 rich 可渲染对象。
|
|
15
|
+
|
|
16
|
+
str 含 ANSI 时转 Text (rich 渲染时按终端能力自动上/去色)。
|
|
17
|
+
"""
|
|
18
|
+
if isinstance(obj, str):
|
|
19
|
+
if '\x1b' in obj:
|
|
20
|
+
return Text.from_ansi(obj)
|
|
21
|
+
return obj
|
|
22
|
+
if hasattr(obj, '__rich_console__'):
|
|
23
|
+
return obj
|
|
24
|
+
return str(obj)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Colors:
|
|
28
|
+
"""ANSI 颜色码 (历史 API, 输出层自动转换为 rich Text)。"""
|
|
29
|
+
|
|
30
|
+
RESET = '\033[0m'
|
|
31
|
+
BOLD = '\033[1m'
|
|
32
|
+
DIM = '\033[2m'
|
|
33
|
+
|
|
34
|
+
BLACK = '\033[30m'
|
|
35
|
+
RED = '\033[31m'
|
|
36
|
+
GREEN = '\033[32m'
|
|
37
|
+
YELLOW = '\033[33m'
|
|
38
|
+
BLUE = '\033[34m'
|
|
39
|
+
MAGENTA = '\033[35m'
|
|
40
|
+
CYAN = '\033[36m'
|
|
41
|
+
WHITE = '\033[37m'
|
|
42
|
+
|
|
43
|
+
BG_BLACK = '\033[40m'
|
|
44
|
+
BG_RED = '\033[41m'
|
|
45
|
+
BG_GREEN = '\033[42m'
|
|
46
|
+
BG_YELLOW = '\033[43m'
|
|
47
|
+
BG_BLUE = '\033[44m'
|
|
48
|
+
BG_MAGENTA = '\033[45m'
|
|
49
|
+
BG_CYAN = '\033[46m'
|
|
50
|
+
BG_WHITE = '\033[47m'
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def strip(text: str) -> str:
|
|
54
|
+
return _ANSI_RE.sub('', text)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def colorize(text: str, color: str, bold: bool = False) -> str:
|
|
58
|
+
return f"{Colors.BOLD if bold else ''}{color}{text}{Colors.RESET}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Console:
|
|
62
|
+
"""rich 控制台封装。"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, force_color: Optional[bool] = None):
|
|
65
|
+
kwargs: dict = {}
|
|
66
|
+
if force_color is not None:
|
|
67
|
+
kwargs['force_terminal'] = force_color
|
|
68
|
+
kwargs['color_system'] = 'truecolor' if force_color else None
|
|
69
|
+
self._rc = RichConsole(
|
|
70
|
+
highlight=False, emoji=False, markup=False,
|
|
71
|
+
soft_wrap=False, **kwargs)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def rich(self) -> RichConsole:
|
|
75
|
+
"""底层 rich Console (供 logger/异常渲染复用)。"""
|
|
76
|
+
return self._rc
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def color_support(self) -> bool:
|
|
80
|
+
return self._rc.color_system is not None
|
|
81
|
+
|
|
82
|
+
def width(self) -> int:
|
|
83
|
+
return self._rc.width
|
|
84
|
+
|
|
85
|
+
def print(self, *args, **kwargs) -> None:
|
|
86
|
+
objs = [_to_renderable(a) for a in args]
|
|
87
|
+
self._rc.print(*objs, sep=kwargs.pop('sep', ' '),
|
|
88
|
+
markup=False, highlight=False, **kwargs)
|
|
89
|
+
|
|
90
|
+
def print_exception(self, **kwargs) -> None:
|
|
91
|
+
"""rich 彩色堆栈回溯。"""
|
|
92
|
+
self._rc.print_exception(word_wrap=True, **kwargs)
|
|
93
|
+
|
|
94
|
+
def clear(self) -> None:
|
|
95
|
+
self._rc.clear()
|
|
96
|
+
|
|
97
|
+
def rule(self, title: str = "", style: str = "cyan") -> None:
|
|
98
|
+
self._rc.rule(Text.from_ansi(title) if '\x1b' in title else title,
|
|
99
|
+
style=style, align='center')
|
|
100
|
+
|
|
101
|
+
def clear_line(self) -> None:
|
|
102
|
+
self._rc.print('\r' + ' ' * 80 + '\r', end='')
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Panel:
|
|
106
|
+
"""rich Panel 适配器, 兼容 str(Panel(...)) 用法。"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, content, title: str = "", border_style: str = "",
|
|
109
|
+
box=None):
|
|
110
|
+
self._content = content
|
|
111
|
+
self._title = title
|
|
112
|
+
self._border_style = border_style or 'cyan'
|
|
113
|
+
self._box = box or _box_mod.ROUNDED
|
|
114
|
+
|
|
115
|
+
def build(self) -> RichPanel:
|
|
116
|
+
content = _to_renderable(self._content)
|
|
117
|
+
title = Text.from_ansi(self._title) if '\x1b' in self._title \
|
|
118
|
+
else self._title
|
|
119
|
+
return RichPanel(content, title=title, border_style=self._border_style,
|
|
120
|
+
box=self._box, expand=False)
|
|
121
|
+
|
|
122
|
+
def __rich_console__(self, console, options):
|
|
123
|
+
return self.build().__rich_console__(console, options)
|
|
124
|
+
|
|
125
|
+
def __str__(self) -> str:
|
|
126
|
+
import io
|
|
127
|
+
buf = io.StringIO()
|
|
128
|
+
c = RichConsole(file=buf, width=80, highlight=False, emoji=False,
|
|
129
|
+
markup=False)
|
|
130
|
+
c.print(self.build())
|
|
131
|
+
return buf.getvalue().rstrip('\n')
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Table:
|
|
135
|
+
"""rich Table 适配器, 兼容旧 add_column/add_row API。
|
|
136
|
+
|
|
137
|
+
print(table) 时按 rich 全彩渲染; str(table) 输出纯文本。
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(self, title: str = "", box=None, border_style: str = "",
|
|
141
|
+
show_lines: bool = False):
|
|
142
|
+
self._title = title
|
|
143
|
+
self._box = box or _box_mod.ROUNDED
|
|
144
|
+
self._border_style = border_style or 'cyan'
|
|
145
|
+
self._show_lines = show_lines
|
|
146
|
+
self._cols: list = []
|
|
147
|
+
self._rows: list = []
|
|
148
|
+
|
|
149
|
+
def add_column(self, name, style: str = "", width: int = 0, **kw) -> None:
|
|
150
|
+
self._cols.append((str(name), style or None, width or None, kw))
|
|
151
|
+
|
|
152
|
+
def add_row(self, *args) -> None:
|
|
153
|
+
self._rows.append([_to_renderable(a) if isinstance(a, str) else a
|
|
154
|
+
for a in args])
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def rows(self) -> list:
|
|
158
|
+
return self._rows
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def headers(self) -> list:
|
|
162
|
+
return [name for name, _, _, _ in self._cols]
|
|
163
|
+
|
|
164
|
+
def build(self) -> RichTable:
|
|
165
|
+
t = RichTable(title=self._title or None, box=self._box,
|
|
166
|
+
border_style=self._border_style,
|
|
167
|
+
show_lines=self._show_lines, highlight=False)
|
|
168
|
+
for name, style, width, kw in self._cols:
|
|
169
|
+
t.add_column(name, style=style, width=width, **kw)
|
|
170
|
+
for row in self._rows:
|
|
171
|
+
t.add_row(*row)
|
|
172
|
+
return t
|
|
173
|
+
|
|
174
|
+
def __rich_console__(self, console, options):
|
|
175
|
+
return self.build().__rich_console__(console, options)
|
|
176
|
+
|
|
177
|
+
def __str__(self) -> str:
|
|
178
|
+
import io
|
|
179
|
+
buf = io.StringIO()
|
|
180
|
+
c = RichConsole(file=buf, width=80, highlight=False, emoji=False,
|
|
181
|
+
markup=False)
|
|
182
|
+
c.print(self.build())
|
|
183
|
+
return buf.getvalue().rstrip('\n')
|