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.
- tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
- tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
- tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
- tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
- tpyc/__init__.py +104 -0
- tpyc/__main__.py +6 -0
- tpyc/_buildinfo.py +1 -0
- tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
- tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
- tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
- tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
- tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
- tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
- tpyc/_data/lib/tpy/_functools_macros.py +80 -0
- tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
- tpyc/_data/lib/tpy/argparse.py +2062 -0
- tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
- tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
- tpyc/_data/lib/tpy/base64.py +410 -0
- tpyc/_data/lib/tpy/bisect.py +39 -0
- tpyc/_data/lib/tpy/builtins.py +38 -0
- tpyc/_data/lib/tpy/dataclasses.py +354 -0
- tpyc/_data/lib/tpy/enum.py +23 -0
- tpyc/_data/lib/tpy/functools.py +33 -0
- tpyc/_data/lib/tpy/hashlib.py +206 -0
- tpyc/_data/lib/tpy/heapq.py +118 -0
- tpyc/_data/lib/tpy/io.py +395 -0
- tpyc/_data/lib/tpy/json.py +221 -0
- tpyc/_data/lib/tpy/math.py +406 -0
- tpyc/_data/lib/tpy/random.py +597 -0
- tpyc/_data/lib/tpy/re.py +467 -0
- tpyc/_data/lib/tpy/socket.py +379 -0
- tpyc/_data/lib/tpy/struct.py +178 -0
- tpyc/_data/lib/tpy/sys.py +40 -0
- tpyc/_data/lib/tpy/time.py +39 -0
- tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
- tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
- tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
- tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
- tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
- tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
- tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
- tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
- tpyc/_data/lib/tpy/tpy/_version.py +29 -0
- tpyc/_data/lib/tpy/tpy/bits.py +28 -0
- tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
- tpyc/_data/lib/tpy/tpy/extern.py +8 -0
- tpyc/_data/lib/tpy/tpy/mem.py +49 -0
- tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
- tpyc/_data/lib/tpy/tpy/version.py +21 -0
- tpyc/_data/lib/tpy/typing.py +13 -0
- tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
- tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
- tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
- tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
- tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
- tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
- tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
- tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
- tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
- tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
- tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
- tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
- tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
- tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
- tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
- tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
- tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
- tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
- tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
- tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
- tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
- tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
- tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
- tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
- tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
- tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
- tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
- tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
- tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
- tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
- tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
- tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
- tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
- tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
- tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
- tpyc/_data/runtime/cpp/third_party/README.md +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
- tpyc/build/__init__.py +7 -0
- tpyc/build/pcre2.py +122 -0
- tpyc/build/third_party.py +413 -0
- tpyc/cli.py +822 -0
- tpyc/codegen_cpp/__init__.py +18 -0
- tpyc/codegen_cpp/builtins.py +484 -0
- tpyc/codegen_cpp/context.py +2064 -0
- tpyc/codegen_cpp/expressions.py +5940 -0
- tpyc/codegen_cpp/functions.py +1913 -0
- tpyc/codegen_cpp/gen_async.py +3258 -0
- tpyc/codegen_cpp/gen_generators.py +657 -0
- tpyc/codegen_cpp/generator.py +2258 -0
- tpyc/codegen_cpp/match.py +1997 -0
- tpyc/codegen_cpp/param_const.py +172 -0
- tpyc/codegen_cpp/protocols.py +907 -0
- tpyc/codegen_cpp/records.py +1654 -0
- tpyc/codegen_cpp/resumable_cfg.py +1651 -0
- tpyc/codegen_cpp/statements.py +4963 -0
- tpyc/codegen_cpp/string_dispatch.py +76 -0
- tpyc/codegen_cpp/test_context.py +46 -0
- tpyc/codegen_cpp/test_param_const.py +113 -0
- tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
- tpyc/codegen_cpp/type_resolution.py +53 -0
- tpyc/codegen_cpp/types.py +436 -0
- tpyc/codegen_cpp/variant_access.py +135 -0
- tpyc/coercions.py +749 -0
- tpyc/compilation_context.py +57 -0
- tpyc/compiler.py +3945 -0
- tpyc/cycle_detection.py +358 -0
- tpyc/diagnostics.py +135 -0
- tpyc/dump_types.py +353 -0
- tpyc/frontend_diagnostics.py +47 -0
- tpyc/frontend_ir/__init__.py +140 -0
- tpyc/frontend_ir/lower.py +1098 -0
- tpyc/frontend_ir/nodes.py +718 -0
- tpyc/frontend_ir/resolver_adapter.py +151 -0
- tpyc/frontend_plugin.py +209 -0
- tpyc/install_docs.py +81 -0
- tpyc/liveness.py +756 -0
- tpyc/macro_api.py +1724 -0
- tpyc/macro_loader.py +497 -0
- tpyc/module_names.py +64 -0
- tpyc/modules/__init__.py +31 -0
- tpyc/modules/defs.py +89 -0
- tpyc/modules/registry.py +36 -0
- tpyc/modules/resolver.py +192 -0
- tpyc/modules/type_resolution.py +629 -0
- tpyc/namespace.py +172 -0
- tpyc/parse/__init__.py +84 -0
- tpyc/parse/imports.py +490 -0
- tpyc/parse/nodes.py +1732 -0
- tpyc/parse/parser.py +4043 -0
- tpyc/parse/resolve_refs.py +466 -0
- tpyc/parse/type_resolver.py +1060 -0
- tpyc/prescan.py +254 -0
- tpyc/qnames.py +149 -0
- tpyc/repl.py +529 -0
- tpyc/repl_backends.py +848 -0
- tpyc/sema/__init__.py +21 -0
- tpyc/sema/analyzer.py +3625 -0
- tpyc/sema/bound_check.py +72 -0
- tpyc/sema/builder_trace.py +684 -0
- tpyc/sema/calls.py +5406 -0
- tpyc/sema/compatibility.py +2107 -0
- tpyc/sema/context.py +1243 -0
- tpyc/sema/expressions.py +3737 -0
- tpyc/sema/flow_facts.py +199 -0
- tpyc/sema/init_tracker.py +150 -0
- tpyc/sema/list_literals.py +69 -0
- tpyc/sema/literal_utils.py +27 -0
- tpyc/sema/local_deduction.py +1088 -0
- tpyc/sema/macros.py +179 -0
- tpyc/sema/match.py +1177 -0
- tpyc/sema/method_expansion.py +347 -0
- tpyc/sema/methods.py +2197 -0
- tpyc/sema/mutation_propagation.py +268 -0
- tpyc/sema/narrowing.py +857 -0
- tpyc/sema/numeric_lattice.py +160 -0
- tpyc/sema/operators.py +402 -0
- tpyc/sema/overloads.py +841 -0
- tpyc/sema/protocols.py +1209 -0
- tpyc/sema/reach_analysis.py +202 -0
- tpyc/sema/registration.py +3156 -0
- tpyc/sema/scope_tracker.py +193 -0
- tpyc/sema/statements.py +4426 -0
- tpyc/sema/type_ops.py +1879 -0
- tpyc/sema/value_range.py +181 -0
- tpyc/symbol_binding.py +259 -0
- tpyc/test_c3_mro.py +208 -0
- tpyc/test_cli_argv.py +52 -0
- tpyc/test_compiler.py +559 -0
- tpyc/test_contains_type_param.py +101 -0
- tpyc/test_cycle_detection.py +221 -0
- tpyc/test_dump_types.py +225 -0
- tpyc/test_install_docs.py +65 -0
- tpyc/test_local_cpp_form.py +135 -0
- tpyc/test_macro_loader.py +76 -0
- tpyc/test_method_expansion.py +254 -0
- tpyc/test_nominal_identity.py +182 -0
- tpyc/test_overloads.py +410 -0
- tpyc/test_parse.py +303 -0
- tpyc/test_parse_type_ref.py +506 -0
- tpyc/test_parse_version_info.py +58 -0
- tpyc/test_reach_analysis.py +72 -0
- tpyc/test_ref_type.py +216 -0
- tpyc/test_send_sync_substitution.py +276 -0
- tpyc/test_tuple_mutation_propagation.py +206 -0
- tpyc/test_type_def_registry.py +1729 -0
- tpyc/test_union_types.py +195 -0
- tpyc/type_def_registry.py +975 -0
- 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)
|