codecin 5.6.0__tar.gz → 5.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. {codecin-5.6.0 → codecin-5.7.4}/CHANGELOG.md +62 -0
  2. {codecin-5.6.0/codecin.egg-info → codecin-5.7.4}/PKG-INFO +1 -1
  3. {codecin-5.6.0 → codecin-5.7.4}/codecin/__init__.py +1 -1
  4. {codecin-5.6.0 → codecin-5.7.4}/codecin/aot.py +182 -2
  5. {codecin-5.6.0 → codecin-5.7.4}/codecin/cin.py +665 -44
  6. {codecin-5.6.0 → codecin-5.7.4}/codecin/cli.py +20 -3
  7. {codecin-5.6.0 → codecin-5.7.4}/codecin/cpu.py +8 -6
  8. codecin-5.7.4/codecin/lib/bigint.cin +589 -0
  9. codecin-5.7.4/codecin/lib/bitset.cin +374 -0
  10. codecin-5.7.4/codecin/lib/codec.cin +563 -0
  11. codecin-5.7.4/codecin/lib/combin.cin +414 -0
  12. codecin-5.7.4/codecin/lib/csv.cin +405 -0
  13. codecin-5.7.4/codecin/lib/dp.cin +325 -0
  14. codecin-5.7.4/codecin/lib/fmt.cin +325 -0
  15. codecin-5.7.4/codecin/lib/frac.cin +252 -0
  16. codecin-5.7.4/codecin/lib/graph.cin +367 -0
  17. codecin-5.7.4/codecin/lib/heap.cin +199 -0
  18. codecin-5.7.4/codecin/lib/path.cin +372 -0
  19. codecin-5.7.4/codecin/lib/set.cin +269 -0
  20. codecin-5.7.4/codecin/lib/text.cin +323 -0
  21. codecin-5.7.4/codecin/lib/token.cin +183 -0
  22. codecin-5.7.4/codecin/lib/tree.cin +256 -0
  23. codecin-5.7.4/codecin/lib/unionfind.cin +115 -0
  24. {codecin-5.6.0 → codecin-5.7.4}/codecin/memory.py +17 -0
  25. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/compiler/tokenizer.go +1 -1
  26. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/compiler/types.go +0 -1
  27. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/version_gen.go +1 -1
  28. codecin-5.7.4/codecin/version.py +274 -0
  29. {codecin-5.6.0 → codecin-5.7.4/codecin.egg-info}/PKG-INFO +1 -1
  30. {codecin-5.6.0 → codecin-5.7.4}/codecin.egg-info/SOURCES.txt +44 -0
  31. codecin-5.7.4/misc/vscode-cin/.vscodeignore +4 -0
  32. codecin-5.7.4/misc/vscode-cin/README.md +34 -0
  33. codecin-5.7.4/misc/vscode-cin/build.bat +3 -0
  34. codecin-5.7.4/misc/vscode-cin/language-configuration.json +87 -0
  35. codecin-5.7.4/misc/vscode-cin/package.json +52 -0
  36. codecin-5.7.4/misc/vscode-cin/snippets/cin.json +113 -0
  37. codecin-5.7.4/misc/vscode-cin/syntaxes/cin.tmLanguage.json +254 -0
  38. {codecin-5.6.0 → codecin-5.7.4}/pyproject.toml +3 -0
  39. codecin-5.7.4/script/bump_version.py +349 -0
  40. codecin-5.7.4/tests/test_aggregate_and_const.py +377 -0
  41. codecin-5.7.4/tests/test_all_libs_together.py +104 -0
  42. codecin-5.7.4/tests/test_aot.py +433 -0
  43. {codecin-5.6.0 → codecin-5.7.4}/tests/test_compiler_errors.py +8 -7
  44. {codecin-5.6.0 → codecin-5.7.4}/tests/test_keyboard.py +8 -4
  45. codecin-5.7.4/tests/test_lib_bigint.py +322 -0
  46. codecin-5.7.4/tests/test_lib_bitset.py +472 -0
  47. codecin-5.7.4/tests/test_lib_codec.py +164 -0
  48. codecin-5.7.4/tests/test_lib_combin.py +276 -0
  49. codecin-5.7.4/tests/test_lib_csv.py +241 -0
  50. codecin-5.7.4/tests/test_lib_dp.py +256 -0
  51. codecin-5.7.4/tests/test_lib_fmt.py +230 -0
  52. codecin-5.7.4/tests/test_lib_frac.py +201 -0
  53. codecin-5.7.4/tests/test_lib_graph.py +353 -0
  54. codecin-5.7.4/tests/test_lib_heap.py +234 -0
  55. codecin-5.7.4/tests/test_lib_path.py +250 -0
  56. codecin-5.7.4/tests/test_lib_set.py +294 -0
  57. codecin-5.7.4/tests/test_lib_text.py +246 -0
  58. codecin-5.7.4/tests/test_lib_token.py +158 -0
  59. codecin-5.7.4/tests/test_lib_tree.py +130 -0
  60. codecin-5.7.4/tests/test_lib_unionfind.py +150 -0
  61. {codecin-5.6.0 → codecin-5.7.4}/tests/test_p0_fixes.py +14 -8
  62. codecin-5.7.4/tests/test_struct_value_semantics.py +440 -0
  63. codecin-5.7.4/tests/test_version.py +424 -0
  64. codecin-5.6.0/tests/test_aot.py +0 -206
  65. codecin-5.6.0/tests/test_version.py +0 -58
  66. {codecin-5.6.0 → codecin-5.7.4}/LICENSE +0 -0
  67. {codecin-5.6.0 → codecin-5.7.4}/MANIFEST.in +0 -0
  68. {codecin-5.6.0 → codecin-5.7.4}/README.md +0 -0
  69. {codecin-5.6.0 → codecin-5.7.4}/codecin/assembler.py +0 -0
  70. {codecin-5.6.0 → codecin-5.7.4}/codecin/cache.py +0 -0
  71. {codecin-5.6.0 → codecin-5.7.4}/codecin/config.py +0 -0
  72. {codecin-5.6.0 → codecin-5.7.4}/codecin/console.py +0 -0
  73. {codecin-5.6.0 → codecin-5.7.4}/codecin/crom.py +0 -0
  74. {codecin-5.6.0 → codecin-5.7.4}/codecin/debugger.py +0 -0
  75. {codecin-5.6.0 → codecin-5.7.4}/codecin/disasm.py +0 -0
  76. {codecin-5.6.0 → codecin-5.7.4}/codecin/errors.py +0 -0
  77. {codecin-5.6.0 → codecin-5.7.4}/codecin/isa.py +0 -0
  78. {codecin-5.6.0 → codecin-5.7.4}/codecin/jit.py +0 -0
  79. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/array.cin +0 -0
  80. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/bits.cin +0 -0
  81. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/conv.cin +0 -0
  82. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/gui.cin +0 -0
  83. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/hash.cin +0 -0
  84. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/io.cin +0 -0
  85. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/json.cin +0 -0
  86. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/key.cin +0 -0
  87. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/math.cin +0 -0
  88. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/matrix.cin +0 -0
  89. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/queue.cin +0 -0
  90. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/rand.cin +0 -0
  91. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/sort.cin +0 -0
  92. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/stat.cin +0 -0
  93. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/str.cin +0 -0
  94. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/termux.cin +0 -0
  95. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/test.cin +0 -0
  96. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/time.cin +0 -0
  97. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/validate.cin +0 -0
  98. {codecin-5.6.0 → codecin-5.7.4}/codecin/lib/vec.cin +0 -0
  99. {codecin-5.6.0 → codecin-5.7.4}/codecin/logger.py +0 -0
  100. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/aot/aot.go +0 -0
  101. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/aot/stub_main.go.txt +0 -0
  102. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/build.ps1 +0 -0
  103. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/build.sh +0 -0
  104. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/compiler/codegen.go +0 -0
  105. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/compiler/compile_test.go +0 -0
  106. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/compiler/parser.go +0 -0
  107. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/compiler/syscalls.go +0 -0
  108. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/audio.go +0 -0
  109. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/audio_other.go +0 -0
  110. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/audio_windows.go +0 -0
  111. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/canvas.go +0 -0
  112. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/crom.go +0 -0
  113. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/encode.go +0 -0
  114. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/engine_test.go +0 -0
  115. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/host_test.go +0 -0
  116. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/hostcrypto.go +0 -0
  117. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/hostdesktop.go +0 -0
  118. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/hostnet.go +0 -0
  119. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/isa_gen.go +0 -0
  120. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard.go +0 -0
  121. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard_other.go +0 -0
  122. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard_stub.go +0 -0
  123. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard_termios_darwin.go +0 -0
  124. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard_termios_linux.go +0 -0
  125. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard_test.go +0 -0
  126. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/keyboard_windows.go +0 -0
  127. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/mem_other.go +0 -0
  128. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/mem_windows.go +0 -0
  129. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/system.go +0 -0
  130. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/termux.go +0 -0
  131. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/engine/vm.go +0 -0
  132. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/go.mod +0 -0
  133. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/ir/ir.go +0 -0
  134. {codecin-5.6.0 → codecin-5.7.4}/codecin/native/main.go +0 -0
  135. {codecin-5.6.0 → codecin-5.7.4}/codecin/native.py +0 -0
  136. {codecin-5.6.0 → codecin-5.7.4}/codecin/registers.py +0 -0
  137. {codecin-5.6.0 → codecin-5.7.4}/codecin/stats.py +0 -0
  138. {codecin-5.6.0 → codecin-5.7.4}/codecin.egg-info/dependency_links.txt +0 -0
  139. {codecin-5.6.0 → codecin-5.7.4}/codecin.egg-info/entry_points.txt +0 -0
  140. {codecin-5.6.0 → codecin-5.7.4}/codecin.egg-info/requires.txt +0 -0
  141. {codecin-5.6.0 → codecin-5.7.4}/codecin.egg-info/top_level.txt +0 -0
  142. {codecin-5.6.0 → codecin-5.7.4}/examples/asm_constants.asm +0 -0
  143. {codecin-5.6.0 → codecin-5.7.4}/examples/bitwise_builtins.cin +0 -0
  144. {codecin-5.6.0 → codecin-5.7.4}/examples/control_flow.cin +0 -0
  145. {codecin-5.6.0 → codecin-5.7.4}/examples/enum_range_for.cin +0 -0
  146. {codecin-5.6.0 → codecin-5.7.4}/examples/literals_types.cin +0 -0
  147. {codecin-5.6.0 → codecin-5.7.4}/examples/modules_demo.cin +0 -0
  148. {codecin-5.6.0 → codecin-5.7.4}/examples/stdlib_demo.cin +0 -0
  149. {codecin-5.6.0 → codecin-5.7.4}/examples/system_interaction.cin +0 -0
  150. {codecin-5.6.0 → codecin-5.7.4}/misc/vim/ftdetect/codecin.vim +0 -0
  151. {codecin-5.6.0 → codecin-5.7.4}/misc/vim/syntax/cin.vim +0 -0
  152. {codecin-5.6.0 → codecin-5.7.4}/misc/vim/syntax/codecinasm.vim +0 -0
  153. {codecin-5.6.0 → codecin-5.7.4}/script/check_paths.py +0 -0
  154. {codecin-5.6.0 → codecin-5.7.4}/script/gen_isa_docs.py +0 -0
  155. {codecin-5.6.0 → codecin-5.7.4}/script/gen_native_isa.py +0 -0
  156. {codecin-5.6.0 → codecin-5.7.4}/setup.cfg +0 -0
  157. {codecin-5.6.0 → codecin-5.7.4}/setup.py +0 -0
  158. {codecin-5.6.0 → codecin-5.7.4}/tests/test_assembler_ext.py +0 -0
  159. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cache.py +0 -0
  160. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cin_host.py +0 -0
  161. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cin_host_ext.py +0 -0
  162. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cin_new_features.py +0 -0
  163. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cin_syntax.py +0 -0
  164. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cin_syntax_ext.py +0 -0
  165. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cin_system.py +0 -0
  166. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cli.py +0 -0
  167. {codecin-5.6.0 → codecin-5.7.4}/tests/test_cpu_interpreted.py +0 -0
  168. {codecin-5.6.0 → codecin-5.7.4}/tests/test_debugger.py +0 -0
  169. {codecin-5.6.0 → codecin-5.7.4}/tests/test_examples.py +0 -0
  170. {codecin-5.6.0 → codecin-5.7.4}/tests/test_features_ab.py +0 -0
  171. {codecin-5.6.0 → codecin-5.7.4}/tests/test_features_crom_mmu.py +0 -0
  172. {codecin-5.6.0 → codecin-5.7.4}/tests/test_features_import.py +0 -0
  173. {codecin-5.6.0 → codecin-5.7.4}/tests/test_features_importloc.py +0 -0
  174. {codecin-5.6.0 → codecin-5.7.4}/tests/test_features_mmu.py +0 -0
  175. {codecin-5.6.0 → codecin-5.7.4}/tests/test_features_remote.py +0 -0
  176. {codecin-5.6.0 → codecin-5.7.4}/tests/test_index_subscript_type.py +0 -0
  177. {codecin-5.6.0 → codecin-5.7.4}/tests/test_isa_dispatch.py +0 -0
  178. {codecin-5.6.0 → codecin-5.7.4}/tests/test_isa_single_source.py +0 -0
  179. {codecin-5.6.0 → codecin-5.7.4}/tests/test_libs.py +0 -0
  180. {codecin-5.6.0 → codecin-5.7.4}/tests/test_libs_ext.py +0 -0
  181. {codecin-5.6.0 → codecin-5.7.4}/tests/test_literals_and_bom.py +0 -0
  182. {codecin-5.6.0 → codecin-5.7.4}/tests/test_memory_protection.py +0 -0
  183. {codecin-5.6.0 → codecin-5.7.4}/tests/test_native_hardening.py +0 -0
  184. {codecin-5.6.0 → codecin-5.7.4}/tests/test_native_lib_lookup.py +0 -0
  185. {codecin-5.6.0 → codecin-5.7.4}/tests/test_nested_subscript_codegen.py +0 -0
  186. {codecin-5.6.0 → codecin-5.7.4}/tests/test_no_go_cli.py +0 -0
  187. {codecin-5.6.0 → codecin-5.7.4}/tests/test_packaging.py +0 -0
  188. {codecin-5.6.0 → codecin-5.7.4}/tests/test_paths_consistency.py +0 -0
  189. {codecin-5.6.0 → codecin-5.7.4}/tests/test_switch_semantics.py +0 -0
  190. {codecin-5.6.0 → codecin-5.7.4}/tests/test_three_paths.py +0 -0
  191. {codecin-5.6.0 → codecin-5.7.4}/tests/test_workflows.py +0 -0
