tpy-lang 0.3.0.dev0__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 (333) hide show
  1. tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
  2. tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
  3. tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
  4. tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
  5. tpyc/__init__.py +104 -0
  6. tpyc/__main__.py +6 -0
  7. tpyc/_buildinfo.py +1 -0
  8. tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
  9. tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
  10. tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
  11. tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
  12. tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
  13. tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
  14. tpyc/_data/lib/tpy/_functools_macros.py +80 -0
  15. tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
  16. tpyc/_data/lib/tpy/argparse.py +2062 -0
  17. tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
  18. tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
  19. tpyc/_data/lib/tpy/base64.py +410 -0
  20. tpyc/_data/lib/tpy/bisect.py +39 -0
  21. tpyc/_data/lib/tpy/builtins.py +38 -0
  22. tpyc/_data/lib/tpy/dataclasses.py +354 -0
  23. tpyc/_data/lib/tpy/enum.py +23 -0
  24. tpyc/_data/lib/tpy/functools.py +33 -0
  25. tpyc/_data/lib/tpy/hashlib.py +206 -0
  26. tpyc/_data/lib/tpy/heapq.py +118 -0
  27. tpyc/_data/lib/tpy/io.py +395 -0
  28. tpyc/_data/lib/tpy/json.py +221 -0
  29. tpyc/_data/lib/tpy/math.py +406 -0
  30. tpyc/_data/lib/tpy/random.py +597 -0
  31. tpyc/_data/lib/tpy/re.py +467 -0
  32. tpyc/_data/lib/tpy/socket.py +379 -0
  33. tpyc/_data/lib/tpy/struct.py +178 -0
  34. tpyc/_data/lib/tpy/sys.py +40 -0
  35. tpyc/_data/lib/tpy/time.py +39 -0
  36. tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
  37. tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
  38. tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
  39. tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
  40. tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
  41. tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
  42. tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
  43. tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
  44. tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
  45. tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
  46. tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
  47. tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
  48. tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
  49. tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
  50. tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
  51. tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
  52. tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
  53. tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
  54. tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
  55. tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
  56. tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
  57. tpyc/_data/lib/tpy/tpy/_version.py +29 -0
  58. tpyc/_data/lib/tpy/tpy/bits.py +28 -0
  59. tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
  60. tpyc/_data/lib/tpy/tpy/extern.py +8 -0
  61. tpyc/_data/lib/tpy/tpy/mem.py +49 -0
  62. tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
  63. tpyc/_data/lib/tpy/tpy/version.py +21 -0
  64. tpyc/_data/lib/tpy/typing.py +13 -0
  65. tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
  66. tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
  67. tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
  68. tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
  69. tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
  70. tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
  71. tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
  72. tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
  73. tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
  74. tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
  75. tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
  76. tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
  77. tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
  78. tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
  79. tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
  80. tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
  81. tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
  82. tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
  83. tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
  84. tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
  85. tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
  86. tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
  87. tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
  88. tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
  89. tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
  90. tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
  91. tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
  92. tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
  93. tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
  94. tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
  95. tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
  96. tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
  97. tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
  98. tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
  99. tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
  100. tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
  101. tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
  102. tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
  103. tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
  104. tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
  105. tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
  106. tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
  107. tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
  108. tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
  109. tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
  110. tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
  111. tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
  112. tpyc/_data/runtime/cpp/third_party/README.md +58 -0
  113. tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
  114. tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
  115. tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
  116. tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
  117. tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
  118. tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
  119. tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
  120. tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
  121. tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
  122. tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
  123. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
  124. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
  125. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
  126. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
  127. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
  128. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
  129. tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
  130. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
  131. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
  132. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
  133. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
  134. tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
  135. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
  136. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
  137. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
  138. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
  139. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
  140. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
  141. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
  142. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
  143. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
  144. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
  145. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
  146. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
  147. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
  148. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
  149. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
  150. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
  151. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
  152. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
  153. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
  154. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
  155. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
  156. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
  157. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
  158. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
  159. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
  160. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
  161. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
  162. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
  163. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
  164. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
  165. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
  166. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
  167. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
  168. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
  169. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
  170. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
  171. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
  172. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
  173. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
  174. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
  175. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
  176. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
  177. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
  178. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
  179. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
  180. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
  181. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
  182. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
  183. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
  184. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
  185. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
  186. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
  187. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
  188. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
  189. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
  190. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
  191. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
  192. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
  193. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
  194. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
  195. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
  196. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
  197. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
  198. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
  199. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
  200. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
  201. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
  202. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
  203. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
  204. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
  205. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
  206. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
  207. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
  208. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
  209. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
  210. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
  211. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
  212. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
  213. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
  214. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
  215. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
  216. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
  217. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
  218. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
  219. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
  220. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
  221. tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
  222. tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
  223. tpyc/build/__init__.py +7 -0
  224. tpyc/build/pcre2.py +122 -0
  225. tpyc/build/third_party.py +413 -0
  226. tpyc/cli.py +822 -0
  227. tpyc/codegen_cpp/__init__.py +18 -0
  228. tpyc/codegen_cpp/builtins.py +484 -0
  229. tpyc/codegen_cpp/context.py +2064 -0
  230. tpyc/codegen_cpp/expressions.py +5940 -0
  231. tpyc/codegen_cpp/functions.py +1913 -0
  232. tpyc/codegen_cpp/gen_async.py +3258 -0
  233. tpyc/codegen_cpp/gen_generators.py +657 -0
  234. tpyc/codegen_cpp/generator.py +2258 -0
  235. tpyc/codegen_cpp/match.py +1997 -0
  236. tpyc/codegen_cpp/param_const.py +172 -0
  237. tpyc/codegen_cpp/protocols.py +907 -0
  238. tpyc/codegen_cpp/records.py +1654 -0
  239. tpyc/codegen_cpp/resumable_cfg.py +1651 -0
  240. tpyc/codegen_cpp/statements.py +4963 -0
  241. tpyc/codegen_cpp/string_dispatch.py +76 -0
  242. tpyc/codegen_cpp/test_context.py +46 -0
  243. tpyc/codegen_cpp/test_param_const.py +113 -0
  244. tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
  245. tpyc/codegen_cpp/type_resolution.py +53 -0
  246. tpyc/codegen_cpp/types.py +436 -0
  247. tpyc/codegen_cpp/variant_access.py +135 -0
  248. tpyc/coercions.py +749 -0
  249. tpyc/compilation_context.py +57 -0
  250. tpyc/compiler.py +3945 -0
  251. tpyc/cycle_detection.py +358 -0
  252. tpyc/diagnostics.py +135 -0
  253. tpyc/dump_types.py +353 -0
  254. tpyc/frontend_diagnostics.py +47 -0
  255. tpyc/frontend_ir/__init__.py +140 -0
  256. tpyc/frontend_ir/lower.py +1098 -0
  257. tpyc/frontend_ir/nodes.py +718 -0
  258. tpyc/frontend_ir/resolver_adapter.py +151 -0
  259. tpyc/frontend_plugin.py +209 -0
  260. tpyc/install_docs.py +81 -0
  261. tpyc/liveness.py +756 -0
  262. tpyc/macro_api.py +1724 -0
  263. tpyc/macro_loader.py +497 -0
  264. tpyc/module_names.py +64 -0
  265. tpyc/modules/__init__.py +31 -0
  266. tpyc/modules/defs.py +89 -0
  267. tpyc/modules/registry.py +36 -0
  268. tpyc/modules/resolver.py +192 -0
  269. tpyc/modules/type_resolution.py +629 -0
  270. tpyc/namespace.py +172 -0
  271. tpyc/parse/__init__.py +84 -0
  272. tpyc/parse/imports.py +490 -0
  273. tpyc/parse/nodes.py +1732 -0
  274. tpyc/parse/parser.py +4043 -0
  275. tpyc/parse/resolve_refs.py +466 -0
  276. tpyc/parse/type_resolver.py +1060 -0
  277. tpyc/prescan.py +254 -0
  278. tpyc/qnames.py +149 -0
  279. tpyc/repl.py +529 -0
  280. tpyc/repl_backends.py +848 -0
  281. tpyc/sema/__init__.py +21 -0
  282. tpyc/sema/analyzer.py +3625 -0
  283. tpyc/sema/bound_check.py +72 -0
  284. tpyc/sema/builder_trace.py +684 -0
  285. tpyc/sema/calls.py +5406 -0
  286. tpyc/sema/compatibility.py +2107 -0
  287. tpyc/sema/context.py +1243 -0
  288. tpyc/sema/expressions.py +3737 -0
  289. tpyc/sema/flow_facts.py +199 -0
  290. tpyc/sema/init_tracker.py +150 -0
  291. tpyc/sema/list_literals.py +69 -0
  292. tpyc/sema/literal_utils.py +27 -0
  293. tpyc/sema/local_deduction.py +1088 -0
  294. tpyc/sema/macros.py +179 -0
  295. tpyc/sema/match.py +1177 -0
  296. tpyc/sema/method_expansion.py +347 -0
  297. tpyc/sema/methods.py +2197 -0
  298. tpyc/sema/mutation_propagation.py +268 -0
  299. tpyc/sema/narrowing.py +857 -0
  300. tpyc/sema/numeric_lattice.py +160 -0
  301. tpyc/sema/operators.py +402 -0
  302. tpyc/sema/overloads.py +841 -0
  303. tpyc/sema/protocols.py +1209 -0
  304. tpyc/sema/reach_analysis.py +202 -0
  305. tpyc/sema/registration.py +3156 -0
  306. tpyc/sema/scope_tracker.py +193 -0
  307. tpyc/sema/statements.py +4426 -0
  308. tpyc/sema/type_ops.py +1879 -0
  309. tpyc/sema/value_range.py +181 -0
  310. tpyc/symbol_binding.py +259 -0
  311. tpyc/test_c3_mro.py +208 -0
  312. tpyc/test_cli_argv.py +52 -0
  313. tpyc/test_compiler.py +559 -0
  314. tpyc/test_contains_type_param.py +101 -0
  315. tpyc/test_cycle_detection.py +221 -0
  316. tpyc/test_dump_types.py +225 -0
  317. tpyc/test_install_docs.py +65 -0
  318. tpyc/test_local_cpp_form.py +135 -0
  319. tpyc/test_macro_loader.py +76 -0
  320. tpyc/test_method_expansion.py +254 -0
  321. tpyc/test_nominal_identity.py +182 -0
  322. tpyc/test_overloads.py +410 -0
  323. tpyc/test_parse.py +303 -0
  324. tpyc/test_parse_type_ref.py +506 -0
  325. tpyc/test_parse_version_info.py +58 -0
  326. tpyc/test_reach_analysis.py +72 -0
  327. tpyc/test_ref_type.py +216 -0
  328. tpyc/test_send_sync_substitution.py +276 -0
  329. tpyc/test_tuple_mutation_propagation.py +206 -0
  330. tpyc/test_type_def_registry.py +1729 -0
  331. tpyc/test_union_types.py +195 -0
  332. tpyc/type_def_registry.py +975 -0
  333. tpyc/typesys.py +5104 -0
