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
|
@@ -0,0 +1,2062 @@
|
|
|
1
|
+
# tpy: macro_module
|
|
2
|
+
"""argparse builder-trace macro.
|
|
3
|
+
|
|
4
|
+
Mirrors a slice of CPython's argparse.ArgumentParser surface as a
|
|
5
|
+
@builder_macro: the user writes ordinary builder-style code, the
|
|
6
|
+
compiler synthesizes a per-call-site record + parse function on
|
|
7
|
+
parse_args(...). Under CPython the same source resolves to the
|
|
8
|
+
stdlib argparse module (this file is invisible outside tpyc's lib
|
|
9
|
+
search path), so test programs run under both backends without
|
|
10
|
+
code changes.
|
|
11
|
+
|
|
12
|
+
Subparsers are layered on top via @builder_returns: the macro
|
|
13
|
+
classes ``_SubparsersAction`` (returned by ``add_subparsers()``)
|
|
14
|
+
and ``_SubparserBuilder`` (returned by ``add_parser(name)``)
|
|
15
|
+
collect per-sub arg specs that the top-level terminal walks to
|
|
16
|
+
emit per-sub records and parse fns. The top namespace flattens
|
|
17
|
+
per-sub fields as ``Optional[T]`` (CPython argparse Namespace
|
|
18
|
+
shape); a typed-union escape hatch is **not** stored -- see the
|
|
19
|
+
argparse Future Work table in ``docs/MACRO_DESIGN.md`` for the
|
|
20
|
+
phasing reason and the planned pre-pass-6 expansion that would
|
|
21
|
+
unlock it.
|
|
22
|
+
|
|
23
|
+
v1 surface, future-work tiers, and known CPython divergences are
|
|
24
|
+
tracked in ``docs/MACRO_DESIGN.md`` (the argparse use-case section)
|
|
25
|
+
and mirrored in ``docs/STDLIB_ROADMAP.md``. Keeping the canonical
|
|
26
|
+
list there avoids drift between the macro module and the design doc.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from tpyc.macro_api import (
|
|
30
|
+
builder_macro, builder_method, builder_returns, builder_terminal,
|
|
31
|
+
BuilderContext, MacroArg, MacroArgs, TypeInfo, MacroError,
|
|
32
|
+
macro_deps, types, ast, Type,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
macro_deps("tpy", "sys")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Default ``prog`` used in synthesized usage / help / error text when
|
|
39
|
+
# the user doesn't pass ``prog=`` to ``ArgumentParser``. Hardcoded for
|
|
40
|
+
# v1; CPython uses ``os.path.basename(sys.argv[0])`` -- runtime-derived
|
|
41
|
+
# prog is Tier 2 follow-up. Defined up here (before any helper that
|
|
42
|
+
# uses it as a default-arg) so module load order is well-defined.
|
|
43
|
+
_DEFAULT_PROG = "prog"
|
|
44
|
+
_DEFAULT_ERROR_PREFIX = f"{_DEFAULT_PROG}: error: "
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Internal: ArgSpec -- one registered argument
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
class _ArgSpec:
|
|
52
|
+
"""One @builder_method add_argument(...) call, fully resolved at
|
|
53
|
+
macro time. Carries everything the terminal needs to synthesize
|
|
54
|
+
init / dispatch / record-construction code.
|
|
55
|
+
"""
|
|
56
|
+
__slots__ = (
|
|
57
|
+
"is_flag", "flag_names", "name", "dest",
|
|
58
|
+
"type_info", "is_arg_type", "action", "default", "has_default",
|
|
59
|
+
"const", "has_const",
|
|
60
|
+
"choices", "required",
|
|
61
|
+
"nargs",
|
|
62
|
+
"help_text",
|
|
63
|
+
"metavar",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self, *, is_flag: bool, flag_names: list[str], name: str,
|
|
68
|
+
dest: str, type_info: TypeInfo, is_arg_type: bool,
|
|
69
|
+
action: str, default, has_default: bool,
|
|
70
|
+
const=None, has_const: bool = False,
|
|
71
|
+
choices: list | None = None, required: bool = False,
|
|
72
|
+
nargs=None, help_text: str | None = None,
|
|
73
|
+
metavar: str | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
self.is_flag = is_flag
|
|
76
|
+
self.flag_names = flag_names
|
|
77
|
+
self.name = name
|
|
78
|
+
self.dest = dest
|
|
79
|
+
self.type_info = type_info # resolved ``type=`` (defaults to str)
|
|
80
|
+
self.is_arg_type = is_arg_type # True iff type uses from_arg factory
|
|
81
|
+
self.action = action
|
|
82
|
+
self.default = default
|
|
83
|
+
self.has_default = has_default
|
|
84
|
+
self.const = const
|
|
85
|
+
self.has_const = has_const
|
|
86
|
+
self.choices = choices # None or list of macro-time literals
|
|
87
|
+
self.required = required # always True for positionals; user-controllable for flags
|
|
88
|
+
self.nargs = nargs # None | "?" | "*" | "+" | int
|
|
89
|
+
self.help_text = help_text # source text for the `--help` printer
|
|
90
|
+
self.metavar = metavar # display name override for usage / help
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def is_list_field(self) -> bool:
|
|
94
|
+
"""True when the synthesized record field is list[T]."""
|
|
95
|
+
if self.action in ("append", "extend"):
|
|
96
|
+
return True
|
|
97
|
+
if self.action == "store" and isinstance(self.nargs, int):
|
|
98
|
+
return True
|
|
99
|
+
if self.action == "store" and self.nargs in ("*", "+"):
|
|
100
|
+
return True
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def is_optional_field(self) -> bool:
|
|
105
|
+
"""True when the synthesized field is wrapped in Optional[T].
|
|
106
|
+
|
|
107
|
+
Mirrors CPython argparse: an optional flag that wasn't given
|
|
108
|
+
on the command line and has no ``default=`` returns ``None``.
|
|
109
|
+
Positional arguments are always required (or carry an explicit
|
|
110
|
+
default for ``nargs='?'``), so positionals never produce
|
|
111
|
+
Optional fields.
|
|
112
|
+
|
|
113
|
+
For list-typed actions (``append`` / ``extend`` / ``store``
|
|
114
|
+
with multi-valued nargs), an absent flag also produces ``None``
|
|
115
|
+
rather than an empty list -- the synthesizer accumulates into
|
|
116
|
+
a local list and reconciles to ``None`` post-loop when the
|
|
117
|
+
flag was never seen.
|
|
118
|
+
"""
|
|
119
|
+
if not self.is_flag:
|
|
120
|
+
return False
|
|
121
|
+
if self.has_default:
|
|
122
|
+
return False
|
|
123
|
+
if self.required:
|
|
124
|
+
return False
|
|
125
|
+
if self.is_list_field:
|
|
126
|
+
return True
|
|
127
|
+
if self.action == "store_const":
|
|
128
|
+
return True
|
|
129
|
+
if self.action == "store" and self.nargs == "?" and not self.has_const:
|
|
130
|
+
return True
|
|
131
|
+
if self.action == "store" and self.nargs is None:
|
|
132
|
+
return True
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def is_optional_list_field(self) -> bool:
|
|
137
|
+
"""True when the synthesized field is ``Optional[list[T]]``.
|
|
138
|
+
|
|
139
|
+
These specs use the accumulator + post-loop reconciliation
|
|
140
|
+
pattern: the flag handler writes into an accumulator local,
|
|
141
|
+
and the post-loop fixup either copies it into the dest field
|
|
142
|
+
(flag was seen) or leaves the dest at ``None``.
|
|
143
|
+
"""
|
|
144
|
+
return self.is_list_field and self.is_optional_field
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def field_type(self):
|
|
148
|
+
# Field type follows action / nargs.
|
|
149
|
+
if self.action in ("store_true", "store_false"):
|
|
150
|
+
return types.bool
|
|
151
|
+
if self.action == "count":
|
|
152
|
+
return types.bigint
|
|
153
|
+
if self.action == "store_const":
|
|
154
|
+
scalar = _python_value_type_info(self.const).raw_type
|
|
155
|
+
else:
|
|
156
|
+
scalar = self.type_info.raw_type
|
|
157
|
+
if self.is_optional_list_field:
|
|
158
|
+
return types.optional(types.list(scalar))
|
|
159
|
+
if self.is_list_field:
|
|
160
|
+
return types.list(scalar)
|
|
161
|
+
if self.is_optional_field:
|
|
162
|
+
return types.optional(scalar)
|
|
163
|
+
return scalar
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Internal: resolution helpers
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
# Actions that consume value(s) from argv and apply ``type=`` to them.
|
|
171
|
+
_VALUE_TAKING_ACTIONS: frozenset[str] = frozenset({"store", "append", "extend"})
|
|
172
|
+
|
|
173
|
+
# Actions that take no value (the flag itself fully specifies the result).
|
|
174
|
+
_VALUE_FREE_ACTIONS: frozenset[str] = frozenset(
|
|
175
|
+
{"store_true", "store_false", "count", "store_const"}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Actions that produce a list-typed field.
|
|
179
|
+
_LIST_ACTIONS: frozenset[str] = frozenset({"append", "extend"})
|
|
180
|
+
|
|
181
|
+
_ALLOWED_ACTIONS: frozenset[str] = _VALUE_TAKING_ACTIONS | _VALUE_FREE_ACTIONS
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Default ``type=`` is ``str`` (CPython parity). Cached as a TypeInfo
|
|
185
|
+
# so callers don't have to special-case the absent-kwarg path.
|
|
186
|
+
_STR_TYPE_INFO: TypeInfo = TypeInfo.from_tpy_type(types.str)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _is_allowed_arg_type(ti: TypeInfo) -> bool:
|
|
190
|
+
"""Whether ``type=<ti>`` is supported as a value-taking arg type.
|
|
191
|
+
|
|
192
|
+
Keyed on TypeInfo's category helpers rather than name lookup so new
|
|
193
|
+
acceptable types get picked up via the type system, not a string
|
|
194
|
+
table. ``is_float`` covers both ``float`` (Float64) and ``Float32``
|
|
195
|
+
-- both lower to a constructor that accepts ``str`` at runtime.
|
|
196
|
+
"""
|
|
197
|
+
return ti.is_str or ti.is_bigint or ti.is_int or ti.is_float
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _is_arg_type(ctx: BuilderContext, ti: TypeInfo) -> bool:
|
|
201
|
+
"""Whether ``ti`` is a user type with a ``@staticmethod from_arg(s: str) -> Self``.
|
|
202
|
+
|
|
203
|
+
This is the duck-typed escape hatch that lets users plug in custom
|
|
204
|
+
types (Path, datetime, domain records) without a built-in
|
|
205
|
+
enumeration: any record exposing such a factory is accepted, and
|
|
206
|
+
the synthesized parse fn calls ``T.from_arg(token)`` for it.
|
|
207
|
+
Sema checks the param/return-type details when the synthesized
|
|
208
|
+
call is analyzed -- the macro just gates on existence + staticness.
|
|
209
|
+
"""
|
|
210
|
+
return ctx.get_static_method_return_type(ti, "from_arg") is not None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _arg_type_name(ti: TypeInfo) -> str:
|
|
214
|
+
"""User-facing / source-text name for an arg type.
|
|
215
|
+
|
|
216
|
+
Doubles as the constructor expression for value coercion: ``int(s)``
|
|
217
|
+
for BigInt, ``Int32(s)`` for fixed-width, ``float(s)`` for Float64,
|
|
218
|
+
``Float32(s)`` for Float32. The ``str`` case is handled by callers
|
|
219
|
+
that skip wrapping (no constructor needed when the field stays a
|
|
220
|
+
plain string). ``is_float32`` is checked before ``is_float`` because
|
|
221
|
+
``is_float`` is true for both Float64 and Float32.
|
|
222
|
+
"""
|
|
223
|
+
if ti.is_str:
|
|
224
|
+
return "str"
|
|
225
|
+
if ti.is_bigint:
|
|
226
|
+
return "int"
|
|
227
|
+
if ti.is_int:
|
|
228
|
+
return ti.int_type_name
|
|
229
|
+
if ti.is_float32:
|
|
230
|
+
return "Float32"
|
|
231
|
+
if ti.is_float:
|
|
232
|
+
return "float"
|
|
233
|
+
# Should not occur once _is_allowed_arg_type gates entries; the
|
|
234
|
+
# raw_type's own repr is the safest fallback for diagnostics.
|
|
235
|
+
return str(ti.raw_type)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _python_value_type_info(value) -> TypeInfo:
|
|
239
|
+
"""TypeInfo for a Python literal value extracted via
|
|
240
|
+
eval_literal_or_final. Used to derive a record field type from a
|
|
241
|
+
``const=`` literal so ``store_const`` and ``store + type=`` agree
|
|
242
|
+
on the field type for the same conceptual value.
|
|
243
|
+
"""
|
|
244
|
+
if isinstance(value, bool):
|
|
245
|
+
return TypeInfo.from_tpy_type(types.bool)
|
|
246
|
+
if isinstance(value, int):
|
|
247
|
+
return TypeInfo.from_tpy_type(types.bigint)
|
|
248
|
+
if isinstance(value, float):
|
|
249
|
+
return TypeInfo.from_tpy_type(types.float64)
|
|
250
|
+
if isinstance(value, str):
|
|
251
|
+
return TypeInfo.from_tpy_type(types.str)
|
|
252
|
+
raise MacroError(
|
|
253
|
+
f"argparse: const= must be a bool/int/float/str literal, "
|
|
254
|
+
f"got {type(value).__name__}"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _resolve_type_info(
|
|
259
|
+
ctx: BuilderContext, args: MacroArgs,
|
|
260
|
+
) -> tuple[TypeInfo, bool]:
|
|
261
|
+
"""Read ``type=`` and return ``(TypeInfo, is_arg_type)``.
|
|
262
|
+
|
|
263
|
+
Defaults to ``(str, False)``. The second tuple element is True when
|
|
264
|
+
the type is a user record providing the ``ArgType`` factory hook
|
|
265
|
+
(see ``_is_arg_type``); callers consult it to decide between
|
|
266
|
+
``T(arg)`` and ``T.from_arg(arg)`` at synthesis time.
|
|
267
|
+
"""
|
|
268
|
+
ti = ctx.kwarg_type(args, "type")
|
|
269
|
+
if ti is None:
|
|
270
|
+
return _STR_TYPE_INFO, False
|
|
271
|
+
if _is_allowed_arg_type(ti):
|
|
272
|
+
return ti, False
|
|
273
|
+
if _is_arg_type(ctx, ti):
|
|
274
|
+
# Builder-trace passes user-defined types as placeholders with
|
|
275
|
+
# ``_tpy_type=None``; resolve_type_info rebuilds them on the
|
|
276
|
+
# registered record's qname-bearing NominalType so downstream
|
|
277
|
+
# field types and codegen identity-match sema-resolved
|
|
278
|
+
# references in the synthesized parse function body.
|
|
279
|
+
return ctx.resolve_type_info(ti), True
|
|
280
|
+
ctx.error(
|
|
281
|
+
f"argparse: unsupported type={ti.name!r}; "
|
|
282
|
+
f"supported: int, float, str, "
|
|
283
|
+
f"Int8/16/32/64, UInt8/16/32/64, Float32, "
|
|
284
|
+
f"or any record with @staticmethod from_arg(s: str) -> Self"
|
|
285
|
+
)
|
|
286
|
+
return ti, False
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _dest_for_flags(flag_names: list[str], explicit_dest: str | None) -> str:
|
|
290
|
+
"""Compute the destination field name from the flag spelling.
|
|
291
|
+
|
|
292
|
+
Mirrors CPython argparse: prefer the long flag with leading dashes
|
|
293
|
+
stripped and remaining dashes replaced by underscores; otherwise
|
|
294
|
+
use the (single) short flag.
|
|
295
|
+
"""
|
|
296
|
+
if explicit_dest is not None:
|
|
297
|
+
return explicit_dest
|
|
298
|
+
long_flags = [f for f in flag_names if f.startswith("--")]
|
|
299
|
+
if long_flags:
|
|
300
|
+
return long_flags[0][2:].replace("-", "_")
|
|
301
|
+
short = flag_names[0]
|
|
302
|
+
return short[1:] if short.startswith("-") else short
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _is_flag(name: str) -> bool:
|
|
306
|
+
return name.startswith("-")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _unwrap_optional(t):
|
|
310
|
+
"""Strip one ``Optional[...]`` wrapper if present, else return ``t``
|
|
311
|
+
unchanged. Used to compare per-sub field types under the flat
|
|
312
|
+
namespace where ``T`` and ``Optional[T]`` should be treated as
|
|
313
|
+
equivalent (the top record always wraps once anyway).
|
|
314
|
+
"""
|
|
315
|
+
ti = TypeInfo.from_tpy_type(t)
|
|
316
|
+
inner = ti.unwrap_optional()
|
|
317
|
+
return inner.raw_type if inner is not None else t
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _resolve_nargs_kwarg(ctx: BuilderContext, args: MacroArgs):
|
|
321
|
+
"""Read ``nargs=`` and validate. Returns None | '?' | '*' | '+' | int."""
|
|
322
|
+
ma = ctx.kwarg_macroarg(args, "nargs")
|
|
323
|
+
if ma is None:
|
|
324
|
+
return None
|
|
325
|
+
v = ctx.eval_literal_or_final(ma.expr)
|
|
326
|
+
if isinstance(v, bool) or not isinstance(v, (str, int)):
|
|
327
|
+
ctx.error(
|
|
328
|
+
f"argparse: nargs= must be a string '?'/'*'/'+' or a "
|
|
329
|
+
f"positive int, got {type(v).__name__}"
|
|
330
|
+
)
|
|
331
|
+
if isinstance(v, str):
|
|
332
|
+
if v not in ("?", "*", "+"):
|
|
333
|
+
ctx.error(f"argparse: nargs={v!r} is not supported")
|
|
334
|
+
return v
|
|
335
|
+
# int
|
|
336
|
+
if v <= 0:
|
|
337
|
+
ctx.error(f"argparse: nargs= must be a positive integer, got {v}")
|
|
338
|
+
return v
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _check_default_elem(ctx: BuilderContext, value, ti: TypeInfo) -> None:
|
|
342
|
+
"""Validate a single element of a list-typed ``default=`` against
|
|
343
|
+
the spec's resolved ``type=``. Bool literals are rejected upfront
|
|
344
|
+
even for int / float types, since Python's ``isinstance(True, int)``
|
|
345
|
+
quirk would otherwise let ``default=[True]`` slip through.
|
|
346
|
+
"""
|
|
347
|
+
name = _arg_type_name(ti)
|
|
348
|
+
if isinstance(value, bool):
|
|
349
|
+
ctx.error(f"argparse: list default element must be {name}, got bool")
|
|
350
|
+
if ti.is_str:
|
|
351
|
+
ok = isinstance(value, str)
|
|
352
|
+
elif ti.is_bigint or ti.is_int:
|
|
353
|
+
ok = isinstance(value, int)
|
|
354
|
+
elif ti.is_float:
|
|
355
|
+
ok = isinstance(value, (int, float))
|
|
356
|
+
else:
|
|
357
|
+
ok = True # _is_allowed_arg_type already gated unsupported types
|
|
358
|
+
if not ok:
|
|
359
|
+
ctx.error(
|
|
360
|
+
f"argparse: list default element must be {name}, "
|
|
361
|
+
f"got {type(value).__name__}"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _action_implicit_default(action: str):
|
|
366
|
+
"""Default value applied when the user omits default= for a value-
|
|
367
|
+
free action. Mirrors CPython argparse where it can: store_true
|
|
368
|
+
defaults to False, store_false defaults to True, count and
|
|
369
|
+
store_const default to None there but we materialize 0 / None to
|
|
370
|
+
keep the typed record well-formed.
|
|
371
|
+
"""
|
|
372
|
+
if action == "store_true":
|
|
373
|
+
return False
|
|
374
|
+
if action == "store_false":
|
|
375
|
+
return True
|
|
376
|
+
if action == "count":
|
|
377
|
+
return 0
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
# Internal: shared add_argument body
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
def _build_arg_spec(
|
|
386
|
+
ctx: BuilderContext, args: MacroArgs, *,
|
|
387
|
+
add_help_reserved: bool,
|
|
388
|
+
) -> _ArgSpec:
|
|
389
|
+
"""Validate add_argument() kwargs and construct an _ArgSpec.
|
|
390
|
+
|
|
391
|
+
Shared between ArgumentParser.add_argument and
|
|
392
|
+
_SubparserBuilder.add_argument. ``add_help_reserved`` controls
|
|
393
|
+
whether ``-h``/``--help`` are rejected as user-registered flag
|
|
394
|
+
names (true for the top-level parser when ``add_help=True``;
|
|
395
|
+
always false for sub-parsers in v1, since sub-help is not yet
|
|
396
|
+
auto-emitted).
|
|
397
|
+
"""
|
|
398
|
+
names = ctx.positional_strs(args)
|
|
399
|
+
if not names:
|
|
400
|
+
ctx.error("argparse: add_argument() requires at least one name")
|
|
401
|
+
|
|
402
|
+
help_text = ctx.kwarg_str(args, "help")
|
|
403
|
+
metavar = ctx.kwarg_str(args, "metavar")
|
|
404
|
+
explicit_dest = ctx.kwarg_str(args, "dest")
|
|
405
|
+
required_kw = ctx.kwarg_bool(args, "required", False)
|
|
406
|
+
choices_arg = ctx.kwarg_macroarg(args, "choices")
|
|
407
|
+
choices: list | None = None
|
|
408
|
+
if choices_arg is not None:
|
|
409
|
+
choices = ctx.eval_sequence_of_literal_or_final(choices_arg.expr)
|
|
410
|
+
if not choices:
|
|
411
|
+
ctx.error("argparse: choices= must be non-empty")
|
|
412
|
+
|
|
413
|
+
flags = [n for n in names if _is_flag(n)]
|
|
414
|
+
positionals = [n for n in names if not _is_flag(n)]
|
|
415
|
+
if flags and positionals:
|
|
416
|
+
ctx.error(
|
|
417
|
+
"argparse: add_argument() positional and optional names "
|
|
418
|
+
"cannot be mixed"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
action = ctx.kwarg_str(args, "action") or "store"
|
|
422
|
+
if action not in _ALLOWED_ACTIONS:
|
|
423
|
+
ctx.error(
|
|
424
|
+
f"argparse: action={action!r} is not supported "
|
|
425
|
+
f"(supported: store, store_true, store_false, count, "
|
|
426
|
+
f"append, extend, store_const)"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# type= is meaningful only for value-taking actions. Reject
|
|
430
|
+
# explicit type= on the value-free ones to mirror CPython.
|
|
431
|
+
if action in _VALUE_FREE_ACTIONS and ctx.kwarg_macroarg(args, "type") is not None:
|
|
432
|
+
ctx.error(
|
|
433
|
+
f"argparse: type= cannot be combined with action={action!r}"
|
|
434
|
+
)
|
|
435
|
+
type_info, is_arg_type = _resolve_type_info(ctx, args)
|
|
436
|
+
|
|
437
|
+
nargs = _resolve_nargs_kwarg(ctx, args)
|
|
438
|
+
# Custom ``type=<MyType>`` (ArgType) v1: ``store`` /
|
|
439
|
+
# ``append`` / ``extend`` plus all four nargs shapes are
|
|
440
|
+
# supported via the same synthesis as the built-in types
|
|
441
|
+
# (``T.from_arg`` is just a different value-coercion than the
|
|
442
|
+
# builtin constructor). ``choices=`` stays out of v1 because
|
|
443
|
+
# comparing the unparsed token against a literal set would
|
|
444
|
+
# diverge from CPython's "compare parsed values" semantics.
|
|
445
|
+
if is_arg_type and choices is not None:
|
|
446
|
+
ctx.error(
|
|
447
|
+
"argparse: type=<custom> does not support choices= in v1"
|
|
448
|
+
)
|
|
449
|
+
if nargs is not None and action in _VALUE_FREE_ACTIONS and action != "store_const":
|
|
450
|
+
ctx.error(
|
|
451
|
+
f"argparse: nargs= cannot be combined with action={action!r}"
|
|
452
|
+
)
|
|
453
|
+
# store_const + nargs is allowed only as nargs='?' (and means
|
|
454
|
+
# "if flag-bare use const, if flag-with-value use value, if
|
|
455
|
+
# absent use default"). For now reject the explicit pairing.
|
|
456
|
+
if action == "store_const" and nargs is not None:
|
|
457
|
+
ctx.error(
|
|
458
|
+
"argparse: nargs= is not supported with action='store_const'"
|
|
459
|
+
)
|
|
460
|
+
# extend without nargs is the confusing CPython case (iterates
|
|
461
|
+
# the converted value); require nargs explicitly.
|
|
462
|
+
if action == "extend" and nargs is None:
|
|
463
|
+
ctx.error(
|
|
464
|
+
"argparse: action='extend' requires nargs= (otherwise "
|
|
465
|
+
"the converted value gets iterated, which is rarely "
|
|
466
|
+
"what callers want)"
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# const= is required for store_const and for store + nargs='?'
|
|
470
|
+
# (where it's the value used when the flag appears bare).
|
|
471
|
+
const_arg = ctx.kwarg_macroarg(args, "const")
|
|
472
|
+
const = None
|
|
473
|
+
has_const = False
|
|
474
|
+
if const_arg is not None:
|
|
475
|
+
if action == "store_const":
|
|
476
|
+
pass # always allowed
|
|
477
|
+
elif action == "store" and nargs == "?":
|
|
478
|
+
pass # const provides the bare-flag value
|
|
479
|
+
else:
|
|
480
|
+
ctx.error(
|
|
481
|
+
f"argparse: const= is only valid with action='store_const' "
|
|
482
|
+
f"or action='store' + nargs='?' (got action={action!r}, "
|
|
483
|
+
f"nargs={nargs!r})"
|
|
484
|
+
)
|
|
485
|
+
const = ctx.eval_literal_or_final(const_arg.expr)
|
|
486
|
+
has_const = True
|
|
487
|
+
elif action == "store_const":
|
|
488
|
+
ctx.error("argparse: action='store_const' requires const=")
|
|
489
|
+
|
|
490
|
+
# Default handling. ``default=`` is a literal at macro time;
|
|
491
|
+
# absent for required positionals, None-typed for absent flags
|
|
492
|
+
# without explicit default. List literals are accepted only
|
|
493
|
+
# for list-typed actions (append / extend / store + nargs=*/+/N).
|
|
494
|
+
default_arg = ctx.kwarg_macroarg(args, "default")
|
|
495
|
+
if default_arg is not None:
|
|
496
|
+
default = ctx.eval_literal_or_final(default_arg.expr)
|
|
497
|
+
has_default = True
|
|
498
|
+
if is_arg_type and not isinstance(default, str):
|
|
499
|
+
# Mirrors CPython's "string defaults run through type="
|
|
500
|
+
# rule: a single string literal is routed through
|
|
501
|
+
# ``T.from_arg`` at parse-fn entry. List defaults
|
|
502
|
+
# diverge from CPython (CPython keeps the elements as
|
|
503
|
+
# strings while TPy needs them typed as T to fit a
|
|
504
|
+
# ``list[T]`` field), so they're rejected; users can
|
|
505
|
+
# omit ``default=`` to get ``Optional[list[T]]`` or an
|
|
506
|
+
# empty ``list[T]``.
|
|
507
|
+
ctx.error(
|
|
508
|
+
f"argparse: type=<custom> with default= requires "
|
|
509
|
+
f"a string literal (got {type(default).__name__})"
|
|
510
|
+
)
|
|
511
|
+
if isinstance(default, list):
|
|
512
|
+
list_action_ok = (
|
|
513
|
+
action in _LIST_ACTIONS
|
|
514
|
+
or (action == "store"
|
|
515
|
+
and (isinstance(nargs, int) or nargs in ("*", "+")))
|
|
516
|
+
)
|
|
517
|
+
if not list_action_ok:
|
|
518
|
+
ctx.error(
|
|
519
|
+
f"argparse: list default is only valid for "
|
|
520
|
+
f"list-typed actions (append/extend or store + "
|
|
521
|
+
f"nargs=*/+/<int>); got action={action!r}, "
|
|
522
|
+
f"nargs={nargs!r}"
|
|
523
|
+
)
|
|
524
|
+
for v in default:
|
|
525
|
+
_check_default_elem(ctx, v, type_info)
|
|
526
|
+
else:
|
|
527
|
+
default = _action_implicit_default(action)
|
|
528
|
+
has_default = default is not None
|
|
529
|
+
|
|
530
|
+
if positionals:
|
|
531
|
+
if action != "store":
|
|
532
|
+
ctx.error(
|
|
533
|
+
f"argparse: action={action!r} is only valid for "
|
|
534
|
+
f"optional flags, not positional arguments"
|
|
535
|
+
)
|
|
536
|
+
if has_default and nargs != "?":
|
|
537
|
+
# nargs='?' positionals can use default= when the slot
|
|
538
|
+
# is missing from argv; non-'?' positionals are always
|
|
539
|
+
# required.
|
|
540
|
+
ctx.error(
|
|
541
|
+
"argparse: positional arguments may only specify "
|
|
542
|
+
"default= when nargs='?'"
|
|
543
|
+
)
|
|
544
|
+
if required_kw:
|
|
545
|
+
ctx.error(
|
|
546
|
+
"argparse: required= is meaningless for positional "
|
|
547
|
+
"arguments (they are always required)"
|
|
548
|
+
)
|
|
549
|
+
name = positionals[0]
|
|
550
|
+
dest = explicit_dest if explicit_dest is not None else name
|
|
551
|
+
return _ArgSpec(
|
|
552
|
+
is_flag=False, flag_names=[], name=name, dest=dest,
|
|
553
|
+
type_info=type_info, is_arg_type=is_arg_type, action=action,
|
|
554
|
+
default=default, has_default=has_default,
|
|
555
|
+
choices=choices, required=True,
|
|
556
|
+
nargs=nargs, help_text=help_text,
|
|
557
|
+
metavar=metavar,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Optional flag(s). Absent flags fall back to their default
|
|
561
|
+
# (or None when no default is given; field type becomes
|
|
562
|
+
# Optional[T] or Optional[list[T]] depending on action).
|
|
563
|
+
# nargs='?' on a flag uses const when the flag is bare and
|
|
564
|
+
# value when the flag carries one.
|
|
565
|
+
name = flags[0]
|
|
566
|
+
dest = _dest_for_flags(flags, explicit_dest)
|
|
567
|
+
if add_help_reserved:
|
|
568
|
+
for f in flags:
|
|
569
|
+
if f in ("-h", "--help"):
|
|
570
|
+
ctx.error(
|
|
571
|
+
"argparse: -h / --help is reserved by the "
|
|
572
|
+
"auto-generated help printer; pass "
|
|
573
|
+
"add_help=False to ArgumentParser() to "
|
|
574
|
+
"register your own"
|
|
575
|
+
)
|
|
576
|
+
return _ArgSpec(
|
|
577
|
+
is_flag=True, flag_names=list(flags), name=name, dest=dest,
|
|
578
|
+
type_info=type_info, is_arg_type=is_arg_type, action=action,
|
|
579
|
+
default=default, has_default=has_default,
|
|
580
|
+
const=const, has_const=has_const,
|
|
581
|
+
choices=choices, required=required_kw,
|
|
582
|
+
nargs=nargs, help_text=help_text,
|
|
583
|
+
metavar=metavar,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
# ---------------------------------------------------------------------------
|
|
588
|
+
# Public macro: nested builders for subparsers
|
|
589
|
+
# ---------------------------------------------------------------------------
|
|
590
|
+
|
|
591
|
+
class _SubparserBuilder:
|
|
592
|
+
"""One sub-parser registered via ``parser.add_subparsers().add_parser(name)``.
|
|
593
|
+
|
|
594
|
+
Holds its own arg specs; the top-level terminal walks every
|
|
595
|
+
sub-parser registered on the enclosing _SubparsersAction to
|
|
596
|
+
synthesize per-sub records and parse functions. v1 does not
|
|
597
|
+
auto-emit ``--help`` on sub-parsers, so ``add_help_reserved`` is
|
|
598
|
+
always false for sub-arg validation -- users may register their
|
|
599
|
+
own ``-h`` / ``--help`` here.
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
def __init__(self, name: str, help_text: str | None) -> None:
|
|
603
|
+
self.name = name
|
|
604
|
+
self.help_text = help_text
|
|
605
|
+
self.specs: list[_ArgSpec] = []
|
|
606
|
+
self._dest_seen: set[str] = set()
|
|
607
|
+
|
|
608
|
+
@builder_method
|
|
609
|
+
def add_argument(self, ctx: BuilderContext, args: MacroArgs) -> None:
|
|
610
|
+
spec = _build_arg_spec(ctx, args, add_help_reserved=False)
|
|
611
|
+
if spec.dest in self._dest_seen:
|
|
612
|
+
ctx.error(f"argparse: duplicate argument destination {spec.dest!r}")
|
|
613
|
+
self._dest_seen.add(spec.dest)
|
|
614
|
+
self.specs.append(spec)
|
|
615
|
+
|
|
616
|
+
@builder_method
|
|
617
|
+
def add_subparsers(self, ctx: BuilderContext, args: MacroArgs) -> None:
|
|
618
|
+
# No nested subparsers in v1: the top-level synth assumes a
|
|
619
|
+
# flat (top, sub*) shape so it can emit a single union field.
|
|
620
|
+
# Lifting this requires recursive sub-record synthesis and a
|
|
621
|
+
# nested union representation -- defer.
|
|
622
|
+
ctx.error(
|
|
623
|
+
"argparse: nested add_subparsers() is not supported in v1"
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
class _SubparsersAction:
|
|
628
|
+
"""Result of ``parser.add_subparsers()``.
|
|
629
|
+
|
|
630
|
+
Tracks the subparser dispatch parameters (`dest=`, `required=`,
|
|
631
|
+
optional `help=`/`title=` for help-formatting hooks) and the
|
|
632
|
+
list of registered sub-parsers. Reachable through the trace via
|
|
633
|
+
``add_parser(name)``, which returns a fresh _SubparserBuilder.
|
|
634
|
+
"""
|
|
635
|
+
|
|
636
|
+
def __init__(
|
|
637
|
+
self, *, dest: str, required: bool,
|
|
638
|
+
title: str | None, action_help: str | None,
|
|
639
|
+
loc: object,
|
|
640
|
+
) -> None:
|
|
641
|
+
self.dest = dest
|
|
642
|
+
self.required = required
|
|
643
|
+
self.title = title
|
|
644
|
+
self.action_help = action_help
|
|
645
|
+
self.loc = loc
|
|
646
|
+
self.subparsers: list[_SubparserBuilder] = []
|
|
647
|
+
self._names_seen: set[str] = set()
|
|
648
|
+
|
|
649
|
+
@builder_returns(_SubparserBuilder)
|
|
650
|
+
def add_parser(
|
|
651
|
+
self, ctx: BuilderContext, args: MacroArgs,
|
|
652
|
+
) -> _SubparserBuilder:
|
|
653
|
+
if not args.positional:
|
|
654
|
+
ctx.error("argparse: add_parser() requires a name argument")
|
|
655
|
+
name = ctx.positional_str(args, 0)
|
|
656
|
+
if name in self._names_seen:
|
|
657
|
+
ctx.error(f"argparse: duplicate sub-parser name {name!r}")
|
|
658
|
+
if not name or name.startswith("-"):
|
|
659
|
+
ctx.error(
|
|
660
|
+
f"argparse: sub-parser name must be a non-empty "
|
|
661
|
+
f"non-flag token, got {name!r}"
|
|
662
|
+
)
|
|
663
|
+
self._names_seen.add(name)
|
|
664
|
+
help_text = ctx.kwarg_str(args, "help")
|
|
665
|
+
sub = _SubparserBuilder(name=name, help_text=help_text)
|
|
666
|
+
self.subparsers.append(sub)
|
|
667
|
+
return sub
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
# ---------------------------------------------------------------------------
|
|
671
|
+
# Public macro: ArgumentParser
|
|
672
|
+
# ---------------------------------------------------------------------------
|
|
673
|
+
|
|
674
|
+
@builder_macro
|
|
675
|
+
class ArgumentParser:
|
|
676
|
+
def __init__(self, ctx: BuilderContext, args: MacroArgs) -> None:
|
|
677
|
+
self.description: str | None = ctx.kwarg_str(args, "description")
|
|
678
|
+
# Help-text customization. ``prog`` substitutes for the
|
|
679
|
+
# default ``"prog"`` placeholder in the usage line and
|
|
680
|
+
# parse-error prefix; ``usage`` overrides the auto-generated
|
|
681
|
+
# usage line entirely (CPython prefixes it with ``"usage: "``);
|
|
682
|
+
# ``epilog`` is appended after the options block in --help.
|
|
683
|
+
# ``add_help`` controls whether the ``-h`` / ``--help`` printer
|
|
684
|
+
# is auto-emitted and the flag names reserved.
|
|
685
|
+
self.prog: str | None = ctx.kwarg_str(args, "prog")
|
|
686
|
+
self.usage: str | None = ctx.kwarg_str(args, "usage")
|
|
687
|
+
self.epilog: str | None = ctx.kwarg_str(args, "epilog")
|
|
688
|
+
self.add_help: bool = ctx.kwarg_bool(args, "add_help", True)
|
|
689
|
+
self.specs: list[_ArgSpec] = []
|
|
690
|
+
self._dest_seen: set[str] = set()
|
|
691
|
+
# Set by add_subparsers(); a single _SubparsersAction holds
|
|
692
|
+
# every registered sub-parser. v1 allows at most one call.
|
|
693
|
+
self._subparsers: _SubparsersAction | None = None
|
|
694
|
+
|
|
695
|
+
@builder_method
|
|
696
|
+
def add_argument(self, ctx: BuilderContext, args: MacroArgs) -> None:
|
|
697
|
+
spec = _build_arg_spec(ctx, args, add_help_reserved=self.add_help)
|
|
698
|
+
if spec.dest in self._dest_seen:
|
|
699
|
+
ctx.error(f"argparse: duplicate argument destination {spec.dest!r}")
|
|
700
|
+
self._dest_seen.add(spec.dest)
|
|
701
|
+
self.specs.append(spec)
|
|
702
|
+
|
|
703
|
+
@builder_returns(_SubparsersAction)
|
|
704
|
+
def add_subparsers(
|
|
705
|
+
self, ctx: BuilderContext, args: MacroArgs,
|
|
706
|
+
) -> _SubparsersAction:
|
|
707
|
+
if self._subparsers is not None:
|
|
708
|
+
ctx.error(
|
|
709
|
+
"argparse: add_subparsers() can only be called once "
|
|
710
|
+
"per parser"
|
|
711
|
+
)
|
|
712
|
+
# CPython argparse rejects sub-parsers when any positional
|
|
713
|
+
# argument is already registered (the regex matcher can't
|
|
714
|
+
# disambiguate where the subcommand starts). v1 keeps that
|
|
715
|
+
# restriction; lifting it requires the regex matcher.
|
|
716
|
+
for s in self.specs:
|
|
717
|
+
if not s.is_flag:
|
|
718
|
+
ctx.error(
|
|
719
|
+
"argparse: add_subparsers() is not supported when "
|
|
720
|
+
"the parser also has positional arguments"
|
|
721
|
+
)
|
|
722
|
+
dest = ctx.kwarg_str(args, "dest") or "cmd"
|
|
723
|
+
if dest in self._dest_seen:
|
|
724
|
+
ctx.error(
|
|
725
|
+
f"argparse: subparsers dest={dest!r} collides with "
|
|
726
|
+
f"a top-level argument of the same dest"
|
|
727
|
+
)
|
|
728
|
+
required = ctx.kwarg_bool(args, "required", False)
|
|
729
|
+
title = ctx.kwarg_str(args, "title")
|
|
730
|
+
action_help = ctx.kwarg_str(args, "help")
|
|
731
|
+
action = _SubparsersAction(
|
|
732
|
+
dest=dest, required=required,
|
|
733
|
+
title=title, action_help=action_help,
|
|
734
|
+
loc=ctx.call_loc,
|
|
735
|
+
)
|
|
736
|
+
self._subparsers = action
|
|
737
|
+
return action
|
|
738
|
+
|
|
739
|
+
@builder_terminal
|
|
740
|
+
def parse_args(self, ctx: BuilderContext, args: MacroArgs) -> TypeInfo:
|
|
741
|
+
# CPython's ``parse_args()`` falls back to ``sys.argv[1:]``
|
|
742
|
+
# when called with no arguments. ``sys`` is injected into the
|
|
743
|
+
# user's macro_ns via this module's ``macro_deps("tpy", "sys")``
|
|
744
|
+
# declaration, so the synthesized rewrite can reference it
|
|
745
|
+
# without the user having imported sys explicitly.
|
|
746
|
+
if not args.positional:
|
|
747
|
+
# ``list(...)`` materializes a fresh list[str] from the
|
|
748
|
+
# span returned by ``sys.argv[1:]`` so the synthesized
|
|
749
|
+
# parse fn (which takes list[str]) accepts it.
|
|
750
|
+
args.positional.append(MacroArg(
|
|
751
|
+
expr=ast.quote_expr("list(sys.argv[1:])"),
|
|
752
|
+
type=TypeInfo("?", _tpy_type=None),
|
|
753
|
+
))
|
|
754
|
+
|
|
755
|
+
if self._subparsers is None:
|
|
756
|
+
return self._terminal_single(ctx, args)
|
|
757
|
+
return self._terminal_with_subparsers(ctx, args)
|
|
758
|
+
|
|
759
|
+
def _terminal_single(
|
|
760
|
+
self, ctx: BuilderContext, args: MacroArgs,
|
|
761
|
+
) -> TypeInfo:
|
|
762
|
+
# Validate positional layout before code synthesis: ``*``,
|
|
763
|
+
# ``+``, and ``?`` positionals must be the LAST positional --
|
|
764
|
+
# earlier ones can't be deterministically consumed without
|
|
765
|
+
# CPython's full regex-fitting algorithm.
|
|
766
|
+
positional_specs = [s for s in self.specs if not s.is_flag]
|
|
767
|
+
for i, s in enumerate(positional_specs):
|
|
768
|
+
if s.nargs in ("*", "+", "?") and i != len(positional_specs) - 1:
|
|
769
|
+
ctx.error(
|
|
770
|
+
f"argparse: positional {s.name!r} with "
|
|
771
|
+
f"nargs={s.nargs!r} must be the last positional"
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
record_name = ctx.fresh_module_name("argparse_args")
|
|
775
|
+
record_fields = [(s.dest, s.field_type) for s in self.specs]
|
|
776
|
+
record_type = ctx.emit_record(record_name, record_fields)
|
|
777
|
+
|
|
778
|
+
prog = self.prog if self.prog is not None else _DEFAULT_PROG
|
|
779
|
+
|
|
780
|
+
# Render usage once and reuse it for both the parse-error path
|
|
781
|
+
# and the help-printer body so we don't walk specs twice.
|
|
782
|
+
usage_text = _format_usage(
|
|
783
|
+
self.specs, prog=prog, usage=self.usage,
|
|
784
|
+
include_help_opt=self.add_help,
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# Synthesize the help-printer (when add_help=True) so the parse
|
|
788
|
+
# fn's `-h` / `--help` detection prelude can reference its name.
|
|
789
|
+
# Pre-render the entire help text at macro time -- the printer
|
|
790
|
+
# body is just `print(<literal>); sys.exit(0)`.
|
|
791
|
+
help_fn_name: str | None = None
|
|
792
|
+
if self.add_help:
|
|
793
|
+
help_fn_name = ctx.fresh_module_name("argparse_help")
|
|
794
|
+
help_text = _format_help_text(
|
|
795
|
+
self.specs, self.description,
|
|
796
|
+
usage_text=usage_text, epilog=self.epilog,
|
|
797
|
+
)
|
|
798
|
+
ctx.emit_function(
|
|
799
|
+
help_fn_name, [], types.void,
|
|
800
|
+
ast.quote(f"print({help_text!r})\nsys.exit(Int32(0))"),
|
|
801
|
+
)
|
|
802
|
+
body = _build_parse_body(
|
|
803
|
+
self.specs, record_name, help_fn_name, usage_text,
|
|
804
|
+
prog=prog, add_help=self.add_help,
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# ``argv`` is ``list[str]`` rather than ``Span[str]`` so an
|
|
808
|
+
# empty list literal at the call site (``parse_args([])``)
|
|
809
|
+
# infers its element type from the parameter -- empty-literal
|
|
810
|
+
# inference doesn't reach through Span[T] context today.
|
|
811
|
+
fn_name = ctx.fresh_module_name("argparse_parse")
|
|
812
|
+
ctx.emit_function(
|
|
813
|
+
fn_name,
|
|
814
|
+
[("argv", types.list(types.str))],
|
|
815
|
+
record_type.raw_type,
|
|
816
|
+
body,
|
|
817
|
+
)
|
|
818
|
+
ctx.replace_call(fn_name, args)
|
|
819
|
+
return record_type
|
|
820
|
+
|
|
821
|
+
def _terminal_with_subparsers(
|
|
822
|
+
self, ctx: BuilderContext, args: MacroArgs,
|
|
823
|
+
) -> TypeInfo:
|
|
824
|
+
"""Synthesize the subparsers-aware parser.
|
|
825
|
+
|
|
826
|
+
Layout (Phase 1, no property forwarders):
|
|
827
|
+
per_sub_record_i: <fields for sub_i> # one record per sub-parser
|
|
828
|
+
per_sub_parse_i: list[str] -> per_sub_record_i # one parse fn per sub
|
|
829
|
+
top_record: <common arg fields>
|
|
830
|
+
<dest=:str | Optional[str]> # CPython-shape subcommand name
|
|
831
|
+
<_subcommand: Union[Sub] | Optional[Union[Sub]]>
|
|
832
|
+
# TPy-only typed union
|
|
833
|
+
top_parse: list[str] -> top_record
|
|
834
|
+
|
|
835
|
+
Top-level parse:
|
|
836
|
+
- --help / -h scan (top help only; sub-help is v2)
|
|
837
|
+
- main loop dispatches common flags
|
|
838
|
+
- non-flag token -> sub-parser dispatch:
|
|
839
|
+
tail = list(argv[i+1:]); _subcommand = sub_parse_<name>(tail); break
|
|
840
|
+
- post-loop required-flag and required-subparser checks.
|
|
841
|
+
"""
|
|
842
|
+
sp = self._subparsers
|
|
843
|
+
assert sp is not None
|
|
844
|
+
if not sp.subparsers:
|
|
845
|
+
ctx.error(
|
|
846
|
+
"argparse: add_subparsers() requires at least one "
|
|
847
|
+
"add_parser() call before parse_args()"
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
# Per-sub vs common-arg collisions are checked further below
|
|
851
|
+
# when per-sub flat fields are added to the top record.
|
|
852
|
+
# ``add_subparsers`` already rejected ``sp.dest`` colliding
|
|
853
|
+
# with an existing common-arg dest at registration time.
|
|
854
|
+
top_dests = {s.dest for s in self.specs}
|
|
855
|
+
|
|
856
|
+
prog = self.prog if self.prog is not None else _DEFAULT_PROG
|
|
857
|
+
|
|
858
|
+
# Synthesize per-sub records + per-sub parse fns. Each sub's
|
|
859
|
+
# error path uses ``"<top-prog> <sub-name>"`` as the prog so
|
|
860
|
+
# error messages match CPython's "prog cmd: error: ..." shape.
|
|
861
|
+
sub_record_types: list = [] # parallel to sp.subparsers
|
|
862
|
+
sub_record_names: list[str] = [] # bare names, parallel to sp.subparsers
|
|
863
|
+
sub_parse_fn_names: list[str] = []
|
|
864
|
+
for sub in sp.subparsers:
|
|
865
|
+
positional_in_sub = [x for x in sub.specs if not x.is_flag]
|
|
866
|
+
for i, s in enumerate(positional_in_sub):
|
|
867
|
+
if s.nargs in ("*", "+", "?") and i != len(positional_in_sub) - 1:
|
|
868
|
+
ctx.error(
|
|
869
|
+
f"argparse: positional {s.name!r} with "
|
|
870
|
+
f"nargs={s.nargs!r} must be the last "
|
|
871
|
+
f"positional in sub-parser {sub.name!r}"
|
|
872
|
+
)
|
|
873
|
+
sub_record_name = ctx.fresh_module_name(f"argparse_{sub.name}_args")
|
|
874
|
+
sub_fields = [(s.dest, s.field_type) for s in sub.specs]
|
|
875
|
+
sub_record_type = ctx.emit_record(sub_record_name, sub_fields)
|
|
876
|
+
sub_record_types.append(sub_record_type)
|
|
877
|
+
sub_record_names.append(sub_record_name)
|
|
878
|
+
|
|
879
|
+
sub_prog = f"{prog} {sub.name}"
|
|
880
|
+
sub_usage_text = _format_usage(
|
|
881
|
+
sub.specs, prog=sub_prog, usage=None,
|
|
882
|
+
include_help_opt=False,
|
|
883
|
+
)
|
|
884
|
+
sub_body = _build_parse_body(
|
|
885
|
+
sub.specs, sub_record_name, None, sub_usage_text,
|
|
886
|
+
prog=sub_prog, add_help=False,
|
|
887
|
+
)
|
|
888
|
+
sub_fn_name = ctx.fresh_module_name(f"argparse_{sub.name}_parse")
|
|
889
|
+
ctx.emit_function(
|
|
890
|
+
sub_fn_name,
|
|
891
|
+
[("argv", types.list(types.str))],
|
|
892
|
+
sub_record_type.raw_type,
|
|
893
|
+
sub_body,
|
|
894
|
+
)
|
|
895
|
+
sub_parse_fn_names.append(sub_fn_name)
|
|
896
|
+
|
|
897
|
+
cmd_field_type = types.str if sp.required else types.optional(types.str)
|
|
898
|
+
|
|
899
|
+
# Per-sub flat fields on the top record. CPython argparse
|
|
900
|
+
# exposes per-sub args directly as Optional[T] attributes on
|
|
901
|
+
# the Namespace; we mirror that. A typed-union escape hatch
|
|
902
|
+
# ``_subcommand: A | B`` is intentionally NOT stored: users
|
|
903
|
+
# can't reference the synthesized sub-record names (they
|
|
904
|
+
# carry the ``__tpy_builder_`` private prefix), so a stored
|
|
905
|
+
# union would be unreachable. Once builder-trace expansion
|
|
906
|
+
# moves to a dedicated pre-pass-6 pass (see MACRO_DESIGN.md's
|
|
907
|
+
# "Move builder-trace expansion to a dedicated pre-pass-6
|
|
908
|
+
# pass"), property forwarders that match over the union
|
|
909
|
+
# become viable and the union storage can be added back as
|
|
910
|
+
# the underlying state.
|
|
911
|
+
flat_field_map: dict[str, list[tuple[int, _ArgSpec]]] = {}
|
|
912
|
+
for sub_idx, sub in enumerate(sp.subparsers):
|
|
913
|
+
for spec in sub.specs:
|
|
914
|
+
flat_field_map.setdefault(spec.dest, []).append(
|
|
915
|
+
(sub_idx, spec)
|
|
916
|
+
)
|
|
917
|
+
flat_fields: list[tuple[str, Type, list[tuple[int, _ArgSpec]]]] = []
|
|
918
|
+
# Reserve the cmd dest now -- the top record carries cmd as a
|
|
919
|
+
# stored field too, so a per-sub arg named the same as sp.dest
|
|
920
|
+
# would shadow it.
|
|
921
|
+
reserved_dests = {sp.dest}
|
|
922
|
+
for name in sorted(flat_field_map.keys()):
|
|
923
|
+
occurrences = flat_field_map[name]
|
|
924
|
+
if name in top_dests:
|
|
925
|
+
ctx.error(
|
|
926
|
+
f"argparse: per-sub argument {name!r} collides with "
|
|
927
|
+
f"a top-level argument of the same dest; rename one "
|
|
928
|
+
f"of them so the top record can carry distinct fields"
|
|
929
|
+
)
|
|
930
|
+
if name in reserved_dests:
|
|
931
|
+
ctx.error(
|
|
932
|
+
f"argparse: per-sub argument {name!r} is reserved "
|
|
933
|
+
f"by the subparsers machinery (matches the "
|
|
934
|
+
f"subcommand-name field on the top record)"
|
|
935
|
+
)
|
|
936
|
+
# Compare *unwrapped* field types: a sub with ``required=True``
|
|
937
|
+
# carries ``T`` and another with the same flag but no required=
|
|
938
|
+
# carries ``Optional[T]`` -- both unify to a single ``Optional[T]``
|
|
939
|
+
# field on the top record (matching CPython, which doesn't care
|
|
940
|
+
# which sub set the attribute). Reject only when the underlying
|
|
941
|
+
# base types disagree.
|
|
942
|
+
base_types = {_unwrap_optional(spec.field_type) for _, spec in occurrences}
|
|
943
|
+
if len(base_types) > 1:
|
|
944
|
+
ctx.error(
|
|
945
|
+
f"argparse: per-sub argument {name!r} has conflicting "
|
|
946
|
+
f"field types across sub-parsers; under Option B "
|
|
947
|
+
f"(flat namespace) the top record needs a single "
|
|
948
|
+
f"field type per name. Rename one of them, or unify "
|
|
949
|
+
f"the types (same ``type=`` and same default-shape)"
|
|
950
|
+
)
|
|
951
|
+
top_type = types.optional(next(iter(base_types)))
|
|
952
|
+
flat_fields.append((name, top_type, occurrences))
|
|
953
|
+
|
|
954
|
+
top_record_name = ctx.fresh_module_name("argparse_args")
|
|
955
|
+
top_fields: list = [(s.dest, s.field_type) for s in self.specs]
|
|
956
|
+
top_fields.append((sp.dest, cmd_field_type))
|
|
957
|
+
for name, top_type, _ in flat_fields:
|
|
958
|
+
top_fields.append((name, top_type))
|
|
959
|
+
top_record_type = ctx.emit_record(top_record_name, top_fields)
|
|
960
|
+
|
|
961
|
+
# Top-level usage / help. v1 lists subcommands as a single
|
|
962
|
+
# positional placeholder ``{a,b,c}`` to match CPython's output
|
|
963
|
+
# shape; per-sub help is not auto-emitted in v1.
|
|
964
|
+
usage_text = _format_usage(
|
|
965
|
+
self.specs, prog=prog, usage=self.usage,
|
|
966
|
+
include_help_opt=self.add_help,
|
|
967
|
+
subparser_action=sp,
|
|
968
|
+
)
|
|
969
|
+
help_fn_name: str | None = None
|
|
970
|
+
if self.add_help:
|
|
971
|
+
help_fn_name = ctx.fresh_module_name("argparse_help")
|
|
972
|
+
help_text = _format_help_text(
|
|
973
|
+
self.specs, self.description,
|
|
974
|
+
usage_text=usage_text, epilog=self.epilog,
|
|
975
|
+
subparser_action=sp,
|
|
976
|
+
)
|
|
977
|
+
ctx.emit_function(
|
|
978
|
+
help_fn_name, [], types.void,
|
|
979
|
+
ast.quote(f"print({help_text!r})\nsys.exit(Int32(0))"),
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
body = _build_subparser_parse_body(
|
|
983
|
+
self.specs, sp, sub_parse_fn_names, flat_fields,
|
|
984
|
+
top_record_name, help_fn_name, usage_text,
|
|
985
|
+
prog=prog, add_help=self.add_help,
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
fn_name = ctx.fresh_module_name("argparse_parse")
|
|
989
|
+
ctx.emit_function(
|
|
990
|
+
fn_name,
|
|
991
|
+
[("argv", types.list(types.str))],
|
|
992
|
+
top_record_type.raw_type,
|
|
993
|
+
body,
|
|
994
|
+
)
|
|
995
|
+
ctx.replace_call(fn_name, args)
|
|
996
|
+
return top_record_type
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
# ---------------------------------------------------------------------------
|
|
1000
|
+
# Internal: parse-function body synthesis
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
|
|
1003
|
+
def _emit_parse_prelude(
|
|
1004
|
+
specs: list[_ArgSpec], usage_text: str, *,
|
|
1005
|
+
add_help: bool, help_fn_name: str | None,
|
|
1006
|
+
) -> tuple[list, list[str]]:
|
|
1007
|
+
"""Returns ``(init_stmts, src_lines)`` for the boilerplate every
|
|
1008
|
+
parse-fn body opens with: usage local, optional ``-h``/``--help``
|
|
1009
|
+
pre-scan, per-spec init, required-flag-seen tracking. Shared by
|
|
1010
|
+
the single-parser and subparser-aware parse-body builders.
|
|
1011
|
+
|
|
1012
|
+
Both lists are returned mutable; callers append further AST
|
|
1013
|
+
decls / source lines onto them before assembling the final body
|
|
1014
|
+
(e.g. the subparser path adds ``acc_<dest>`` and per-sub flat
|
|
1015
|
+
locals to ``init_stmts`` and emits its dispatch loop into
|
|
1016
|
+
``src_lines``).
|
|
1017
|
+
|
|
1018
|
+
The help fn prints help and calls ``sys.exit(0)``, so the
|
|
1019
|
+
pre-scan loop never returns from the call -- but its return type
|
|
1020
|
+
is ``None``, so sema sees normal flow and the post-call increment
|
|
1021
|
+
compiles fine.
|
|
1022
|
+
"""
|
|
1023
|
+
init_stmts: list = []
|
|
1024
|
+
src_lines: list[str] = []
|
|
1025
|
+
src_lines.append(f"__tpy_argparse_usage = {usage_text!r}")
|
|
1026
|
+
if add_help:
|
|
1027
|
+
assert help_fn_name is not None
|
|
1028
|
+
src_lines.append("__tpy_argparse_h = 0")
|
|
1029
|
+
src_lines.append("while __tpy_argparse_h < len(argv):")
|
|
1030
|
+
src_lines.append(
|
|
1031
|
+
' if argv[__tpy_argparse_h] == "-h" '
|
|
1032
|
+
'or argv[__tpy_argparse_h] == "--help":'
|
|
1033
|
+
)
|
|
1034
|
+
src_lines.append(f" {help_fn_name}()")
|
|
1035
|
+
src_lines.append(" __tpy_argparse_h = __tpy_argparse_h + 1")
|
|
1036
|
+
for s in specs:
|
|
1037
|
+
init_stmts.extend(_spec_init_stmts(s))
|
|
1038
|
+
for s in specs:
|
|
1039
|
+
if s.is_flag and s.required:
|
|
1040
|
+
src_lines.append(f"__tpy_argparse_seen_{s.dest} = False")
|
|
1041
|
+
return init_stmts, src_lines
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def _emit_parse_reconciliation(
|
|
1045
|
+
specs: list[_ArgSpec], *, error_prefix: str,
|
|
1046
|
+
) -> list[str]:
|
|
1047
|
+
"""Returns ``src_lines`` for the post-loop reconciliation shared
|
|
1048
|
+
by both parse-body builders: required-flag missing checks, then
|
|
1049
|
+
Optional[list[T]] accumulator -> dest copy, then ArgType
|
|
1050
|
+
accumulator unwrap (assert + copy).
|
|
1051
|
+
"""
|
|
1052
|
+
src_lines: list[str] = []
|
|
1053
|
+
for s in specs:
|
|
1054
|
+
if s.is_flag and s.required:
|
|
1055
|
+
src_lines.append(f"if not __tpy_argparse_seen_{s.dest}:")
|
|
1056
|
+
src_lines.extend(_error_emit_lines(
|
|
1057
|
+
" ", repr(f"missing required argument: {s.flag_names[0]}"),
|
|
1058
|
+
error_prefix=error_prefix,
|
|
1059
|
+
))
|
|
1060
|
+
# Optional[list[T]] reconciliation: ``tpy.copy`` is here for the
|
|
1061
|
+
# implicit-copy warning, not correctness -- assigning a
|
|
1062
|
+
# ``list[T]`` local into an ``Optional[list[T]]`` slot warns even
|
|
1063
|
+
# when the local is at its last use. Codegen lowers ``copy`` to a
|
|
1064
|
+
# ``vector(acc)`` copy ctor, so this is one redundant allocation
|
|
1065
|
+
# per seen flag. Replace with a true move once TPy core gains
|
|
1066
|
+
# move-into-Optional.
|
|
1067
|
+
for s in specs:
|
|
1068
|
+
if s.is_optional_list_field:
|
|
1069
|
+
src_lines.append(f"if {_seen_local(s)}:")
|
|
1070
|
+
src_lines.append(f" {s.dest} = tpy.copy({_acc_local(s)})")
|
|
1071
|
+
# ArgType accumulator unwrap: missing-required checks above
|
|
1072
|
+
# already exited via sys.exit(2) when the accumulator stayed
|
|
1073
|
+
# None, so the assert is for sema's flow-narrowing
|
|
1074
|
+
# (Optional[T] -> T) rather than runtime safety. ``tpy.copy``
|
|
1075
|
+
# makes the implicit copy explicit so the record ctor (which
|
|
1076
|
+
# moves into owned storage) doesn't warn.
|
|
1077
|
+
for s in specs:
|
|
1078
|
+
if _needs_arg_type_accumulator(s):
|
|
1079
|
+
src_lines.append(f"assert {_acc_local(s)} is not None")
|
|
1080
|
+
src_lines.append(f"{s.dest} = tpy.copy({_acc_local(s)})")
|
|
1081
|
+
return src_lines
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def _build_parse_body(
|
|
1085
|
+
specs: list[_ArgSpec], record_name: str, help_fn_name: str | None,
|
|
1086
|
+
usage_text: str, *, prog: str, add_help: bool,
|
|
1087
|
+
) -> list:
|
|
1088
|
+
"""Generate the full body of the synthesized parse function.
|
|
1089
|
+
|
|
1090
|
+
Layout:
|
|
1091
|
+
<init defaults / typed empty lists>
|
|
1092
|
+
<seen-tracking for required flags>
|
|
1093
|
+
__tpy_argparse_i = 0
|
|
1094
|
+
__tpy_argparse_pi = 0
|
|
1095
|
+
while __tpy_argparse_i < len(argv):
|
|
1096
|
+
__tpy_argparse_tok = argv[__tpy_argparse_i]
|
|
1097
|
+
if __tpy_argparse_tok == "--flag1": ...handler advances i...
|
|
1098
|
+
elif ...: ...
|
|
1099
|
+
else: ...positional dispatch advances i and pi...
|
|
1100
|
+
<missing-positional check>
|
|
1101
|
+
<required-flag check>
|
|
1102
|
+
return Record(...)
|
|
1103
|
+
"""
|
|
1104
|
+
error_prefix = f"{prog}: error: "
|
|
1105
|
+
|
|
1106
|
+
# Empty parser: no add_argument() was called. Reject any argv
|
|
1107
|
+
# tokens (matching CPython argparse) and return an empty record
|
|
1108
|
+
# without entering the main loop -- the loop body would otherwise
|
|
1109
|
+
# never advance __tpy_argparse_i and hang on non-empty argv.
|
|
1110
|
+
if not specs:
|
|
1111
|
+
init_stmts, src_lines = _emit_parse_prelude(
|
|
1112
|
+
specs, usage_text, add_help=add_help, help_fn_name=help_fn_name,
|
|
1113
|
+
)
|
|
1114
|
+
src_lines.append("if len(argv) != 0:")
|
|
1115
|
+
src_lines.extend(_error_emit_lines(
|
|
1116
|
+
" ", '"unrecognized arguments: " + argv[0]',
|
|
1117
|
+
error_prefix=error_prefix,
|
|
1118
|
+
))
|
|
1119
|
+
src_lines.append(f"return {record_name}()")
|
|
1120
|
+
return init_stmts + ast.quote("\n".join(src_lines))
|
|
1121
|
+
|
|
1122
|
+
init_stmts, src_lines = _emit_parse_prelude(
|
|
1123
|
+
specs, usage_text, add_help=add_help, help_fn_name=help_fn_name,
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
flag_specs = [s for s in specs if s.is_flag]
|
|
1127
|
+
positional_specs = [s for s in specs if not s.is_flag]
|
|
1128
|
+
|
|
1129
|
+
# --- Main loop ---
|
|
1130
|
+
src_lines.append("__tpy_argparse_i = 0")
|
|
1131
|
+
if positional_specs:
|
|
1132
|
+
src_lines.append("__tpy_argparse_pi = 0")
|
|
1133
|
+
src_lines.append("while __tpy_argparse_i < len(argv):")
|
|
1134
|
+
src_lines.append(" __tpy_argparse_tok = argv[__tpy_argparse_i]")
|
|
1135
|
+
|
|
1136
|
+
# Flag dispatch
|
|
1137
|
+
first = True
|
|
1138
|
+
for s in flag_specs:
|
|
1139
|
+
match_expr = " or ".join(
|
|
1140
|
+
f"__tpy_argparse_tok == {f!r}" for f in s.flag_names
|
|
1141
|
+
)
|
|
1142
|
+
kw = "if" if first else "elif"
|
|
1143
|
+
first = False
|
|
1144
|
+
src_lines.append(f" {kw} {match_expr}:")
|
|
1145
|
+
for line in _flag_handler_lines(s, indent=" ",
|
|
1146
|
+
error_prefix=error_prefix):
|
|
1147
|
+
src_lines.append(line)
|
|
1148
|
+
if s.required:
|
|
1149
|
+
src_lines.append(f" __tpy_argparse_seen_{s.dest} = True")
|
|
1150
|
+
|
|
1151
|
+
# Positional dispatch
|
|
1152
|
+
if positional_specs:
|
|
1153
|
+
kw = "else" if not first else "if True"
|
|
1154
|
+
src_lines.append(f" {kw}:")
|
|
1155
|
+
inner_first = True
|
|
1156
|
+
for pi, s in enumerate(positional_specs):
|
|
1157
|
+
inner_kw = "if" if inner_first else "elif"
|
|
1158
|
+
inner_first = False
|
|
1159
|
+
src_lines.append(f" {inner_kw} __tpy_argparse_pi == {pi}:")
|
|
1160
|
+
for line in _positional_handler_lines(
|
|
1161
|
+
s, indent=" ", error_prefix=error_prefix,
|
|
1162
|
+
):
|
|
1163
|
+
src_lines.append(line)
|
|
1164
|
+
src_lines.append(" else:")
|
|
1165
|
+
src_lines.extend(_error_emit_lines(
|
|
1166
|
+
" ",
|
|
1167
|
+
'"unexpected positional argument: " + __tpy_argparse_tok',
|
|
1168
|
+
error_prefix=error_prefix,
|
|
1169
|
+
))
|
|
1170
|
+
elif not first:
|
|
1171
|
+
src_lines.append(" else:")
|
|
1172
|
+
src_lines.extend(_error_emit_lines(
|
|
1173
|
+
" ",
|
|
1174
|
+
'"unknown argument: " + __tpy_argparse_tok',
|
|
1175
|
+
error_prefix=error_prefix,
|
|
1176
|
+
))
|
|
1177
|
+
|
|
1178
|
+
# Missing-positional check: every required positional slot must
|
|
1179
|
+
# have been filled. Variable-nargs trailing positionals (* / ?)
|
|
1180
|
+
# are themselves optional; +/N/none are required.
|
|
1181
|
+
# nargs='+' on the trailing positional must have got at least
|
|
1182
|
+
# one value; the dispatch sets pi only when consumed, so the
|
|
1183
|
+
# count check below already covers this.
|
|
1184
|
+
required_positional_count = sum(
|
|
1185
|
+
1 for s in positional_specs
|
|
1186
|
+
if s.nargs not in ("*", "?")
|
|
1187
|
+
)
|
|
1188
|
+
if required_positional_count > 0:
|
|
1189
|
+
src_lines.append(f"if __tpy_argparse_pi < {required_positional_count}:")
|
|
1190
|
+
src_lines.extend(_error_emit_lines(
|
|
1191
|
+
" ", '"missing required positional argument(s)"',
|
|
1192
|
+
error_prefix=error_prefix,
|
|
1193
|
+
))
|
|
1194
|
+
|
|
1195
|
+
src_lines.extend(_emit_parse_reconciliation(specs, error_prefix=error_prefix))
|
|
1196
|
+
|
|
1197
|
+
ctor_args = ", ".join(s.dest for s in specs)
|
|
1198
|
+
src_lines.append(f"return {record_name}({ctor_args})")
|
|
1199
|
+
|
|
1200
|
+
return init_stmts + ast.quote("\n".join(src_lines))
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
def _spec_init_stmts(spec: _ArgSpec) -> list:
|
|
1204
|
+
"""Initialization statements for one spec.
|
|
1205
|
+
|
|
1206
|
+
Type-bearing decls (typed list, Optional[T]) go through ``ast.*``
|
|
1207
|
+
so the annotation is a TpyType the per-module resolver doesn't
|
|
1208
|
+
need to import. Plain scalar inits go through source text so
|
|
1209
|
+
default-literal rendering stays compact.
|
|
1210
|
+
"""
|
|
1211
|
+
if spec.is_optional_list_field:
|
|
1212
|
+
# Three locals: dest field (Optional[list[T]] = None), the
|
|
1213
|
+
# accumulator the flag handler writes into, and a seen flag
|
|
1214
|
+
# so the post-loop reconciliation can distinguish "absent"
|
|
1215
|
+
# from "present with zero values".
|
|
1216
|
+
elem = spec.type_info.raw_type
|
|
1217
|
+
return [
|
|
1218
|
+
ast.var_decl(
|
|
1219
|
+
spec.dest, types.optional(types.list(elem)), ast.none_lit()
|
|
1220
|
+
),
|
|
1221
|
+
ast.var_decl(
|
|
1222
|
+
_acc_local(spec), types.list(elem), ast.list_lit()
|
|
1223
|
+
),
|
|
1224
|
+
*ast.quote(f"{_seen_local(spec)} = False"),
|
|
1225
|
+
]
|
|
1226
|
+
if spec.is_list_field:
|
|
1227
|
+
elem = spec.type_info.raw_type
|
|
1228
|
+
if spec.has_default:
|
|
1229
|
+
# Non-empty list literal default. Element-typed list
|
|
1230
|
+
# initializer with each element wrapped through the
|
|
1231
|
+
# spec's value-conversion (handles fixed-width int
|
|
1232
|
+
# constructor wrapping the same way scalar defaults do).
|
|
1233
|
+
elements = [_render_default_elem(v, spec.type_info)
|
|
1234
|
+
for v in spec.default]
|
|
1235
|
+
return [ast.var_decl(
|
|
1236
|
+
spec.dest, types.list(elem), ast.list_lit(elements),
|
|
1237
|
+
)]
|
|
1238
|
+
return [ast.var_decl(spec.dest, types.list(elem), ast.list_lit())]
|
|
1239
|
+
if spec.is_optional_field:
|
|
1240
|
+
if spec.action == "store_const":
|
|
1241
|
+
scalar = _python_value_type_info(spec.const).raw_type
|
|
1242
|
+
else:
|
|
1243
|
+
scalar = spec.type_info.raw_type
|
|
1244
|
+
return [ast.var_decl(
|
|
1245
|
+
spec.dest, types.optional(scalar), ast.none_lit()
|
|
1246
|
+
)]
|
|
1247
|
+
if _needs_arg_type_accumulator(spec):
|
|
1248
|
+
# No zero-arg sentinel for the user type. Init only the
|
|
1249
|
+
# ``Optional[T]`` accumulator; ``spec.dest`` is rebound to the
|
|
1250
|
+
# unwrapped ``T`` after the post-loop missing-required check.
|
|
1251
|
+
scalar = spec.type_info.raw_type
|
|
1252
|
+
return [ast.var_decl(
|
|
1253
|
+
_acc_local(spec), types.optional(scalar), ast.none_lit()
|
|
1254
|
+
)]
|
|
1255
|
+
return ast.quote(f"{spec.dest} = {_default_expr_src(spec)}")
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def _render_default_elem(value, ti: TypeInfo):
|
|
1259
|
+
"""AST expression for one element of a list-typed default.
|
|
1260
|
+
|
|
1261
|
+
Mirrors ``_default_expr_src``'s wrapping at the AST level:
|
|
1262
|
+
``Int32(1)`` / ``Float32(0.5)`` for fixed-width primitives so the
|
|
1263
|
+
typed list annotation stays consistent regardless of options.json
|
|
1264
|
+
defaults and Float32 elements don't widen to Float64.
|
|
1265
|
+
"""
|
|
1266
|
+
if ti.is_str:
|
|
1267
|
+
return ast.str_lit(str(value))
|
|
1268
|
+
if ti.is_bigint:
|
|
1269
|
+
return ast.int_lit(int(value))
|
|
1270
|
+
if ti.is_float32:
|
|
1271
|
+
return ast.call("Float32", [ast.float_lit(float(value))])
|
|
1272
|
+
if ti.is_float:
|
|
1273
|
+
return ast.float_lit(float(value))
|
|
1274
|
+
# Fixed-width int: wrap with the constructor so the literal type
|
|
1275
|
+
# matches the field type regardless of options.json's default_int.
|
|
1276
|
+
return ast.call(_arg_type_name(ti), [ast.int_lit(int(value))])
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def _acc_local(spec: _ArgSpec) -> str:
|
|
1280
|
+
return f"__tpy_argparse_acc_{spec.dest}"
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
def _seen_local(spec: _ArgSpec) -> str:
|
|
1284
|
+
return f"__tpy_argparse_seen_{spec.dest}"
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def _list_target(spec: _ArgSpec) -> str:
|
|
1288
|
+
"""Local the flag handler writes into: accumulator for
|
|
1289
|
+
Optional[list[T]] (post-loop reconciled via tpy.copy), dest field
|
|
1290
|
+
directly otherwise.
|
|
1291
|
+
"""
|
|
1292
|
+
return _acc_local(spec) if spec.is_optional_list_field else spec.dest
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
def _needs_arg_type_accumulator(spec: _ArgSpec) -> bool:
|
|
1296
|
+
"""True for ArgType specs whose record field is non-Optional T but
|
|
1297
|
+
whose parse local has to start as ``Optional[T]`` (no zero-arg
|
|
1298
|
+
sentinel for the user type). Covers required positional + required
|
|
1299
|
+
flag without a string default. Optional flags without default
|
|
1300
|
+
already get an Optional[T] field via ``is_optional_field``, so
|
|
1301
|
+
they don't need the accumulator+narrow dance. List-typed fields
|
|
1302
|
+
(``nargs=*/+/<int>`` or append/extend) own a ``list[T]`` directly
|
|
1303
|
+
and never reach the scalar-unwrap path either.
|
|
1304
|
+
"""
|
|
1305
|
+
if not spec.is_arg_type:
|
|
1306
|
+
return False
|
|
1307
|
+
if spec.has_default:
|
|
1308
|
+
return False
|
|
1309
|
+
if spec.is_optional_field:
|
|
1310
|
+
return False
|
|
1311
|
+
if spec.is_list_field:
|
|
1312
|
+
return False
|
|
1313
|
+
return True
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def _store_target(spec: _ArgSpec) -> str:
|
|
1317
|
+
"""Local the ``store`` handler writes into. For ArgType specs that
|
|
1318
|
+
need the accumulator-then-narrow pattern, that's ``_acc_<dest>``
|
|
1319
|
+
(Optional[T]); a post-missing-check rebind to ``<dest>`` (T) lands
|
|
1320
|
+
the unwrapped value in the name the record constructor consumes.
|
|
1321
|
+
"""
|
|
1322
|
+
return _acc_local(spec) if _needs_arg_type_accumulator(spec) else spec.dest
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
def _flag_handler_lines(
|
|
1326
|
+
spec: _ArgSpec, *, indent: str,
|
|
1327
|
+
error_prefix: str = _DEFAULT_ERROR_PREFIX,
|
|
1328
|
+
) -> list[str]:
|
|
1329
|
+
"""Source lines that consume the matched flag plus its values
|
|
1330
|
+
(per nargs/action) and advance __tpy_argparse_i.
|
|
1331
|
+
"""
|
|
1332
|
+
L: list[str] = []
|
|
1333
|
+
a = spec.action
|
|
1334
|
+
if a == "store_true":
|
|
1335
|
+
L.append(f"{indent}{spec.dest} = True")
|
|
1336
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1337
|
+
return L
|
|
1338
|
+
if a == "store_false":
|
|
1339
|
+
L.append(f"{indent}{spec.dest} = False")
|
|
1340
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1341
|
+
return L
|
|
1342
|
+
if a == "count":
|
|
1343
|
+
L.append(f"{indent}{spec.dest} = {spec.dest} + 1")
|
|
1344
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1345
|
+
return L
|
|
1346
|
+
if a == "store_const":
|
|
1347
|
+
const_repr = _literal_repr(spec.const)
|
|
1348
|
+
L.append(f"{indent}{spec.dest} = {const_repr}")
|
|
1349
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1350
|
+
return L
|
|
1351
|
+
|
|
1352
|
+
target = _list_target(spec)
|
|
1353
|
+
|
|
1354
|
+
# Value-taking actions: store / append / extend, with optional nargs.
|
|
1355
|
+
if spec.nargs is None:
|
|
1356
|
+
# Single value, advance by 2.
|
|
1357
|
+
value_expr = _value_expr_src(spec, "argv[__tpy_argparse_i + 1]")
|
|
1358
|
+
L.append(f"{indent}if __tpy_argparse_i + 1 >= len(argv):")
|
|
1359
|
+
L.extend(_error_emit_lines(
|
|
1360
|
+
f"{indent} ", '"missing value for " + __tpy_argparse_tok',
|
|
1361
|
+
error_prefix=error_prefix,
|
|
1362
|
+
))
|
|
1363
|
+
if spec.choices:
|
|
1364
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1365
|
+
L.append(f"{indent}{tmp} = {value_expr}")
|
|
1366
|
+
for line in _choices_check_lines(
|
|
1367
|
+
spec, tmp, indent=indent, error_prefix=error_prefix,
|
|
1368
|
+
):
|
|
1369
|
+
L.append(line)
|
|
1370
|
+
if a == "store":
|
|
1371
|
+
L.append(f"{indent}{_store_target(spec)} = {tmp}")
|
|
1372
|
+
else:
|
|
1373
|
+
L.append(f"{indent}{target}.append({tmp})")
|
|
1374
|
+
else:
|
|
1375
|
+
if a == "store":
|
|
1376
|
+
L.append(f"{indent}{_store_target(spec)} = {value_expr}")
|
|
1377
|
+
else:
|
|
1378
|
+
L.append(f"{indent}{target}.append({value_expr})")
|
|
1379
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 2")
|
|
1380
|
+
if spec.is_optional_list_field:
|
|
1381
|
+
L.append(f"{indent}{_seen_local(spec)} = True")
|
|
1382
|
+
return L
|
|
1383
|
+
|
|
1384
|
+
if spec.nargs == "?":
|
|
1385
|
+
# 0 or 1 value: peek at next token; if absent or starts
|
|
1386
|
+
# with '-' use const, otherwise consume one value.
|
|
1387
|
+
L.append(
|
|
1388
|
+
f"{indent}if __tpy_argparse_i + 1 < len(argv) and not "
|
|
1389
|
+
f"argv[__tpy_argparse_i + 1].startswith(\"-\"):"
|
|
1390
|
+
)
|
|
1391
|
+
value_expr = _value_expr_src(spec, "argv[__tpy_argparse_i + 1]")
|
|
1392
|
+
if spec.choices:
|
|
1393
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1394
|
+
L.append(f"{indent} {tmp} = {value_expr}")
|
|
1395
|
+
for line in _choices_check_lines(
|
|
1396
|
+
spec, tmp, indent=indent + " ", error_prefix=error_prefix,
|
|
1397
|
+
):
|
|
1398
|
+
L.append(line)
|
|
1399
|
+
L.append(f"{indent} {spec.dest} = {tmp}")
|
|
1400
|
+
else:
|
|
1401
|
+
L.append(f"{indent} {spec.dest} = {value_expr}")
|
|
1402
|
+
L.append(f"{indent} __tpy_argparse_i = __tpy_argparse_i + 2")
|
|
1403
|
+
L.append(f"{indent}else:")
|
|
1404
|
+
if spec.has_const:
|
|
1405
|
+
const_repr = _literal_repr(spec.const)
|
|
1406
|
+
L.append(f"{indent} {spec.dest} = {const_repr}")
|
|
1407
|
+
# else: leave field at its initial default
|
|
1408
|
+
L.append(f"{indent} __tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1409
|
+
return L
|
|
1410
|
+
|
|
1411
|
+
# nargs in {'*', '+', int}: scan forward consuming non-flag
|
|
1412
|
+
# tokens, appending into the field directly. The field is
|
|
1413
|
+
# already typed list[T] (or Optional[list[T]]) from the init
|
|
1414
|
+
# pass; for action=store the field is cleared at the top of
|
|
1415
|
+
# the match so repeated flags replace rather than accumulate.
|
|
1416
|
+
if a == "store":
|
|
1417
|
+
# Reassign to empty list -- field type was set at init.
|
|
1418
|
+
L.append(f"{indent}{target} = []")
|
|
1419
|
+
L.append(f"{indent}__tpy_argparse_j = __tpy_argparse_i + 1")
|
|
1420
|
+
L.append(
|
|
1421
|
+
f"{indent}while __tpy_argparse_j < len(argv) and not "
|
|
1422
|
+
f"argv[__tpy_argparse_j].startswith(\"-\"):"
|
|
1423
|
+
)
|
|
1424
|
+
inner_value = _value_expr_src(spec, "argv[__tpy_argparse_j]")
|
|
1425
|
+
if spec.choices:
|
|
1426
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1427
|
+
L.append(f"{indent} {tmp} = {inner_value}")
|
|
1428
|
+
for line in _choices_check_lines(
|
|
1429
|
+
spec, tmp, indent=indent + " ", error_prefix=error_prefix,
|
|
1430
|
+
):
|
|
1431
|
+
L.append(line)
|
|
1432
|
+
L.append(f"{indent} {target}.append({tmp})")
|
|
1433
|
+
else:
|
|
1434
|
+
L.append(f"{indent} {target}.append({inner_value})")
|
|
1435
|
+
L.append(f"{indent} __tpy_argparse_j = __tpy_argparse_j + 1")
|
|
1436
|
+
consumed = "(__tpy_argparse_j - __tpy_argparse_i - 1)"
|
|
1437
|
+
if isinstance(spec.nargs, int):
|
|
1438
|
+
L.append(f"{indent}if {consumed} != {spec.nargs}:")
|
|
1439
|
+
L.extend(_error_emit_lines(
|
|
1440
|
+
f"{indent} ",
|
|
1441
|
+
f'__tpy_argparse_tok + " requires exactly {spec.nargs} value(s)"',
|
|
1442
|
+
error_prefix=error_prefix,
|
|
1443
|
+
))
|
|
1444
|
+
elif spec.nargs == "+":
|
|
1445
|
+
L.append(f"{indent}if {consumed} == 0:")
|
|
1446
|
+
L.extend(_error_emit_lines(
|
|
1447
|
+
f"{indent} ",
|
|
1448
|
+
'__tpy_argparse_tok + " requires at least one value"',
|
|
1449
|
+
error_prefix=error_prefix,
|
|
1450
|
+
))
|
|
1451
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_j")
|
|
1452
|
+
if spec.is_optional_list_field:
|
|
1453
|
+
L.append(f"{indent}{_seen_local(spec)} = True")
|
|
1454
|
+
return L
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
def _positional_handler_lines(
|
|
1458
|
+
spec: _ArgSpec, *, indent: str,
|
|
1459
|
+
error_prefix: str = _DEFAULT_ERROR_PREFIX,
|
|
1460
|
+
) -> list[str]:
|
|
1461
|
+
"""Source lines that handle one positional slot when the loop
|
|
1462
|
+
falls through to the else branch. Each branch advances both
|
|
1463
|
+
__tpy_argparse_i and __tpy_argparse_pi.
|
|
1464
|
+
"""
|
|
1465
|
+
L: list[str] = []
|
|
1466
|
+
if spec.nargs is None:
|
|
1467
|
+
value_expr = _value_expr_src(spec, "__tpy_argparse_tok")
|
|
1468
|
+
if spec.choices:
|
|
1469
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1470
|
+
L.append(f"{indent}{tmp} = {value_expr}")
|
|
1471
|
+
for line in _choices_check_lines(
|
|
1472
|
+
spec, tmp, indent=indent, error_prefix=error_prefix,
|
|
1473
|
+
):
|
|
1474
|
+
L.append(line)
|
|
1475
|
+
L.append(f"{indent}{_store_target(spec)} = {tmp}")
|
|
1476
|
+
else:
|
|
1477
|
+
L.append(f"{indent}{_store_target(spec)} = {value_expr}")
|
|
1478
|
+
L.append(f"{indent}__tpy_argparse_pi = __tpy_argparse_pi + 1")
|
|
1479
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1480
|
+
return L
|
|
1481
|
+
if isinstance(spec.nargs, int):
|
|
1482
|
+
N = spec.nargs
|
|
1483
|
+
L.append(f"{indent}if __tpy_argparse_i + {N} > len(argv):")
|
|
1484
|
+
L.extend(_error_emit_lines(
|
|
1485
|
+
f"{indent} ",
|
|
1486
|
+
repr(f"positional {spec.name!r} requires {N} value(s)"),
|
|
1487
|
+
error_prefix=error_prefix,
|
|
1488
|
+
))
|
|
1489
|
+
L.append(f"{indent}__tpy_argparse_k = 0")
|
|
1490
|
+
L.append(f"{indent}while __tpy_argparse_k < {N}:")
|
|
1491
|
+
idx_expr = "argv[__tpy_argparse_i + __tpy_argparse_k]"
|
|
1492
|
+
inner_value = _value_expr_src(spec, idx_expr)
|
|
1493
|
+
if spec.choices:
|
|
1494
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1495
|
+
L.append(f"{indent} {tmp} = {inner_value}")
|
|
1496
|
+
for line in _choices_check_lines(
|
|
1497
|
+
spec, tmp, indent=indent + " ", error_prefix=error_prefix,
|
|
1498
|
+
):
|
|
1499
|
+
L.append(line)
|
|
1500
|
+
L.append(f"{indent} {spec.dest}.append({tmp})")
|
|
1501
|
+
else:
|
|
1502
|
+
L.append(f"{indent} {spec.dest}.append({inner_value})")
|
|
1503
|
+
L.append(f"{indent} __tpy_argparse_k = __tpy_argparse_k + 1")
|
|
1504
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + {N}")
|
|
1505
|
+
L.append(f"{indent}__tpy_argparse_pi = __tpy_argparse_pi + 1")
|
|
1506
|
+
return L
|
|
1507
|
+
if spec.nargs == "?":
|
|
1508
|
+
# consume exactly one (the current token); falling off the end
|
|
1509
|
+
# is handled by the missing-positional check after the loop.
|
|
1510
|
+
value_expr = _value_expr_src(spec, "__tpy_argparse_tok")
|
|
1511
|
+
L.append(f"{indent}{spec.dest} = {value_expr}")
|
|
1512
|
+
L.append(f"{indent}__tpy_argparse_pi = __tpy_argparse_pi + 1")
|
|
1513
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1514
|
+
return L
|
|
1515
|
+
# nargs in {'*', '+'}: greedy -- consume the current token plus
|
|
1516
|
+
# all consecutive non-flag tokens that follow.
|
|
1517
|
+
value_expr = _value_expr_src(spec, "__tpy_argparse_tok")
|
|
1518
|
+
if spec.choices:
|
|
1519
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1520
|
+
L.append(f"{indent}{tmp} = {value_expr}")
|
|
1521
|
+
for line in _choices_check_lines(
|
|
1522
|
+
spec, tmp, indent=indent, error_prefix=error_prefix,
|
|
1523
|
+
):
|
|
1524
|
+
L.append(line)
|
|
1525
|
+
L.append(f"{indent}{spec.dest}.append({tmp})")
|
|
1526
|
+
else:
|
|
1527
|
+
L.append(f"{indent}{spec.dest}.append({value_expr})")
|
|
1528
|
+
L.append(f"{indent}__tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1529
|
+
L.append(
|
|
1530
|
+
f"{indent}while __tpy_argparse_i < len(argv) and not "
|
|
1531
|
+
f"argv[__tpy_argparse_i].startswith(\"-\"):"
|
|
1532
|
+
)
|
|
1533
|
+
inner_value = _value_expr_src(spec, "argv[__tpy_argparse_i]")
|
|
1534
|
+
if spec.choices:
|
|
1535
|
+
tmp = f"__tpy_argparse_v_{spec.dest}"
|
|
1536
|
+
L.append(f"{indent} {tmp} = {inner_value}")
|
|
1537
|
+
for line in _choices_check_lines(
|
|
1538
|
+
spec, tmp, indent=indent + " ", error_prefix=error_prefix,
|
|
1539
|
+
):
|
|
1540
|
+
L.append(line)
|
|
1541
|
+
L.append(f"{indent} {spec.dest}.append({tmp})")
|
|
1542
|
+
else:
|
|
1543
|
+
L.append(f"{indent} {spec.dest}.append({inner_value})")
|
|
1544
|
+
L.append(f"{indent} __tpy_argparse_i = __tpy_argparse_i + 1")
|
|
1545
|
+
L.append(f"{indent}__tpy_argparse_pi = __tpy_argparse_pi + 1")
|
|
1546
|
+
return L
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
def _default_expr_src(spec: _ArgSpec) -> str:
|
|
1550
|
+
"""Source-text rendering of the spec's default expression.
|
|
1551
|
+
|
|
1552
|
+
Only called for scalar-typed fields; list-typed fields are
|
|
1553
|
+
initialized via ast.var_decl in _spec_init_stmts.
|
|
1554
|
+
"""
|
|
1555
|
+
# Both optional flags and ``nargs='?'`` positionals can carry a
|
|
1556
|
+
# user-supplied default literal; render it as-is when present.
|
|
1557
|
+
# For fixed-width primitives, wrap with the constructor so the
|
|
1558
|
+
# literal carries the field's type rather than the default int /
|
|
1559
|
+
# float literal type.
|
|
1560
|
+
ti = spec.type_info
|
|
1561
|
+
if spec.has_default:
|
|
1562
|
+
rendered = _literal_repr(spec.default)
|
|
1563
|
+
if spec.is_arg_type:
|
|
1564
|
+
# Default is a string literal (validated at add_argument);
|
|
1565
|
+
# route through ``T.from_arg`` so the field carries T at
|
|
1566
|
+
# parse-fn entry. Mirrors CPython's "string defaults run
|
|
1567
|
+
# through type=" behaviour.
|
|
1568
|
+
return f"{ti.name}.from_arg({rendered})"
|
|
1569
|
+
if ti.is_int or ti.is_float32:
|
|
1570
|
+
return f"{_arg_type_name(ti)}({rendered})"
|
|
1571
|
+
return rendered
|
|
1572
|
+
if spec.action in ("store_true", "store_false"):
|
|
1573
|
+
return "False" if spec.action == "store_true" else "True"
|
|
1574
|
+
if spec.action == "count":
|
|
1575
|
+
return "0"
|
|
1576
|
+
if ti.is_bigint:
|
|
1577
|
+
return "0"
|
|
1578
|
+
if ti.is_int or ti.is_float32:
|
|
1579
|
+
# Constructor form so the literal type matches the field type
|
|
1580
|
+
# (regardless of options.json's default_int, and so Float32
|
|
1581
|
+
# fields don't get a Float64 init that widens the inferred type).
|
|
1582
|
+
return f"{_arg_type_name(ti)}(0)"
|
|
1583
|
+
if ti.is_float:
|
|
1584
|
+
return "0.0"
|
|
1585
|
+
return '""'
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
def _choices_check_lines(
|
|
1589
|
+
spec: _ArgSpec, value_var: str, *, indent: str,
|
|
1590
|
+
error_prefix: str = _DEFAULT_ERROR_PREFIX,
|
|
1591
|
+
) -> list[str]:
|
|
1592
|
+
"""Source-text lines that emit a parse error to stderr + exit(2)
|
|
1593
|
+
if value_var is not in the spec's choices=. Empty when no choices=.
|
|
1594
|
+
"""
|
|
1595
|
+
if not spec.choices:
|
|
1596
|
+
return []
|
|
1597
|
+
options = ", ".join(_literal_repr(c) for c in spec.choices)
|
|
1598
|
+
out = [f"{indent}if {value_var} not in ({options},):"]
|
|
1599
|
+
out.extend(_error_emit_lines(
|
|
1600
|
+
f"{indent} ",
|
|
1601
|
+
f'"invalid choice for {spec.name}: " + str({value_var})',
|
|
1602
|
+
error_prefix=error_prefix,
|
|
1603
|
+
))
|
|
1604
|
+
return out
|
|
1605
|
+
|
|
1606
|
+
|
|
1607
|
+
def _value_expr_src(spec: _ArgSpec, source_expr: str) -> str:
|
|
1608
|
+
"""Wrap a source expression with the spec's type conversion.
|
|
1609
|
+
|
|
1610
|
+
Fixed-width int / BigInt / float constructors all accept a string
|
|
1611
|
+
at runtime and parse it (panicking on overflow / invalid). The
|
|
1612
|
+
``str`` case skips wrapping since argv tokens are already strings.
|
|
1613
|
+
Custom ``ArgType`` types route through ``T.from_arg(token)``.
|
|
1614
|
+
"""
|
|
1615
|
+
if spec.type_info.is_str:
|
|
1616
|
+
return source_expr
|
|
1617
|
+
if spec.is_arg_type:
|
|
1618
|
+
return f"{spec.type_info.name}.from_arg({source_expr})"
|
|
1619
|
+
return f"{_arg_type_name(spec.type_info)}({source_expr})"
|
|
1620
|
+
|
|
1621
|
+
|
|
1622
|
+
def _literal_repr(value) -> str:
|
|
1623
|
+
"""Source-text rendering of a literal default. Macro-time literal
|
|
1624
|
+
values flow through eval_literal_or_final, so values are plain
|
|
1625
|
+
Python int / float / str / bool / None.
|
|
1626
|
+
"""
|
|
1627
|
+
if isinstance(value, str):
|
|
1628
|
+
return repr(value)
|
|
1629
|
+
if isinstance(value, bool):
|
|
1630
|
+
return "True" if value else "False"
|
|
1631
|
+
if value is None:
|
|
1632
|
+
return "None"
|
|
1633
|
+
return repr(value)
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
def _error_emit_lines(
|
|
1637
|
+
indent: str, msg_expr: str, *, error_prefix: str = _DEFAULT_ERROR_PREFIX,
|
|
1638
|
+
) -> list[str]:
|
|
1639
|
+
"""Render the parse-error sequence at ``indent``.
|
|
1640
|
+
|
|
1641
|
+
Prints ``<usage>\\n<prog>: error: <msg>`` to stderr and calls
|
|
1642
|
+
``sys.exit(Int32(2))``. ``msg_expr`` is a TPy source expression
|
|
1643
|
+
for the error message (string-typed, may concat a runtime
|
|
1644
|
+
token). The ``error_prefix`` parameter is a Python string baked
|
|
1645
|
+
in at macro time so the generated code keeps the prefix as a
|
|
1646
|
+
plain string literal -- threading it through the helpers (rather
|
|
1647
|
+
than via a runtime local) keeps generated output stable for
|
|
1648
|
+
callers that don't pass ``prog=``.
|
|
1649
|
+
"""
|
|
1650
|
+
return [
|
|
1651
|
+
f'{indent}print(__tpy_argparse_usage, '
|
|
1652
|
+
f'{error_prefix!r} + {msg_expr}, sep="\\n", file=sys.stderr)',
|
|
1653
|
+
f"{indent}sys.exit(Int32(2))",
|
|
1654
|
+
]
|
|
1655
|
+
|
|
1656
|
+
|
|
1657
|
+
# ---------------------------------------------------------------------------
|
|
1658
|
+
# Help-text formatting (also reused by parse-error path)
|
|
1659
|
+
# ---------------------------------------------------------------------------
|
|
1660
|
+
|
|
1661
|
+
_HELP_OPT_FORM = "-h, --help"
|
|
1662
|
+
_HELP_OPT_DESC = "show this help message and exit"
|
|
1663
|
+
|
|
1664
|
+
|
|
1665
|
+
def _metavar_for(spec: _ArgSpec) -> str:
|
|
1666
|
+
"""Metavar shown in usage / help for a value-taking arg.
|
|
1667
|
+
|
|
1668
|
+
Honors an explicit ``metavar=`` override; otherwise flags use the
|
|
1669
|
+
dest in uppercase (matching CPython) and positionals use their
|
|
1670
|
+
literal name.
|
|
1671
|
+
"""
|
|
1672
|
+
if spec.metavar is not None:
|
|
1673
|
+
return spec.metavar
|
|
1674
|
+
return spec.dest.upper() if spec.is_flag else spec.name
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
def _nargs_pattern(token: str, nargs) -> str:
|
|
1678
|
+
"""Render the metavar pattern for a given nargs value.
|
|
1679
|
+
|
|
1680
|
+
``token`` is the metavar to repeat; ``nargs`` is the spec's nargs
|
|
1681
|
+
field. Returns just the metavar pattern (no flag prefix).
|
|
1682
|
+
"""
|
|
1683
|
+
if nargs is None:
|
|
1684
|
+
return token
|
|
1685
|
+
if isinstance(nargs, int):
|
|
1686
|
+
return " ".join([token] * nargs)
|
|
1687
|
+
if nargs == "?":
|
|
1688
|
+
return f"[{token}]"
|
|
1689
|
+
if nargs == "*":
|
|
1690
|
+
return f"[{token} ...]"
|
|
1691
|
+
if nargs == "+":
|
|
1692
|
+
return f"{token} [{token} ...]"
|
|
1693
|
+
return token
|
|
1694
|
+
|
|
1695
|
+
|
|
1696
|
+
def _flag_usage_token(spec: _ArgSpec) -> str:
|
|
1697
|
+
"""One flag's contribution to the usage line. Shows the first
|
|
1698
|
+
flag form only (CPython does the same to keep usage compact).
|
|
1699
|
+
"""
|
|
1700
|
+
first = spec.flag_names[0]
|
|
1701
|
+
metavar = _metavar_for(spec)
|
|
1702
|
+
if spec.action in _VALUE_FREE_ACTIONS:
|
|
1703
|
+
body = first
|
|
1704
|
+
else:
|
|
1705
|
+
body = f"{first} {_nargs_pattern(metavar, spec.nargs)}"
|
|
1706
|
+
return body if spec.required else f"[{body}]"
|
|
1707
|
+
|
|
1708
|
+
|
|
1709
|
+
def _positional_usage_token(spec: _ArgSpec) -> str:
|
|
1710
|
+
return _nargs_pattern(_metavar_for(spec), spec.nargs)
|
|
1711
|
+
|
|
1712
|
+
|
|
1713
|
+
def _flag_help_signature(spec: _ArgSpec) -> str:
|
|
1714
|
+
"""All flag forms with metavar, for the options section.
|
|
1715
|
+
|
|
1716
|
+
e.g. ``-n NAME, --name NAME`` -- CPython repeats the metavar on
|
|
1717
|
+
every form for clarity.
|
|
1718
|
+
"""
|
|
1719
|
+
metavar = _metavar_for(spec)
|
|
1720
|
+
if spec.action in _VALUE_FREE_ACTIONS:
|
|
1721
|
+
return ", ".join(spec.flag_names)
|
|
1722
|
+
pattern = _nargs_pattern(metavar, spec.nargs)
|
|
1723
|
+
return ", ".join(f"{f} {pattern}" for f in spec.flag_names)
|
|
1724
|
+
|
|
1725
|
+
|
|
1726
|
+
_USAGE_TEXT_WIDTH = 80
|
|
1727
|
+
_USAGE_PREFIX = "usage: "
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
def _format_usage(
|
|
1731
|
+
specs: list[_ArgSpec], *,
|
|
1732
|
+
prog: str = _DEFAULT_PROG,
|
|
1733
|
+
usage: str | None = None,
|
|
1734
|
+
include_help_opt: bool = True,
|
|
1735
|
+
subparser_action: '_SubparsersAction | None' = None,
|
|
1736
|
+
) -> str:
|
|
1737
|
+
"""Render just the usage line. Reused by parse-error path.
|
|
1738
|
+
|
|
1739
|
+
When ``usage`` is provided, it overrides the auto-generated tail
|
|
1740
|
+
after ``"usage: "`` (matching CPython's ``ArgumentParser(usage=)``
|
|
1741
|
+
behavior). When ``include_help_opt`` is False, the ``[-h]`` token
|
|
1742
|
+
is omitted from the auto-generated form. When ``subparser_action``
|
|
1743
|
+
is provided, the ``{a,b} ...`` subcommand placeholder is appended
|
|
1744
|
+
after any common positionals (matches CPython's rendering of the
|
|
1745
|
+
subparsers action).
|
|
1746
|
+
|
|
1747
|
+
Long usage lines wrap at ``_USAGE_TEXT_WIDTH`` (80) cols, matching
|
|
1748
|
+
CPython argparse's behavior when stdout isn't a TTY. Continuation
|
|
1749
|
+
lines are indented to align past the prog name (or past
|
|
1750
|
+
``"usage: "`` when prog is too long for that to fit).
|
|
1751
|
+
"""
|
|
1752
|
+
if usage is not None:
|
|
1753
|
+
return _USAGE_PREFIX + usage
|
|
1754
|
+
opt_parts: list[str] = []
|
|
1755
|
+
if include_help_opt:
|
|
1756
|
+
opt_parts.append("[-h]")
|
|
1757
|
+
for s in specs:
|
|
1758
|
+
if s.is_flag:
|
|
1759
|
+
opt_parts.append(_flag_usage_token(s))
|
|
1760
|
+
pos_parts = [_positional_usage_token(s) for s in specs if not s.is_flag]
|
|
1761
|
+
if subparser_action is not None:
|
|
1762
|
+
pos_parts.append(_subcommand_metavar(subparser_action))
|
|
1763
|
+
pos_parts.append("...")
|
|
1764
|
+
|
|
1765
|
+
# Try the single-line form first.
|
|
1766
|
+
flat = " ".join([prog, *opt_parts, *pos_parts]).rstrip()
|
|
1767
|
+
if len(_USAGE_PREFIX) + len(flat) <= _USAGE_TEXT_WIDTH:
|
|
1768
|
+
return _USAGE_PREFIX + flat
|
|
1769
|
+
|
|
1770
|
+
# Wrap. Mirrors CPython argparse.HelpFormatter._format_usage:
|
|
1771
|
+
# short prog stays on row 1 (continuation aligns past it); long
|
|
1772
|
+
# prog gets its own row (continuation aligns past "usage: ").
|
|
1773
|
+
if len(_USAGE_PREFIX) + len(prog) <= 0.75 * _USAGE_TEXT_WIDTH:
|
|
1774
|
+
indent = " " * (len(_USAGE_PREFIX) + len(prog) + 1)
|
|
1775
|
+
opt_lines = _wrap_usage_parts(
|
|
1776
|
+
[prog, *opt_parts], indent, prefix=_USAGE_PREFIX,
|
|
1777
|
+
)
|
|
1778
|
+
pos_lines = _wrap_usage_parts(pos_parts, indent, prefix=None)
|
|
1779
|
+
body = "\n".join(opt_lines + pos_lines)
|
|
1780
|
+
else:
|
|
1781
|
+
indent = " " * len(_USAGE_PREFIX)
|
|
1782
|
+
wrapped = _wrap_usage_parts(
|
|
1783
|
+
[*opt_parts, *pos_parts], indent, prefix=None,
|
|
1784
|
+
)
|
|
1785
|
+
body = "\n".join([prog, *wrapped])
|
|
1786
|
+
return _USAGE_PREFIX + body
|
|
1787
|
+
|
|
1788
|
+
|
|
1789
|
+
def _wrap_usage_parts(
|
|
1790
|
+
parts: list[str], indent: str, *, prefix: str | None,
|
|
1791
|
+
) -> list[str]:
|
|
1792
|
+
"""Greedy-wrap usage tokens to ``_USAGE_TEXT_WIDTH``.
|
|
1793
|
+
|
|
1794
|
+
First line uses ``prefix`` as starter (the caller prepends the
|
|
1795
|
+
prefix; this function strips ``indent`` from the first line so
|
|
1796
|
+
the prefix lines up); continuation lines are emitted with
|
|
1797
|
+
``indent`` already prepended. When ``prefix`` is None, every line
|
|
1798
|
+
starts with ``indent`` (no special-cased first row). Empty
|
|
1799
|
+
``parts`` returns an empty list.
|
|
1800
|
+
"""
|
|
1801
|
+
if not parts:
|
|
1802
|
+
return []
|
|
1803
|
+
lines: list[str] = []
|
|
1804
|
+
line: list[str] = []
|
|
1805
|
+
indent_length = len(indent)
|
|
1806
|
+
line_len = (len(prefix) if prefix is not None else indent_length) - 1
|
|
1807
|
+
for part in parts:
|
|
1808
|
+
if line and line_len + 1 + len(part) > _USAGE_TEXT_WIDTH:
|
|
1809
|
+
lines.append(indent + " ".join(line))
|
|
1810
|
+
line = []
|
|
1811
|
+
line_len = indent_length - 1
|
|
1812
|
+
line.append(part)
|
|
1813
|
+
line_len += len(part) + 1
|
|
1814
|
+
if line:
|
|
1815
|
+
lines.append(indent + " ".join(line))
|
|
1816
|
+
if prefix is not None:
|
|
1817
|
+
# First line carries the prefix instead of the indent; caller
|
|
1818
|
+
# prepends prefix once at the very start of the usage block.
|
|
1819
|
+
lines[0] = lines[0][indent_length:]
|
|
1820
|
+
return lines
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
def _subcommand_metavar(sp: _SubparsersAction) -> str:
|
|
1824
|
+
"""``{a,b,c}`` placeholder shown in usage / help. Matches CPython
|
|
1825
|
+
argparse's rendering of the subparsers positional.
|
|
1826
|
+
"""
|
|
1827
|
+
return "{" + ",".join(s.name for s in sp.subparsers) + "}"
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
def _subparser_cmd_acc(sp: _SubparsersAction) -> str:
|
|
1831
|
+
return f"__tpy_argparse_acc_{sp.dest}"
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
def _subparser_flat_local(field_name: str) -> str:
|
|
1835
|
+
return f"__tpy_argparse_flat_{field_name}"
|
|
1836
|
+
|
|
1837
|
+
|
|
1838
|
+
def _subparser_sub_local(sub_name: str) -> str:
|
|
1839
|
+
return f"__tpy_argparse_sub_{sub_name}"
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
def _format_help_text(
|
|
1843
|
+
specs: list[_ArgSpec], description: str | None, *,
|
|
1844
|
+
usage_text: str,
|
|
1845
|
+
epilog: str | None = None,
|
|
1846
|
+
subparser_action: '_SubparsersAction | None' = None,
|
|
1847
|
+
) -> str:
|
|
1848
|
+
"""Render the full --help output: usage line, description (if
|
|
1849
|
+
any), per-section listings of positionals and options, optional
|
|
1850
|
+
epilog. Only emitted when ``add_help=True``, so the auto
|
|
1851
|
+
``-h, --help`` row is always present. The caller pre-renders
|
|
1852
|
+
``usage_text`` so it isn't walked twice.
|
|
1853
|
+
|
|
1854
|
+
When ``subparser_action`` is provided, the positional section
|
|
1855
|
+
shows the ``{a,b}`` subcommand metavar plus one indented row per
|
|
1856
|
+
registered sub-parser instead of plain positional rows. v1
|
|
1857
|
+
rejects mixing positionals with subparsers, so the two cases
|
|
1858
|
+
don't overlap.
|
|
1859
|
+
"""
|
|
1860
|
+
flags = [s for s in specs if s.is_flag]
|
|
1861
|
+
positionals = [s for s in specs if not s.is_flag]
|
|
1862
|
+
|
|
1863
|
+
pos_rows = [(_positional_usage_token(s), s.help_text or "")
|
|
1864
|
+
for s in positionals]
|
|
1865
|
+
flag_rows = [(_flag_help_signature(s), s.help_text or "") for s in flags]
|
|
1866
|
+
sub_rows: list[tuple[str, str]] = []
|
|
1867
|
+
sub_token = ""
|
|
1868
|
+
if subparser_action is not None:
|
|
1869
|
+
sub_token = _subcommand_metavar(subparser_action)
|
|
1870
|
+
sub_rows = [(s.name, s.help_text or "")
|
|
1871
|
+
for s in subparser_action.subparsers]
|
|
1872
|
+
# Match CPython's ``self._action_max_length + 2``: align all rows
|
|
1873
|
+
# (including the auto ``-h, --help`` entry) to the longest
|
|
1874
|
+
# signature plus two spaces of separation.
|
|
1875
|
+
sigs = [_HELP_OPT_FORM, *(sig for sig, _ in pos_rows),
|
|
1876
|
+
*(sig for sig, _ in flag_rows),
|
|
1877
|
+
*([sub_token] if sub_token else []),
|
|
1878
|
+
*(sig for sig, _ in sub_rows)]
|
|
1879
|
+
pad = max(len(s) for s in sigs) + 2
|
|
1880
|
+
|
|
1881
|
+
lines = [usage_text, ""]
|
|
1882
|
+
if description:
|
|
1883
|
+
lines.append(description)
|
|
1884
|
+
lines.append("")
|
|
1885
|
+
|
|
1886
|
+
if subparser_action is not None:
|
|
1887
|
+
lines.append("positional arguments:")
|
|
1888
|
+
sub_action_help = subparser_action.action_help or ""
|
|
1889
|
+
lines.append(f" {sub_token.ljust(pad)}{sub_action_help}".rstrip())
|
|
1890
|
+
for name, text in sub_rows:
|
|
1891
|
+
lines.append(f" {name.ljust(pad - 2)}{text}".rstrip())
|
|
1892
|
+
lines.append("")
|
|
1893
|
+
elif positionals:
|
|
1894
|
+
lines.append("positional arguments:")
|
|
1895
|
+
for sig, text in pos_rows:
|
|
1896
|
+
lines.append(f" {sig.ljust(pad)}{text}".rstrip())
|
|
1897
|
+
lines.append("")
|
|
1898
|
+
|
|
1899
|
+
lines.append("options:")
|
|
1900
|
+
lines.append(f" {_HELP_OPT_FORM.ljust(pad)}{_HELP_OPT_DESC}")
|
|
1901
|
+
for sig, text in flag_rows:
|
|
1902
|
+
lines.append(f" {sig.ljust(pad)}{text}".rstrip())
|
|
1903
|
+
|
|
1904
|
+
if epilog:
|
|
1905
|
+
if lines and lines[-1] != "":
|
|
1906
|
+
lines.append("")
|
|
1907
|
+
lines.append(epilog)
|
|
1908
|
+
return "\n".join(lines)
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
def _build_subparser_parse_body(
|
|
1912
|
+
specs: list[_ArgSpec], sp: _SubparsersAction,
|
|
1913
|
+
sub_parse_fn_names: list[str],
|
|
1914
|
+
flat_fields: list[tuple[str, Type, list[tuple[int, _ArgSpec]]]],
|
|
1915
|
+
record_name: str, help_fn_name: str | None,
|
|
1916
|
+
usage_text: str, *, prog: str, add_help: bool,
|
|
1917
|
+
) -> list:
|
|
1918
|
+
"""Generate the body of the synthesized top-level parse function
|
|
1919
|
+
when subparsers are present (Option B / flat namespace).
|
|
1920
|
+
|
|
1921
|
+
Each per-sub field appears as an ``Optional[T]`` stored field on
|
|
1922
|
+
the top record. The parse fn dispatches the subcommand name,
|
|
1923
|
+
invokes the matching sub-parser, then copies the chosen sub
|
|
1924
|
+
record's fields into the corresponding flat locals (per-sub
|
|
1925
|
+
fields not on the chosen sub stay None). Layout:
|
|
1926
|
+
|
|
1927
|
+
__tpy_argparse_usage = <usage>
|
|
1928
|
+
<-h/--help scan>
|
|
1929
|
+
<init common arg defaults>
|
|
1930
|
+
<init required-flag-seen tracking>
|
|
1931
|
+
__tpy_argparse_acc_<dest>: Optional[str] = None
|
|
1932
|
+
<flat_field_i>: Optional[T_i] = None # one per per-sub field name
|
|
1933
|
+
while __tpy_argparse_i < len(argv):
|
|
1934
|
+
__tpy_argparse_tok = argv[__tpy_argparse_i]
|
|
1935
|
+
if __tpy_argparse_tok == "--top-flag-1": ...
|
|
1936
|
+
elif __tpy_argparse_tok == "<sub-name-1>":
|
|
1937
|
+
__tpy_argparse_acc_<dest> = "<sub-name-1>"
|
|
1938
|
+
__tpy_argparse_i = __tpy_argparse_i + 1
|
|
1939
|
+
<sub_var> = <sub_parse_1>(list(argv[i:]))
|
|
1940
|
+
<copy sub_var.field into flat_field for each field on this sub>
|
|
1941
|
+
break
|
|
1942
|
+
...
|
|
1943
|
+
else: <unknown / invalid-choice error>
|
|
1944
|
+
<required flag missing checks>
|
|
1945
|
+
<Optional[list[T]] / ArgType reconciliation for common args>
|
|
1946
|
+
if sp.required and __tpy_argparse_acc_<dest> is None: <error>
|
|
1947
|
+
<build top record>
|
|
1948
|
+
"""
|
|
1949
|
+
error_prefix = f"{prog}: error: "
|
|
1950
|
+
init_stmts, src_lines = _emit_parse_prelude(
|
|
1951
|
+
specs, usage_text, add_help=add_help, help_fn_name=help_fn_name,
|
|
1952
|
+
)
|
|
1953
|
+
|
|
1954
|
+
# Subcommand-name accumulator. cmd is always typed Optional[str]
|
|
1955
|
+
# in the body even when sp.required forces the field to str: the
|
|
1956
|
+
# post-loop required-check converts None -> error and the assert
|
|
1957
|
+
# narrows back to str for the field write.
|
|
1958
|
+
init_stmts.append(ast.var_decl(
|
|
1959
|
+
_subparser_cmd_acc(sp),
|
|
1960
|
+
types.optional(types.str),
|
|
1961
|
+
ast.none_lit(),
|
|
1962
|
+
))
|
|
1963
|
+
|
|
1964
|
+
# Per-sub flat field locals (Optional[T] = None). Populated inside
|
|
1965
|
+
# the matching sub-parser dispatch branch from the chosen sub
|
|
1966
|
+
# record's fields.
|
|
1967
|
+
flat_local_for: dict[str, str] = {
|
|
1968
|
+
name: _subparser_flat_local(name) for name, _, _ in flat_fields
|
|
1969
|
+
}
|
|
1970
|
+
for name, top_type, _ in flat_fields:
|
|
1971
|
+
init_stmts.append(ast.var_decl(
|
|
1972
|
+
flat_local_for[name], top_type, ast.none_lit(),
|
|
1973
|
+
))
|
|
1974
|
+
|
|
1975
|
+
# Main loop.
|
|
1976
|
+
src_lines.append("__tpy_argparse_i = 0")
|
|
1977
|
+
src_lines.append("while __tpy_argparse_i < len(argv):")
|
|
1978
|
+
src_lines.append(" __tpy_argparse_tok = argv[__tpy_argparse_i]")
|
|
1979
|
+
|
|
1980
|
+
flag_specs = [s for s in specs if s.is_flag]
|
|
1981
|
+
first = True
|
|
1982
|
+
for s in flag_specs:
|
|
1983
|
+
match_expr = " or ".join(
|
|
1984
|
+
f"__tpy_argparse_tok == {f!r}" for f in s.flag_names
|
|
1985
|
+
)
|
|
1986
|
+
kw = "if" if first else "elif"
|
|
1987
|
+
first = False
|
|
1988
|
+
src_lines.append(f" {kw} {match_expr}:")
|
|
1989
|
+
for line in _flag_handler_lines(s, indent=" ",
|
|
1990
|
+
error_prefix=error_prefix):
|
|
1991
|
+
src_lines.append(line)
|
|
1992
|
+
if s.required:
|
|
1993
|
+
src_lines.append(f" __tpy_argparse_seen_{s.dest} = True")
|
|
1994
|
+
|
|
1995
|
+
# Subcommand dispatch: each registered sub-parser becomes a
|
|
1996
|
+
# branch. After calling the sub parse fn, copy each declared
|
|
1997
|
+
# field on the chosen sub into its flat local on the top record.
|
|
1998
|
+
cmd_acc = _subparser_cmd_acc(sp)
|
|
1999
|
+
for sub_idx, sub in enumerate(sp.subparsers):
|
|
2000
|
+
kw = "if" if first else "elif"
|
|
2001
|
+
first = False
|
|
2002
|
+
src_lines.append(
|
|
2003
|
+
f' {kw} __tpy_argparse_tok == {sub.name!r}:'
|
|
2004
|
+
)
|
|
2005
|
+
src_lines.append(f" {cmd_acc} = {sub.name!r}")
|
|
2006
|
+
src_lines.append(" __tpy_argparse_i = __tpy_argparse_i + 1")
|
|
2007
|
+
sub_var = _subparser_sub_local(sub.name)
|
|
2008
|
+
src_lines.append(
|
|
2009
|
+
f" {sub_var} = "
|
|
2010
|
+
f"{sub_parse_fn_names[sub_idx]}"
|
|
2011
|
+
f"(list(argv[__tpy_argparse_i:]))"
|
|
2012
|
+
)
|
|
2013
|
+
sub_field_names = {s.dest for s in sub.specs}
|
|
2014
|
+
for name, _, _ in flat_fields:
|
|
2015
|
+
if name in sub_field_names:
|
|
2016
|
+
src_lines.append(
|
|
2017
|
+
f" {flat_local_for[name]} = {sub_var}.{name}"
|
|
2018
|
+
)
|
|
2019
|
+
src_lines.append(" break")
|
|
2020
|
+
|
|
2021
|
+
# Else branch: unknown token. Distinguishes "unknown flag" (token
|
|
2022
|
+
# starts with '-') from "invalid choice" (positional that doesn't
|
|
2023
|
+
# match any sub-parser name) so error messages match CPython.
|
|
2024
|
+
src_lines.append(" else:")
|
|
2025
|
+
src_lines.append(
|
|
2026
|
+
" if __tpy_argparse_tok.startswith(\"-\"):"
|
|
2027
|
+
)
|
|
2028
|
+
src_lines.extend(_error_emit_lines(
|
|
2029
|
+
" ",
|
|
2030
|
+
'"unknown argument: " + __tpy_argparse_tok',
|
|
2031
|
+
error_prefix=error_prefix,
|
|
2032
|
+
))
|
|
2033
|
+
src_lines.append(" else:")
|
|
2034
|
+
src_lines.extend(_error_emit_lines(
|
|
2035
|
+
" ",
|
|
2036
|
+
'"invalid choice: " + __tpy_argparse_tok',
|
|
2037
|
+
error_prefix=error_prefix,
|
|
2038
|
+
))
|
|
2039
|
+
|
|
2040
|
+
src_lines.extend(_emit_parse_reconciliation(specs, error_prefix=error_prefix))
|
|
2041
|
+
|
|
2042
|
+
# Subcommand resolution. ``__tpy_argparse_cmd`` is bound here as
|
|
2043
|
+
# the ctor input for the top record's cmd field; sp.required
|
|
2044
|
+
# widens / narrows the accumulator type.
|
|
2045
|
+
if sp.required:
|
|
2046
|
+
src_lines.append(f"if {cmd_acc} is None:")
|
|
2047
|
+
src_lines.extend(_error_emit_lines(
|
|
2048
|
+
" ",
|
|
2049
|
+
repr(f"the following argument is required: {_subcommand_metavar(sp)}"),
|
|
2050
|
+
error_prefix=error_prefix,
|
|
2051
|
+
))
|
|
2052
|
+
src_lines.append(f"assert {cmd_acc} is not None")
|
|
2053
|
+
src_lines.append(f"__tpy_argparse_cmd = {cmd_acc}")
|
|
2054
|
+
|
|
2055
|
+
ctor_args = ", ".join(
|
|
2056
|
+
[s.dest for s in specs]
|
|
2057
|
+
+ ["__tpy_argparse_cmd"]
|
|
2058
|
+
+ [flat_local_for[name] for name, _, _ in flat_fields]
|
|
2059
|
+
)
|
|
2060
|
+
src_lines.append(f"return {record_name}({ctor_args})")
|
|
2061
|
+
|
|
2062
|
+
return init_stmts + ast.quote("\n".join(src_lines))
|