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.
Files changed (54) hide show
  1. codecin/__init__.py +27 -0
  2. codecin/aot.py +229 -0
  3. codecin/assembler.py +548 -0
  4. codecin/cache.py +71 -0
  5. codecin/cin.py +2558 -0
  6. codecin/cli.py +282 -0
  7. codecin/config.py +58 -0
  8. codecin/console.py +183 -0
  9. codecin/cpu.py +1654 -0
  10. codecin/crom.py +222 -0
  11. codecin/debugger.py +659 -0
  12. codecin/disasm.py +86 -0
  13. codecin/errors.py +58 -0
  14. codecin/isa.py +457 -0
  15. codecin/jit.py +270 -0
  16. codecin/libcodecin_native.so +0 -0
  17. codecin/logger.py +145 -0
  18. codecin/memory.py +348 -0
  19. codecin/native/aot/aot.go +70 -0
  20. codecin/native/aot/build.go +219 -0
  21. codecin/native/aot/stub_main.go.txt +22 -0
  22. codecin/native/build.ps1 +33 -0
  23. codecin/native/build.sh +48 -0
  24. codecin/native/cmd/codecin/main.go +287 -0
  25. codecin/native/compiler/codegen.go +1938 -0
  26. codecin/native/compiler/compile_test.go +164 -0
  27. codecin/native/compiler/parser.go +1071 -0
  28. codecin/native/compiler/syscalls.go +86 -0
  29. codecin/native/compiler/tokenizer.go +336 -0
  30. codecin/native/compiler/types.go +213 -0
  31. codecin/native/engine/audio.go +152 -0
  32. codecin/native/engine/audio_other.go +46 -0
  33. codecin/native/engine/audio_windows.go +43 -0
  34. codecin/native/engine/canvas.go +271 -0
  35. codecin/native/engine/crom.go +86 -0
  36. codecin/native/engine/encode.go +93 -0
  37. codecin/native/engine/engine_test.go +198 -0
  38. codecin/native/engine/isa_gen.go +445 -0
  39. codecin/native/engine/system.go +190 -0
  40. codecin/native/engine/termux.go +113 -0
  41. codecin/native/engine/version_gen.go +7 -0
  42. codecin/native/engine/vm.go +1157 -0
  43. codecin/native/go.mod +3 -0
  44. codecin/native/ir/ir.go +48 -0
  45. codecin/native/main.go +225 -0
  46. codecin/native.py +336 -0
  47. codecin/registers.py +130 -0
  48. codecin/stats.py +235 -0
  49. codecin-5.4.2.dist-info/METADATA +985 -0
  50. codecin-5.4.2.dist-info/RECORD +54 -0
  51. codecin-5.4.2.dist-info/WHEEL +5 -0
  52. codecin-5.4.2.dist-info/entry_points.txt +2 -0
  53. codecin-5.4.2.dist-info/licenses/LICENSE +7 -0
  54. 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')