@@ -5,6 +5,68 @@
5
5
 
6
6
  ---
7
7
 
8
+ ## [5.7.4] - 2026-09-28
9
+
10
+ 一次「静默错误清零 + 标准库扩容」的发布:修掉一族会**静默破坏内存/静默给错结果**的
11
+ struct 缺陷与两处**同一程序两条路径结论不同**的分歧,并把官方标准库从 20 个扩到 36 个。
12
+
13
+ ### 新增 (Added)
14
+
15
+ - **标准库 20 → 36 个**(全部为纯 CIN,只调用语言内建,三条执行路径一致):
16
+ - 数据结构与算法:`tree.cin`(`tree_`)、`unionfind.cin`(`uf_`)、`heap.cin`(`heap_`)、
17
+ `graph.cin`(`graph_`)、`dp.cin`(`dp_`)、`set.cin`(`set_`)、`bitset.cin`(`bs_`)
18
+ - 文本与格式化:`text.cin`(`txt_`)、`token.cin`(`tok_`)、`fmt.cin`(`fmt_`)、`csv.cin`(`csv_`)
19
+ - 数值与科学计算:`bigint.cin`(`bi_`,任意精度)、`frac.cin`(`fr_`,有理数)、
20
+ `combin.cin`(`comb_`,数论与组合)
21
+ - 工程与系统:`path.cin`(`path_`,纯字符串路径)、`codec.cin`(`codec_`,十六进制/URL/
22
+ Base64/RLE/摩尔斯/XOR)
23
+ - **`const` 命名常量**:`const int N = 4`(全局与局部),支持 `int/float/bool/string`,
24
+ 纯编译期、不占数据段,可用于**数组维度**与任意表达式;可被同名局部变量遮蔽。
25
+ - **数组维度接受整型常量表达式**:`int a[N]`、`int a[2 + 3]`、`int a[enumMember + 1]`。
26
+ - **struct 聚合初始化**:`P p = {3, 4}`、`P ps[2] = {{1,2},{3,4}}`、嵌套 `Out o = {{5,6},7}`;
27
+ 未列出的字段取默认值,初始化器过多是编译期错误。
28
+ - **`string +=`**:右侧任意可字符串化的值。
29
+ - **`exit(code)`**:立即结束程序,三条路径与 AOT 产物一致。
30
+ - **版本设施**:`codecin/version.py`(`version_info()` / `compare()` / `build_info()` 等,
31
+ `build_info()` 永不抛异常)、CLI 新增 `--build-info` 与 `--build-info --json`、
32
+ `script/bump_version.py`(改真源 → 重生成 Go 版本常量 → 插 CHANGELOG 骨架,含回滚与 `--dry-run`)。
33
+
34
+ ### 修复 (Fixed)
35
+
36
+ - **struct 聚合初始化写穿指针槽**:`P p = {3, 4}` 会把第 2 个字面量写到 `FP + 0`,
37
+ **覆盖保存的帧指针**;全局 `P o = {3,4}` 覆盖对象指针。现按字段写入堆对象。
38
+ - **struct 数组成员访问写到绝对地址 0 / 8**:元素槽被当成指针解引用,
39
+ `ps[0].y = 1` 会**静默破坏数据段**(字符串字面量所在处)。现统一为
40
+ 「槽里存对象指针」模型,每个元素各有对象。
41
+ - **初始化器过多时两条路径结论不同**:解释器越界写后返回残值、原生 VM 抛
42
+ `Address out of bounds`。现为编译期错误 `Too many initializers for struct P`。
43
+ - **struct 整体赋值是指针别名**:`P b = a; b.x = 7` 会改到 `a.x`,与文档承诺的
44
+ 「值拷贝」不符。现按槽逐字拷贝(**传参仍保持文档规定的「引用可见」**)。
45
+ - **嵌套 struct 字段只按 1 槽推进**:`struct Out { In i; int c }` 的 `c` 压在 `i.b` 上;
46
+ 且成员取值会把内嵌字段当指针解引用。两处均已修正。
47
+ - **`parse_array_literal` 静默丢弃尾部平铺元素**:`{{5, 6}, 7}` 里的 `7` 消失。
48
+ - **字符串字节语义分歧**:解释器侧 `+` / `substr` / `indexof` 先做 UTF-8 解码,
49
+ 非法字节序列被替换成 U+FFFD,实测 `strlen(substr("中",2,1)+substr("中",1,1))`
50
+ 解释器 9 / 原生 3。现全部改为字节级。
51
+ - **用户函数与内建同名时永远不会被调用**:`--no-native` 报宿主能力错误、原生路径
52
+ 静默走宿主实现(两条路径结论不同)。现用户函数优先于同名内建。
53
+ - **局部 struct 字段默认值**:`MALLOC` 并不清零,此前「全零」只是堆恰好干净;
54
+ 现于函数序言显式清零。
55
+ - **AOT 临时目录**:清理失败不再静默(记 warning 并给出残留绝对路径与手动删除命令);
56
+ 每次构建前清扫超过 6 小时的 `.aotbuild-*` / `.aotprobe-*`;
57
+ 新增 `[tool.setuptools.exclude-package-data]` 使残留不再进 wheel。
58
+
59
+ ### 变更 (Changed)
60
+
61
+ - **无法自洽的 struct 写法改为编译期报错**(原先静默算错):多维 struct 数组
62
+ `P ps[2][3]`、struct 数组字段 `struct Bag { P items[3] }`、跨 struct 类型赋值
63
+ `P p; Q q; p = q;`、把 struct 写进标量槽 `int a[2]; a[0] = p;`。
64
+ - `tests/test_keyboard.py`、`tests/test_p0_fixes.py` 由 `tmp_path` 改用 `workdir` 夹具,
65
+ 受限沙箱下不再出现 5 个 setup 阶段 `ERROR`。
66
+ - 新增跨库集成护栏 `tests/test_all_libs_together.py`:31 个纯 CIN 库同时 import 并各调用一个接口。
67
+
68
+ ---
69
+
8
70
  ## [5.6.0] - 2026-09-27