tpyc/repl_backends.py ADDED
@@ -0,0 +1,848 @@
1
+ """
2
+ REPL Backend Abstraction
3
+
4
+ Two backend types for executing compiled TurboPython code in the REPL:
5
+ - ClangReplBackend: Incremental JIT via clang-repl (fastest for iteration)
6
+ - CompileBackend: Traditional compile-and-run via any C++ compiler
7
+ (with PCH caching and code-change detection)
8
+
9
+ Auto-detection order: g++ -> clang++ -> zig (clang-repl via explicit --cxx only)
10
+ """
11
+
12
+ from __future__ import annotations
13
+ import abc
14
+ from concurrent.futures import ThreadPoolExecutor, as_completed
15
+ import difflib
16
+ import hashlib
17
+ import os
18
+ import re
19
+ import shutil
20
+ import subprocess
21
+ import sys
22
+ import threading
23
+ import time
24
+ from pathlib import Path
25
+ from typing import TYPE_CHECKING
26
+
27
+ if TYPE_CHECKING:
28
+ from .compiler import CppCompilerConfig
29
+
30
+
31
+ def _fmt_ms(seconds: float) -> str:
32
+ """Format seconds as milliseconds string."""
33
+ ms = seconds * 1000
34
+ if ms < 1:
35
+ return "<1ms"
36
+ return f"{ms:.0f}ms"
37
+
38
+
39
+ from . import get_runtime_dir
40
+
41
+
42
+ class BackendResult:
43
+ """Result of executing code through a backend."""
44
+ __slots__ = ("success", "stdout", "stderr", "t_build", "t_run", "build_cached")
45
+
46
+ def __init__(self, success: bool, stdout: str = "", stderr: str = "",
47
+ t_build: float = 0.0, t_run: float = 0.0,
48
+ build_cached: bool = False):
49
+ self.success = success
50
+ self.stdout = stdout
51
+ self.stderr = stderr
52
+ self.t_build = t_build
53
+ self.t_run = t_run
54
+ self.build_cached = build_cached
55
+
56
+
57
+ class REPLBackend(abc.ABC):
58
+ """Abstract base for REPL execution backends."""
59
+
60
+ @property
61
+ @abc.abstractmethod
62
+ def name(self) -> str:
63
+ """Human-readable backend name (e.g. 'clang-repl-18', 'g++-14')."""
64
+
65
+ @abc.abstractmethod
66
+ def startup(self) -> None:
67
+ """One-time initialization (PCH build, process launch, etc.)."""
68
+
69
+ @abc.abstractmethod
70
+ def execute(
71
+ self,
72
+ all_hpp_code: list[str],
73
+ all_cpp_code: list[str],
74
+ all_hpp_paths: list[Path],
75
+ all_cpp_paths: list[Path],
76
+ ) -> BackendResult:
77
+ """Build and run the current REPL state.
78
+
79
+ Args:
80
+ all_hpp_code: Generated hpp source strings for all modules.
81
+ all_cpp_code: Generated cpp source strings for all modules.
82
+ all_hpp_paths: Paths where hpp files have been written.
83
+ all_cpp_paths: Paths where cpp files have been written.
84
+
85
+ Returns:
86
+ BackendResult with success, output, and timing info.
87
+ """
88
+
89
+ @abc.abstractmethod
90
+ def cleanup(self) -> None:
91
+ """Release resources (kill subprocess, etc.)."""
92
+
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Compile backend (g++ / clang++)
96
+ # ---------------------------------------------------------------------------
97
+
98
+ class CompileBackend(REPLBackend):
99
+ """Traditional compile-and-run backend using g++ or clang++.
100
+
101
+ Uses incremental compilation: each .cpp is compiled to .o separately,
102
+ and only recompiled when its content changes. Stdlib modules are compiled
103
+ once on first execution (in parallel), then reused across REPL inputs.
104
+ """
105
+
106
+ # Extra flags used for both PCH and .o compilation (must match)
107
+ _OPT_FLAGS = ["-g0", "-pipe"]
108
+
109
+ def __init__(self, compiler: list[str], temp_dir: Path, module_name: str,
110
+ verbose: int = 0):
111
+ from .compiler import CppCompilerConfig
112
+ self._temp_dir = temp_dir
113
+ self._module_name = module_name
114
+ self._config = CppCompilerConfig(compiler=compiler)
115
+ self._pch_path: Path | None = None
116
+ self._pch_thread: threading.Thread | None = None
117
+ self._pch_build_time: float | None = None
118
+ self._pch_needs_build = False
119
+ self._binary_path = temp_dir / module_name
120
+ self._build_dir = temp_dir / "build"
121
+ self._build_dir.mkdir(exist_ok=True)
122
+ self._verbose = verbose
123
+ # Track content of each .cpp to detect changes
124
+ self._cpp_hashes: dict[str, str] = {}
125
+ # Cached .o paths keyed by .cpp path string
126
+ self._obj_cache: dict[str, str] = {}
127
+ self._n_jobs = os.cpu_count() or 1
128
+
129
+ @property
130
+ def name(self) -> str:
131
+ return self._config.compiler_name
132
+
133
+ @property
134
+ def pch_cache_dir(self) -> Path:
135
+ return self._get_pch_cache_dir()
136
+
137
+ def startup(self) -> None:
138
+ # Check if PCH needs building before spawning the thread,
139
+ # so we can print the message from the main thread cleanly
140
+ self._pch_needs_build = self._pch_is_stale()
141
+ if self._pch_needs_build:
142
+ print("Precompiling C++ headers in background...")
143
+ self._pch_thread = threading.Thread(target=self._setup_pch, daemon=True)
144
+ self._pch_thread.start()
145
+
146
+ def _wait_for_pch(self) -> None:
147
+ thread = self._pch_thread
148
+ if thread is not None:
149
+ self._pch_thread = None
150
+ thread.join()
151
+ t = self._pch_build_time
152
+ self._pch_build_time = None
153
+ if t is not None and t >= 0:
154
+ print(f" [pch] precompiled tpy.hpp ({_fmt_ms(t)})",
155
+ file=sys.stderr)
156
+ elif t is not None:
157
+ print(" [pch] failed, headers will be parsed each time",
158
+ file=sys.stderr)
159
+
160
+ def _common_flags(self) -> list[str]:
161
+ runtime_dir = get_runtime_dir()
162
+ flags = [
163
+ *self._config.compiler, f"-std={self._config.std}",
164
+ *self._config.extra_flags,
165
+ *self._OPT_FLAGS,
166
+ "-I", str(runtime_dir / "cpp" / "include"),
167
+ "-I", str(self._temp_dir),
168
+ ]
169
+ if self._pch_path:
170
+ flags += ["-include", str(self._pch_path)]
171
+ return flags
172
+
173
+ def _obj_path(self, cpp_path: Path) -> Path:
174
+ try:
175
+ rel = cpp_path.relative_to(self._temp_dir)
176
+ obj_name = str(rel).replace(os.sep, "_").removesuffix(".cpp") + ".o"
177
+ except ValueError:
178
+ obj_name = cpp_path.stem + ".o"
179
+ return self._build_dir / obj_name
180
+
181
+ def _compile_one(self, cpp_path: Path) -> subprocess.CompletedProcess[str]:
182
+ obj = self._obj_path(cpp_path)
183
+ cmd = [*self._common_flags(), "-c", "-o", str(obj), str(cpp_path)]
184
+ return subprocess.run(cmd, capture_output=True, text=True)
185
+
186
+ def execute(
187
+ self,
188
+ all_hpp_code: list[str],
189
+ all_cpp_code: list[str],
190
+ all_hpp_paths: list[Path],
191
+ all_cpp_paths: list[Path],
192
+ ) -> BackendResult:
193
+ # Determine which .cpp files changed
194
+ changed: list[Path] = []
195
+ for cpp_path, cpp_code in zip(all_cpp_paths, all_cpp_code):
196
+ key = str(cpp_path)
197
+ if self._cpp_hashes.get(key) != cpp_code:
198
+ changed.append(cpp_path)
199
+
200
+ if not changed and self._binary_path.exists():
201
+ # Nothing changed, reuse binary
202
+ return self._run_binary(0.0, build_cached=True)
203
+
204
+ t_build_start = time.monotonic()
205
+ self._wait_for_pch()
206
+
207
+ # Compile changed .cpp files (parallel for multiple files)
208
+ if self._verbose >= 1 and changed:
209
+ names = [p.stem + ".cpp" for p in changed]
210
+ print(f" [build] compiling {len(changed)} files: {', '.join(names)}",
211
+ file=sys.stderr)
212
+
213
+ failed_stderr = ""
214
+ if len(changed) > 1 and self._n_jobs > 1:
215
+ with ThreadPoolExecutor(max_workers=self._n_jobs) as pool:
216
+ futures = {pool.submit(self._compile_one, p): p for p in changed}
217
+ for future in as_completed(futures):
218
+ r = future.result()
219
+ if r.returncode != 0 and not failed_stderr:
220
+ failed_stderr = r.stderr
221
+ else:
222
+ for cpp_path in changed:
223
+ r = self._compile_one(cpp_path)
224
+ if r.returncode != 0:
225
+ failed_stderr = r.stderr
226
+ break
227
+
228
+ if failed_stderr:
229
+ return BackendResult(False, stderr=f"C++ compilation failed:\n{failed_stderr}",
230
+ t_build=time.monotonic() - t_build_start)
231
+
232
+ # Update hash cache for successfully compiled files
233
+ for cpp_path, cpp_code in zip(all_cpp_paths, all_cpp_code):
234
+ key = str(cpp_path)
235
+ self._cpp_hashes[key] = cpp_code
236
+ self._obj_cache[key] = str(self._obj_path(cpp_path))
237
+
238
+ # Link all .o files
239
+ all_objs = [self._obj_cache[str(p)] for p in all_cpp_paths]
240
+ link_cmd = [
241
+ *self._config.compiler,
242
+ "-o", str(self._binary_path),
243
+ *all_objs,
244
+ *self._config.link_flags,
245
+ ]
246
+ result = subprocess.run(link_cmd, capture_output=True, text=True)
247
+ t_build = time.monotonic() - t_build_start
248
+
249
+ if result.returncode != 0:
250
+ return BackendResult(False, stderr=f"C++ link failed:\n{result.stderr}",
251
+ t_build=t_build)
252
+
253
+ return self._run_binary(t_build, build_cached=False)
254
+
255
+ def _run_binary(self, t_build: float, build_cached: bool) -> BackendResult:
256
+ t_run_start = time.monotonic()
257
+ result = subprocess.run([str(self._binary_path)], capture_output=True, text=True)
258
+ t_run = time.monotonic() - t_run_start
259
+
260
+ if result.returncode != 0:
261
+ output = result.stdout + result.stderr
262
+ if not output.strip():
263
+ output = f"Runtime error (exit code {result.returncode})\n"
264
+ return BackendResult(False, stderr=output, t_build=t_build, t_run=t_run,
265
+ build_cached=build_cached)
266
+
267
+ return BackendResult(True, stdout=result.stdout, stderr=result.stderr,
268
+ t_build=t_build, t_run=t_run,
269
+ build_cached=build_cached)
270
+
271
+ def cleanup(self) -> None:
272
+ pass
273
+
274
+ # -- PCH management --
275
+
276
+ def _get_pch_cache_dir(self) -> Path:
277
+ runtime_dir = get_runtime_dir()
278
+ opt = " ".join(self._OPT_FLAGS)
279
+ key_data = f"{self._config.compiler_name}:{self._config.std}:{opt}:{runtime_dir}"
280
+ key = hashlib.md5(key_data.encode()).hexdigest()[:12]
281
+ cache_dir = Path.home() / ".cache" / "tpyc" / f"pch_{key}"
282
+ cache_dir.mkdir(parents=True, exist_ok=True)
283
+ return cache_dir
284
+
285
+ def _pch_is_stale(self) -> bool:
286
+ cache_dir = self._get_pch_cache_dir()
287
+ pch_gch = cache_dir / "tpy_pch.hpp.gch"
288
+ if not pch_gch.exists():
289
+ return True
290
+ runtime_dir = get_runtime_dir()
291
+ runtime_include = runtime_dir / "cpp" / "include" / "tpy"
292
+ pch_mtime = pch_gch.stat().st_mtime
293
+ return any(h.stat().st_mtime > pch_mtime
294
+ for h in runtime_include.glob("**/*.hpp"))
295
+
296
+ def _setup_pch(self) -> None:
297
+ runtime_dir = get_runtime_dir()
298
+ cache_dir = self._get_pch_cache_dir()
299
+ pch_header = cache_dir / "tpy_pch.hpp"
300
+ pch_gch = cache_dir / "tpy_pch.hpp.gch"
301
+
302
+ if not self._pch_needs_build:
303
+ self._pch_path = pch_header
304
+ return
305
+
306
+ pch_header.write_text('#include <tpy/tpy.hpp>\n')
307
+ cmd = [
308
+ *self._config.compiler, f"-std={self._config.std}",
309
+ *self._config.extra_flags,
310
+ *self._OPT_FLAGS,
311
+ "-I", str(runtime_dir / "cpp" / "include"),
312
+ "-x", "c++-header",
313
+ str(pch_header), "-o", str(pch_gch),
314
+ ]
315
+
316
+ t0 = time.monotonic()
317
+ result = subprocess.run(cmd, capture_output=True, text=True)
318
+ elapsed = time.monotonic() - t0
319
+
320
+ if result.returncode == 0:
321
+ self._pch_path = pch_header
322
+ self._pch_build_time = elapsed
323
+ else:
324
+ self._pch_build_time = -1.0 # signal failure
325
+
326
+
327
+ # ---------------------------------------------------------------------------
328
+ # clang-repl backend
329
+ # ---------------------------------------------------------------------------
330
+
331
+ class ClangReplBackend(REPLBackend):
332
+ """Incremental JIT backend using clang-repl.
333
+
334
+ Keeps a persistent clang-repl subprocess. On each REPL iteration, diffs
335
+ the generated C++ against the previous state and sends only new/changed
336
+ declarations to clang-repl.
337
+
338
+ Communication uses dual sentinels: one on stderr to synchronize input
339
+ processing, one on stdout to delimit program output.
340
+ """
341
+
342
+ def __init__(self, binary: str, temp_dir: Path, module_name: str):
343
+ self._binary = binary
344
+ self._temp_dir = temp_dir
345
+ self._module_name = module_name
346
+ self._proc: subprocess.Popen | None = None
347
+ self._flat_header: Path | None = None
348
+ self._prev_hpp_lines: list[str] = []
349
+ self._prev_cpp_body_lines: list[str] = []
350
+ self._stderr_thread: threading.Thread | None = None
351
+ self._stderr_lock = threading.Lock()
352
+ self._stderr_lines: list[str] = []
353
+ self._stderr_event = threading.Event()
354
+ self._stderr_sentinel: str = ""
355
+
356
+ @property
357
+ def name(self) -> str:
358
+ return self._binary
359
+
360
+ def startup(self, silent: bool = False) -> None:
361
+ self._flat_header = self._build_flat_header()
362
+ runtime_dir = get_runtime_dir()
363
+
364
+ if not silent:
365
+ print("Starting clang-repl...", end="", flush=True)
366
+ t0 = time.monotonic()
367
+ self._proc = subprocess.Popen(
368
+ [
369
+ self._binary, "-Xcc=-std=c++23",
370
+ f"-Xcc=-I{runtime_dir / 'cpp' / 'include'}",
371
+ f"-Xcc=-I{self._temp_dir}",
372
+ "-Xcc=-include", f"-Xcc={self._flat_header}",
373
+ ],
374
+ stdin=subprocess.PIPE,
375
+ stdout=subprocess.PIPE,
376
+ stderr=subprocess.PIPE,
377
+ text=True,
378
+ )
379
+
380
+ # Start background thread to read stderr (avoids deadlocks)
381
+ self._stderr_thread = threading.Thread(target=self._read_stderr, daemon=True)
382
+ self._stderr_thread.start()
383
+
384
+ # Wait for startup by sending a no-op
385
+ self._send_sync("int __tpy_startup = 0;")
386
+ elapsed = time.monotonic() - t0
387
+ if not silent:
388
+ print(f" done ({_fmt_ms(elapsed)})")
389
+
390
+ def execute(
391
+ self,
392
+ all_hpp_code: list[str],
393
+ all_cpp_code: list[str],
394
+ all_hpp_paths: list[Path],
395
+ all_cpp_paths: list[Path],
396
+ ) -> BackendResult:
397
+ if self._proc is None or self._proc.poll() is not None:
398
+ # Auto-restart after crash
399
+ try:
400
+ self.startup(silent=True)
401
+ self._resend_prev_state()
402
+ except Exception as e:
403
+ return BackendResult(False,
404
+ stderr=f"clang-repl restart failed: {e}\n")
405
+
406
+ t_build_start = time.monotonic()
407
+
408
+ # Extract init body (the REPL statements from __tpy_init)
409
+ entry_cpp = all_cpp_code[-1]
410
+ cpp_body = self._extract_init_body(entry_cpp)
411
+
412
+ # Extract top-level declarations (functions, records) from hpp
413
+ entry_hpp = all_hpp_code[-1]
414
+ hpp_decls = self._extract_toplevel_decls(entry_hpp, entry_cpp)
415
+ new_decls = self._diff_lines(self._prev_hpp_lines, hpp_decls,
416
+ insert_only=True)
417
+ for decl in new_decls:
418
+ err = self._send_sync(decl)
419
+ if err and "error:" in err.lower():
420
+ return BackendResult(False, stderr=f"clang-repl decl error:\n{err}",
421
+ t_build=time.monotonic() - t_build_start)
422
+ if new_decls:
423
+ self._prev_hpp_lines = hpp_decls
424
+
425
+ # Diff init body: send new statements
426
+ new_cpp = self._diff_lines(self._prev_cpp_body_lines, cpp_body)
427
+ if not new_cpp:
428
+ return BackendResult(True, stdout="", t_build=0.0, t_run=0.0, build_cached=True)
429
+
430
+ t_build = time.monotonic() - t_build_start
431
+
432
+ # Execute new lines, capturing stdout between sentinels
433
+ t_run_start = time.monotonic()
434
+ code = "\n".join(new_cpp)
435
+ stdout_output, stderr_output = self._send_and_capture(code)
436
+ t_run = time.monotonic() - t_run_start
437
+
438
+ # Detect if the process died (e.g. tpy_panic -> std::exit)
439
+ if self._proc.poll() is not None:
440
+ # Don't update _prev_cpp_body_lines so the failing statement
441
+ # isn't replayed on restart
442
+ return BackendResult(False,
443
+ stderr="clang-repl crashed (will restart on next input)\n",
444
+ t_build=t_build, t_run=t_run)
445
+
446
+ if stderr_output and "error:" in stderr_output.lower():
447
+ return BackendResult(False, stderr=stderr_output,
448
+ t_build=t_build, t_run=t_run)
449
+
450
+ # Update state on success
451
+ self._prev_cpp_body_lines = cpp_body
452
+
453
+ return BackendResult(True, stdout=stdout_output, stderr=stderr_output,
454
+ t_build=t_build, t_run=t_run)
455
+
456
+ def cleanup(self) -> None:
457
+ if self._proc and self._proc.poll() is None:
458
+ try:
459
+ self._proc.stdin.close()
460
+ self._proc.wait(timeout=2)
461
+ except Exception:
462
+ self._proc.kill()
463
+
464
+ def _resend_prev_state(self) -> None:
465
+ """Replay previously-accepted declarations after a restart."""
466
+ for decl in self._prev_hpp_lines:
467
+ err = self._send_sync(decl)
468
+ if err and "error:" in err.lower():
469
+ raise RuntimeError(f"State replay failed:\n{err}")
470
+ for stmt in self._prev_cpp_body_lines:
471
+ err = self._send_sync(stmt)
472
+ if err and "error:" in err.lower():
473
+ raise RuntimeError(f"State replay failed:\n{err}")
474
+
475
+ # -- Internal helpers --
476
+
477
+ def _build_flat_header(self) -> Path:
478
+ """Build a flat header that includes all tpy runtime headers via angle brackets."""
479
+ runtime_dir = get_runtime_dir()
480
+ tpy_hpp = runtime_dir / "cpp" / "include" / "tpy" / "tpy.hpp"
481
+ content = tpy_hpp.read_text()
482
+
483
+ # Convert relative includes to angle-bracket includes
484
+ lines = []
485
+ for line in content.splitlines():
486
+ stripped = line.strip()
487
+ if stripped.startswith('#include "') and stripped.endswith('"'):
488
+ header_name = stripped[len('#include "'):-1]
489
+ lines.append(f'#include <tpy/{header_name}>')
490
+ elif stripped.startswith("#pragma once"):
491
+ continue
492
+ else:
493
+ lines.append(line)
494
+
495
+ flat_path = self._temp_dir / "tpy_flat.hpp"
496
+ flat_path.write_text("\n".join(lines) + "\n")
497
+ return flat_path
498
+
499
+ def _read_stderr(self) -> None:
500
+ """Background thread: read stderr lines and signal when sentinel seen."""
501
+ while True:
502
+ line = self._proc.stderr.readline()
503
+ if not line:
504
+ # Process exited -- unblock any waiter
505
+ self._stderr_event.set()
506
+ break
507
+ with self._stderr_lock:
508
+ if self._stderr_sentinel and self._stderr_sentinel in line:
509
+ self._stderr_event.set()
510
+ else:
511
+ self._stderr_lines.append(line)
512
+
513
+ def _send_sync(self, code: str) -> str:
514
+ """Send code and wait for stderr sentinel (no stdout capture).
515
+
516
+ Each line is a separate input for clang-repl, so multi-line
517
+ declarations must be collapsed to single lines first.
518
+ """
519
+ sentinel = f"__SE_{time.monotonic_ns()}__"
520
+ with self._stderr_lock:
521
+ self._stderr_sentinel = sentinel
522
+ self._stderr_lines.clear()
523
+ self._stderr_event.clear()
524
+
525
+ # Collapse to single line so clang-repl parses it as one input
526
+ oneliner = " ".join(code.split())
527
+ full = f'{oneliner}\nstd::cerr << "{sentinel}\\n";\n'
528
+ self._proc.stdin.write(full)
529
+ self._proc.stdin.flush()
530
+
531
+ self._stderr_event.wait(timeout=30)
532
+ with self._stderr_lock:
533
+ return "".join(self._stderr_lines)
534
+
535
+ def _send_and_capture(self, code: str) -> tuple[str, str]:
536
+ """Send code and capture both stdout and stderr.
537
+
538
+ Uses stdout sentinel to delimit program output, stderr sentinel
539
+ to know when processing is complete. Each statement must be sent
540
+ as a single line for clang-repl.
541
+ """
542
+ stdout_sentinel = f"__SO_{time.monotonic_ns()}__"
543
+ stderr_sentinel = f"__SE_{time.monotonic_ns()}__"
544
+ with self._stderr_lock:
545
+ self._stderr_sentinel = stderr_sentinel
546
+ self._stderr_lines.clear()
547
+ self._stderr_event.clear()
548
+
549
+ # Each statement is already a complete single-line entry from
550
+ # _extract_init_body, so just join them with newlines.
551
+ body = code
552
+
553
+ full = (
554
+ f'std::cout << "{stdout_sentinel}\\n" << std::flush;\n'
555
+ f'{body}\n'
556
+ f'std::cout << "{stdout_sentinel}\\n" << std::flush;\n'
557
+ f'std::cerr << "{stderr_sentinel}\\n";\n'
558
+ )
559
+ self._proc.stdin.write(full)
560
+ self._proc.stdin.flush()
561
+
562
+ # Read stdout until we see the closing sentinel
563
+ stdout_lines = []
564
+ started = False
565
+ while True:
566
+ line = self._proc.stdout.readline()
567
+ if not line:
568
+ break
569
+ if stdout_sentinel in line:
570
+ if not started:
571
+ started = True
572
+ continue
573
+ else:
574
+ break
575
+ if started:
576
+ stdout_lines.append(line)
577
+
578
+ # Wait for stderr sentinel too
579
+ self._stderr_event.wait(timeout=30)
580
+
581
+ with self._stderr_lock:
582
+ return "".join(stdout_lines), "".join(self._stderr_lines)
583
+
584
+ def _extract_toplevel_decls(self, hpp_code: str, cpp_code: str) -> list[str]:
585
+ """Extract user-defined functions/structs from generated code.
586
+
587
+ For clang-repl, we need to send function definitions and struct
588
+ declarations directly (not wrapped in namespaces). Extracts:
589
+ - Struct/class definitions and operator overloads from hpp
590
+ - Function definitions and global variable declarations from cpp
591
+ """
592
+ result = []
593
+
594
+ # Extract struct definitions from hpp namespace block
595
+ result.extend(self._extract_namespace_body(hpp_code))
596
+
597
+ # Extract function definitions + global vars from cpp namespace block
598
+ result.extend(self._extract_namespace_body(cpp_code))
599
+
600
+ return result
601
+
602
+ def _extract_namespace_body(self, code: str) -> list[str]:
603
+ """Extract brace-balanced declarations from inside the namespace block.
604
+
605
+ Skips includes, pragmas, comments, forward declarations of __tpy_init,
606
+ __name__ constexpr, and stops before __tpy_init() or main().
607
+ """
608
+ result = []
609
+ lines = code.splitlines()
610
+ i = 0
611
+ # Skip includes, pragmas, comments, blank lines before namespace
612
+ while i < len(lines):
613
+ stripped = lines[i].strip()
614
+ if stripped and not stripped.startswith(("//", "#")) and "namespace" not in stripped:
615
+ break
616
+ if "namespace" in stripped:
617
+ i += 1
618
+ break
619
+ i += 1
620
+
621
+ # Collect brace-balanced blocks from inside the namespace
622
+ brace_depth = 0
623
+ collecting = False
624
+ current_block: list[str] = []
625
+ while i < len(lines):
626
+ stripped = lines[i].strip()
627
+ if "__tpy_init()" in stripped or stripped.startswith("int main("):
628
+ break
629
+ if stripped.startswith("} // namespace"):
630
+ break
631
+
632
+ # Skip boilerplate
633
+ if not collecting:
634
+ if (not stripped or stripped.startswith("//")
635
+ or "__name__" in stripped
636
+ or ("__tpy_init" in stripped and ";" in stripped)):
637
+ i += 1
638
+ continue
639
+ collecting = True
640
+
641
+ if collecting:
642
+ current_block.append(stripped)
643
+ brace_depth += stripped.count("{") - stripped.count("}")
644
+ # A block is complete when braces are balanced AND the last
645
+ # line looks like a statement/definition end (';' or '}').
646
+ # This keeps `template<typename T>` attached to what follows.
647
+ if (brace_depth <= 0 and current_block
648
+ and (stripped.endswith(";") or stripped.endswith("}"))):
649
+ block = "\n".join(current_block)
650
+ if block.strip():
651
+ result.append(block)
652
+ current_block = []
653
+ collecting = False
654
+ brace_depth = 0
655
+ i += 1
656
+
657
+ return result
658
+
659
+ def _extract_init_body(self, cpp_code: str) -> list[str]:
660
+ """Extract complete statements from __tpy_init() body.
661
+
662
+ Groups multi-line constructs (lambdas, for-loops, if-blocks) into
663
+ single entries by tracking brace depth, so each returned string is
664
+ a complete statement that clang-repl can parse on one line.
665
+ """
666
+ lines = cpp_code.splitlines()
667
+ # First, collect raw body lines from inside __tpy_init
668
+ body_lines: list[str] = []
669
+ in_init = False
670
+ func_depth = 0
671
+ for line in lines:
672
+ stripped = line.strip()
673
+ if not in_init:
674
+ if "__tpy_init()" in stripped and "{" in stripped:
675
+ in_init = True
676
+ func_depth = 1
677
+ continue
678
+ func_depth += stripped.count("{") - stripped.count("}")
679
+ if func_depth <= 0:
680
+ break
681
+ if stripped in ("static bool initialized = false;",
682
+ "if (initialized) return;",
683
+ "initialized = true;",
684
+ "return 0;", "0;", ""):
685
+ continue
686
+ if stripped:
687
+ body_lines.append(stripped)
688
+
689
+ # Group lines into complete statements by tracking brace depth
690
+ result: list[str] = []
691
+ current: list[str] = []
692
+ depth = 0
693
+ for line in body_lines:
694
+ current.append(line)
695
+ depth += line.count("{") - line.count("}")
696
+ if depth <= 0:
697
+ stmt = " ".join(current)
698
+ # clang-repl executes at top level where lambdas can't use
699
+ # capture-default. Variables are global so [] suffices.
700
+ stmt = stmt.replace("[&]()", "[]()")
701
+ # Hoist immediately-invoked lambdas into temp variables so
702
+ # clang-repl JIT-compiles them in isolation (avoids stack
703
+ # overflow from deeply nested template instantiation).
704
+ stmt = self._hoist_lambdas(stmt, result)
705
+ result.append(stmt)
706
+ current = []
707
+ depth = 0
708
+
709
+ # Flush any remaining (shouldn't happen with well-formed code)
710
+ if current:
711
+ result.append(" ".join(current))
712
+
713
+ return result
714
+
715
+ @staticmethod
716
+ def _hoist_lambdas(stmt: str, result: list[str]) -> str:
717
+ """Extract immediately-invoked lambdas into temp variables.
718
+
719
+ Transforms `...f([]() { body }())...` into:
720
+ auto __lam_N = []() { body }();
721
+ ...f(__lam_N)...
722
+ """
723
+ counter = len(result)
724
+ while True:
725
+ # Match [](){ ... }() -- an immediately-invoked lambda
726
+ # Find the start: []() {
727
+ m = re.search(r'\[\]\(\)\s*\{', stmt)
728
+ if not m:
729
+ break
730
+ lam_start = m.start()
731
+ # Find matching closing brace by tracking depth
732
+ brace_start = m.end() - 1 # position of opening {
733
+ depth = 1
734
+ i = brace_start + 1
735
+ while i < len(stmt) and depth > 0:
736
+ if stmt[i] == '{':
737
+ depth += 1
738
+ elif stmt[i] == '}':
739
+ depth -= 1
740
+ i += 1
741
+ if depth != 0:
742
+ break
743
+ # Check for () invocation after closing brace
744
+ rest = stmt[i:]
745
+ inv = re.match(r'\s*\(\)', rest)
746
+ if not inv:
747
+ break
748
+ lam_end = i + inv.end()
749
+ lam_expr = stmt[lam_start:lam_end]
750
+ var_name = f"__lam_{counter}"
751
+ counter += 1
752
+ result.append(f"auto {var_name} = {lam_expr};")
753
+ stmt = stmt[:lam_start] + var_name + stmt[lam_end:]
754
+ return stmt
755
+
756
+ def _diff_lines(self, old: list[str], new: list[str],
757
+ insert_only: bool = False) -> list[str]:
758
+ """Return lines that changed between old and new.
759
+
760
+ Args:
761
+ insert_only: If True, only return "insert" ops (for declarations
762
+ where redefinition is illegal in C++). If False, also return
763
+ "replace" ops (for executable statements).
764
+ """
765
+ if not old:
766
+ return new
767
+ if old == new:
768
+ return []
769
+ include = {"insert"} if insert_only else {"insert", "replace"}
770
+ added = []
771
+ for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(
772
+ None, old, new
773
+ ).get_opcodes():
774
+ if tag in include:
775
+ added.extend(new[j1:j2])
776
+ return added
777
+
778
+
779
+ # ---------------------------------------------------------------------------
780
+ # Auto-detection
781
+ # ---------------------------------------------------------------------------
782
+
783
+ def detect_backend(
784
+ cxx: str,
785
+ temp_dir: Path,
786
+ module_name: str,
787
+ verbose: int = 0,
788
+ ) -> REPLBackend:
789
+ """Create a backend based on --cxx value or auto-detection.
790
+
791
+ Args:
792
+ cxx: Compiler selection from --cxx flag (e.g. "auto", "gcc", "clang-repl").
793
+ temp_dir: Temp directory for build artifacts.
794
+ module_name: Fixed module name for the REPL.
795
+ verbose: Verbosity level (0=quiet, 1=timing+build info, 2=+generated C++).
796
+
797
+ Returns:
798
+ An initialized (but not yet started) REPLBackend.
799
+ """
800
+ from .compiler import (
801
+ _find_best_versioned, _resolve_compiler,
802
+ )
803
+
804
+ if cxx == "auto":
805
+ return _auto_detect(temp_dir, module_name, verbose)
806
+
807
+ # clang-repl: JIT backend (specific or versioned, e.g. clang-repl-18)
808
+ if cxx == "clang-repl" or cxx.startswith("clang-repl-"):
809
+ binary = cxx if shutil.which(cxx) else _find_best_versioned("clang-repl")
810
+ if not binary:
811
+ print("Warning: clang-repl not found, falling back to auto-detect",
812
+ file=sys.stderr)
813
+ return _auto_detect(temp_dir, module_name, verbose)
814
+ return ClangReplBackend(binary, temp_dir, module_name)
815
+
816
+ # All other values: resolve via shared compiler detection
817
+ resolved = _resolve_compiler(cxx)
818
+ if resolved is None:
819
+ print(f"Warning: C++ compiler '{cxx}' not found, falling back to auto-detect",
820
+ file=sys.stderr)
821
+ return _auto_detect(temp_dir, module_name, verbose)
822
+ return CompileBackend(resolved, temp_dir, module_name, verbose=verbose)
823
+
824
+
825
+ def _auto_detect(temp_dir: Path, module_name: str, verbose: int = 0) -> REPLBackend:
826
+ """Auto-detect the best available backend.
827
+
828
+ Prefers g++ for incremental compile backend (fastest with PCH + split .o),
829
+ then clang++, then zig (system or bundled).
830
+ """
831
+ from .compiler import _find_best_versioned, _find_all_zig
832
+
833
+ gpp = _find_best_versioned("g++")
834
+ if gpp:
835
+ return CompileBackend([gpp], temp_dir, module_name, verbose=verbose)
836
+
837
+ clangpp = _find_best_versioned("clang++")
838
+ if clangpp:
839
+ return CompileBackend([clangpp], temp_dir, module_name, verbose=verbose)
840
+
841
+ system_zig, bundled_zig = _find_all_zig()
842
+ zig = system_zig or bundled_zig
843
+ if zig:
844
+ return CompileBackend([zig, "c++"], temp_dir, module_name, verbose=verbose)
845
+
846
+ print("Error: no C++ compiler found (tried g++, clang++, zig)",
847
+ file=sys.stderr)
848
+ sys.exit(1)