9
71
 
10
72
  一次以「静默错误清零 + 安全加固」为主的修复发布,落地 `docs/SUGGESTIONS_NEXT.md`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecin
3
- Version: 5.6.0
3
+ Version: 5.7.4
4
4
  Summary: Code CIN - 高级语言与运行时 (CIN/PL/ASM 工具链 + Go 原生 VM + ARM64/RISC-V/FP/Vector 指令集)
5
5
  Author: ByUsi Studio, 北啊呢
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- __version__ = "5.6.0"
1
+ __version__ = "5.7.4"
2
2
  __author__ = "ByUsi Studio"
3
3
 
4
4
  from .config import Config
@@ -9,18 +9,40 @@
9
9
  `codecin/native/aot` 共用同一份文件 (``stub_main.go.txt``), 避免两处漂移。
10
10
  临时包建在 `codecin-native` 模块内的 ``.aotbuild-*`` 目录: Go 工具链会忽略
11
11
  以 '.' 开头的目录, 因此既不影响 `go build ./...`, 也不需要 replace 指令。
12
+
13
+ 临时目录的生命周期 (加固, 见 docs/SUGGESTIONS_NEXT.md §3.3):
14
+ * 每次 :func:`build` 之前先调用 :func:`sweep_stale_build_dirs` 清理**陈旧**残留,
15
+ 避免受限环境 (沙箱 / 只读 ACL) 下删不掉的目录无限累积;
16
+ * ``finally`` 里的 ``rmtree`` 失败**不再静默**: 有 logger 就 ``warning``,
17
+ 没有 logger 时退回模块级 stdlib logger (``codecin.aot``), 绝不吞掉;
18
+ * 只清理 ``codecin/native/`` **本目录下**、名字带 ``.aotbuild-`` /
19
+ ``.aotprobe-`` 前缀、且**不是符号链接**的目录, 绝不递归删除别处。
12
20
  """
13
21
 
22
+ import logging
14
23
  import os
15
24
  import platform
16
25
  import secrets
17
26
  import shutil
18
27
  import subprocess
19
- from typing import List, Optional
28
+ import time
29
+ from typing import List, NamedTuple, Optional
20
30
 
21
31
  #: 仓库内 codecin-native 模块目录 (含 go.mod)。
22
32
  _NATIVE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'native')
23
33
 
34
+ #: 模块级 logger: 调用方没有传 logger 时的兜底 (绝不能因为"没人接"就静默)。
35
+ #: 它是 ``codecin`` logger 的子 logger, 因此 CLI 构造的 Logger (RichHandler) 会
36
+ #: 照常渲染它; 完全没配置 logging 时 stdlib 的 lastResort 也会打到 stderr。
37
+ _LOGGER = logging.getLogger(__name__)
38
+
39
+ #: AOT 临时目录名前缀 (``.aotprobe-`` 是历史遗留, 一并纳入守卫)。
40
+ TEMP_DIR_PREFIXES = ('.aotbuild-', '.aotprobe-')
41
+
42
+ #: 陈旧残留的默认容忍时长 (6 小时): 远大于任何一次 go build, 因此不会误删
43
+ #: 并发构建中的临时目录。
44
+ DEFAULT_STALE_AGE_SECONDS = 6 * 3600
45
+
24
46
  #: 生成的 main.go 模板 (与 Go 侧共用)。
25
47
  STUB_PATH = os.path.join(_NATIVE_DIR, 'aot', 'stub_main.go.txt')
26
48
 
@@ -130,6 +152,156 @@ def _run_go_build(cmd, mod, env, logger):
130
152
  return proc, detail
131
153
 
132
154
 
155
+ class SweepResult(NamedTuple):
156
+ """:func:`sweep_stale_build_dirs` 的结果 (两个绝对路径列表)。"""
157
+
158
+ #: 成功删除的目录
159
+ removed: List[str]
160
+ #: 判定为残留但删除/读取失败的目录 (调用方应提示用户手动处理)
161
+ failed: List[str]
162
+
163
+
164
+ def _warn(logger, message: str) -> None:
165
+ """记一条 warning; 没有 logger 时退回模块级 stdlib logger (绝不静默)。
166
+
167
+ 日志本身出问题不能掩盖构建结果 (本函数常在 ``finally`` 里被调用),
168
+ 因此这里再包一层兜底。
169
+ """
170
+ try:
171
+ if logger is not None:
172
+ logger.warning(message)
173
+ else:
174
+ _LOGGER.warning(message)
175
+ except Exception: # noqa: BLE001 - 日志失败不得影响构建
176
+ pass
177
+
178
+
179
+ def _debug(logger, message: str) -> None:
180
+ """记一条 debug (同样带兜底)。"""
181
+ try:
182
+ if logger is not None:
183
+ logger.debug(message)
184
+ else:
185
+ _LOGGER.debug(message)
186
+ except Exception: # noqa: BLE001
187
+ pass
188
+
189
+
190
+ def _manual_removal_hint(path: str) -> str:
191
+ """给用户的「怎么手动删掉它」提示 (Windows + POSIX 各一条)。"""
192
+ return (f'请手动删除该目录 —— Windows PowerShell: '
193
+ f'Remove-Item -LiteralPath "{path}" -Recurse -Force ; '
194
+ f'Linux/macOS: rm -rf "{path}"')
195
+
196
+
197
+ def _is_temp_dir_name(name: str) -> bool:
198
+ """名字是否是本模块自己产生的临时目录名 (只认前缀, 不做任何路径解析)。
199
+
200
+ 显式拒绝 ``.``/``..``、含路径分隔符的名字以及绝对路径 —— 这些都不可能是
201
+ ``secrets.token_hex`` 的产物, 一旦出现就说明有人往我们手里塞了别的东西。
202
+ """
203
+ if not name or name in ('.', '..'):
204
+ return False
205
+ if name != os.path.basename(name): # 含 / 或 \ -> 不是直接子项
206
+ return False
207
+ if os.sep in name or (os.altsep and os.altsep in name):
208
+ return False
209
+ if os.path.isabs(name):
210
+ return False
211
+ return name.startswith(TEMP_DIR_PREFIXES)
212
+
213
+
214
+ def sweep_stale_build_dirs(max_age_seconds: float = DEFAULT_STALE_AGE_SECONDS,
215
+ logger=None) -> SweepResult:
216
+ """删除 ``codecin/native/`` 下**陈旧**的 AOT 临时构建目录。
217
+
218
+ 只处理 ``codecin/native/`` 的**直接子目录** (不递归), 名字必须带
219
+ ``.aotbuild-`` / ``.aotprobe-`` 前缀, 且 mtime 早于 ``max_age_seconds``;
220
+ 符号链接与普通文件一律跳过。任何失败都不会抛异常, 只记录日志并把路径
221
+ 放进 :attr:`SweepResult.failed`。
222
+
223
+ :param max_age_seconds: 容忍时长 (默认 6 小时)。``0`` 表示删除所有匹配目录
224
+ —— 并发构建时不要这么做。
225
+ :param logger: 可选 logger; 缺省时用模块级 stdlib logger。
226
+ :return: 被删除的路径与清理失败的路径
227
+ """
228
+ removed: List[str] = []
229
+ failed: List[str] = []
230
+
231
+ root = os.path.abspath(_NATIVE_DIR)
232
+ try:
233
+ names = os.listdir(root)
234
+ except OSError as e: # 目录不存在 / 不可读: 尽力而为
235
+ _warn(logger, f'AOT: 无法扫描临时目录所在位置 {root} ({e}); 跳过清理')
236
+ return SweepResult(removed, failed)
237
+
238
+ try:
239
+ max_age = max(0.0, float(max_age_seconds))
240
+ except (TypeError, ValueError):
241
+ max_age = float(DEFAULT_STALE_AGE_SECONDS)
242
+
243
+ now = time.time()
244
+ for name in names:
245
+ if not _is_temp_dir_name(name):
246
+ continue
247
+ path = os.path.join(root, name)
248
+ # 双保险: 解析后必须仍然是 root 的直接子项 (防 '..' / 绝对路径混入)
249
+ if os.path.dirname(os.path.abspath(path)) != root:
250
+ _warn(logger, f'AOT: 跳过可疑的临时目录路径: {path}')
251
+ continue
252
+ if os.path.islink(path) or not os.path.isdir(path):
253
+ _debug(logger, f'AOT: 跳过非普通目录的临时项: {path}')
254
+ continue
255
+
256
+ try:
257
+ age = now - os.path.getmtime(path)
258
+ except OSError as e:
259
+ failed.append(path)
260
+ _warn(logger, f'AOT: 无法读取残留临时目录的时间戳: {path} ({e}); '
261
+ f'{_manual_removal_hint(path)}')
262
+ continue
263
+
264
+ if age < max_age:
265
+ _debug(logger, f'AOT: 临时目录仍然新鲜, 保留: {path} '
266
+ f'({age / 3600:.1f} 小时前)')
267
+ continue
268
+
269
+ try:
270
+ shutil.rmtree(path)
271
+ except OSError as e:
272
+ failed.append(path)
273
+ _warn(logger, f'AOT: 残留临时目录清理失败: {path} '
274
+ f'({age / 3600:.1f} 小时前, {e}); '
275
+ f'{_manual_removal_hint(path)}')
276
+ continue
277
+
278
+ removed.append(path)
279
+ _debug(logger, f'AOT: 已清理陈旧临时目录: {path} '
280
+ f'({age / 3600:.1f} 小时前)')
281
+
282
+ if removed:
283
+ _debug(logger, f'AOT: 陈旧临时目录清理完成, 共 {len(removed)} 个')
284
+ return SweepResult(removed, failed)
285
+
286
+
287
+ def _remove_temp_dir(tmp: str, logger) -> None:
288
+ """删除本次构建的临时目录; 失败时告警而不是静默吞掉 (§3.3)。
289
+
290
+ ``shutil.rmtree(ignore_errors=True)`` 会让「删不掉」完全静默, 于是残留
291
+ 在受限环境 (沙箱 / 异常 ACL) 里不断累积, 最终让任何遍历仓库的工具报
292
+ Access is denied。这里改为: 失败 -> warning (含绝对路径与手动删除提示)。
293
+ """
294
+ try:
295
+ shutil.rmtree(tmp)
296
+ except FileNotFoundError:
297
+ pass # 目录已不存在: 目的已达成
298
+ except Exception as e: # noqa: BLE001 - rmtree 正常只抛 OSError,
299
+ # 但这里处于 finally, 任何异常都不能掩盖构建结果, 一律降级为 warning。
300
+ _warn(logger, f'AOT: 临时构建目录清理失败, 残留目录: '
301
+ f'{os.path.abspath(tmp)} (原因: {e}); '
302
+ f'{_manual_removal_hint(tmp)}')
303
+
304
+
133
305
  def build(bytecode: bytes,
134
306
  mem_image: bytes,
135
307
  out: str,
@@ -150,6 +322,14 @@ def build(bytecode: bytes,
150
322
  goos, goarch = parse_target(target) if target else parse_target(host_target())
151
323
  mod = module_dir()
152
324
 
325
+ # 先清掉上一次留下的陈旧残留 (默认 > 6 小时), 再建新目录: 受限环境下
326
+ # rmtree 可能失败, 没有这一步残留就会无限累积。
327
+ # 清理是尽力而为 —— 任何异常都必须吞掉, 绝不能因此让构建失败。
328
+ try:
329
+ sweep_stale_build_dirs(logger=logger)
330
+ except Exception as e: # noqa: BLE001
331
+ _debug(logger, f'AOT: 陈旧临时目录清理跳过 ({e})')
332
+
153
333
  # 注意: 这里用 os.makedirs 而不是 tempfile.mkdtemp —— mkdtemp 会创建 0700
154
334
  # 目录, 在受限环境 (沙箱 / 部分 CI 安全策略) 下随后向其中写文件会被拒绝。
155
335
  tmp = os.path.join(mod, '.aotbuild-' + secrets.token_hex(6))
@@ -199,7 +379,7 @@ def build(bytecode: bytes,
199
379
  if logger:
200
380
  logger.info(f'AOT 临时目录保留: {tmp}')
201
381
  else:
202
- shutil.rmtree(tmp, ignore_errors=True)
382
+ _remove_temp_dir(tmp, logger)
203
383
 
204
384
 
205
385
  def program_dependencies(program_file: str) -> List[str]: