@voxgig/sdkgen 0.35.1 → 0.36.0
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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Readme.js +5 -5
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeEntity.js +74 -24
- package/dist/cmp/ReadmeEntity.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.d.ts +2 -0
- package/dist/cmp/ReadmeExplanation.js +308 -0
- package/dist/cmp/ReadmeExplanation.js.map +1 -0
- package/dist/cmp/ReadmeHowto.d.ts +2 -0
- package/dist/cmp/ReadmeHowto.js +18 -0
- package/dist/cmp/ReadmeHowto.js.map +1 -0
- package/dist/cmp/ReadmeIntro.js +8 -23
- package/dist/cmp/ReadmeIntro.js.map +1 -1
- package/dist/cmp/ReadmeModel.js +55 -91
- package/dist/cmp/ReadmeModel.js.map +1 -1
- package/dist/cmp/ReadmeOptions.js +35 -11
- package/dist/cmp/ReadmeOptions.js.map +1 -1
- package/dist/cmp/ReadmeQuick.js +4 -1
- package/dist/cmp/ReadmeQuick.js.map +1 -1
- package/dist/cmp/ReadmeRef.js +1042 -40
- package/dist/cmp/ReadmeRef.js.map +1 -1
- package/dist/cmp/ReadmeTop.d.ts +2 -0
- package/dist/cmp/ReadmeTop.js +171 -0
- package/dist/cmp/ReadmeTop.js.map +1 -0
- package/dist/sdkgen.d.ts +7 -1
- package/dist/sdkgen.js +13 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/project/.sdk/model/feature/feature-index.jsonic +5 -0
- package/project/.sdk/model/feature/log.jsonic +5 -1
- package/project/.sdk/model/feature/test.jsonic +1 -1
- package/project/.sdk/model/target/lua.jsonic +23 -0
- package/project/.sdk/model/target/php.jsonic +22 -0
- package/project/.sdk/model/target/py.jsonic +23 -0
- package/project/.sdk/model/target/rb.jsonic +23 -0
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -6
- package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +42 -0
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +112 -0
- package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +29 -0
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +129 -0
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +163 -0
- package/project/.sdk/src/cmp/go/ReadmeTopHowto_go.ts +24 -0
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +67 -0
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +40 -0
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +46 -2
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +360 -160
- package/project/.sdk/src/cmp/go/Test_go.ts +24 -1
- package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +1 -1
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +0 -4
- package/project/.sdk/src/cmp/lua/Config_lua.ts +112 -0
- package/project/.sdk/src/cmp/lua/EntityOperation_lua.ts +44 -0
- package/project/.sdk/src/cmp/lua/Entity_lua.ts +62 -0
- package/project/.sdk/src/cmp/lua/MainEntity_lua.ts +23 -0
- package/project/.sdk/src/cmp/lua/Main_lua.ts +133 -0
- package/project/.sdk/src/cmp/lua/Package_lua.ts +80 -0
- package/project/.sdk/src/cmp/lua/ReadmeExplanation_lua.ts +42 -0
- package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +100 -0
- package/project/.sdk/src/cmp/lua/ReadmeInstall_lua.ts +26 -0
- package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +129 -0
- package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +99 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopHowto_lua.ts +24 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +55 -0
- package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +38 -0
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +261 -0
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +483 -0
- package/project/.sdk/src/cmp/lua/Test_lua.ts +49 -0
- package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +147 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +27 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +24 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +30 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +30 -0
- package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +30 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +256 -0
- package/project/.sdk/src/cmp/lua/fragment/SdkError.fragment.lua +26 -0
- package/project/.sdk/src/cmp/lua/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/lua/utility_lua.ts +84 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +107 -0
- package/project/.sdk/src/cmp/php/EntityOperation_php.ts +44 -0
- package/project/.sdk/src/cmp/php/Entity_php.ts +62 -0
- package/project/.sdk/src/cmp/php/MainEntity_php.ts +24 -0
- package/project/.sdk/src/cmp/php/Main_php.ts +135 -0
- package/project/.sdk/src/cmp/php/Package_php.ts +87 -0
- package/project/.sdk/src/cmp/php/ReadmeExplanation_php.ts +42 -0
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +101 -0
- package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +19 -0
- package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +129 -0
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +100 -0
- package/project/.sdk/src/cmp/php/ReadmeTopHowto_php.ts +24 -0
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +56 -0
- package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +38 -0
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +270 -0
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +484 -0
- package/project/.sdk/src/cmp/php/Test_php.ts +56 -0
- package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +146 -0
- package/project/.sdk/src/cmp/php/fragment/EntityCreateOp.fragment.php +26 -0
- package/project/.sdk/src/cmp/php/fragment/EntityListOp.fragment.php +23 -0
- package/project/.sdk/src/cmp/php/fragment/EntityLoadOp.fragment.php +29 -0
- package/project/.sdk/src/cmp/php/fragment/EntityRemoveOp.fragment.php +29 -0
- package/project/.sdk/src/cmp/php/fragment/EntityUpdateOp.fragment.php +29 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +233 -0
- package/project/.sdk/src/cmp/php/fragment/SdkError.fragment.php +24 -0
- package/project/.sdk/src/cmp/php/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/php/utility_php.ts +85 -0
- package/project/.sdk/src/cmp/py/Config_py.ts +93 -0
- package/project/.sdk/src/cmp/py/EntityOperation_py.ts +44 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +62 -0
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +22 -0
- package/project/.sdk/src/cmp/py/Main_py.ts +156 -0
- package/project/.sdk/src/cmp/py/Package_py.ts +75 -0
- package/project/.sdk/src/cmp/py/ReadmeExplanation_py.ts +41 -0
- package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +98 -0
- package/project/.sdk/src/cmp/py/ReadmeInstall_py.ts +25 -0
- package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +130 -0
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +100 -0
- package/project/.sdk/src/cmp/py/ReadmeTopHowto_py.ts +24 -0
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +56 -0
- package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +38 -0
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +248 -0
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +475 -0
- package/project/.sdk/src/cmp/py/Test_py.ts +55 -0
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +112 -0
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +25 -0
- package/project/.sdk/src/cmp/py/fragment/EntityListOp.fragment.py +22 -0
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +27 -0
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +27 -0
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +27 -0
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +222 -0
- package/project/.sdk/src/cmp/py/fragment/SdkError.fragment.py +16 -0
- package/project/.sdk/src/cmp/py/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/py/utility_py.ts +84 -0
- package/project/.sdk/src/cmp/rb/Config_rb.ts +101 -0
- package/project/.sdk/src/cmp/rb/EntityOperation_rb.ts +44 -0
- package/project/.sdk/src/cmp/rb/Entity_rb.ts +62 -0
- package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +23 -0
- package/project/.sdk/src/cmp/rb/Main_rb.ts +130 -0
- package/project/.sdk/src/cmp/rb/Package_rb.ts +111 -0
- package/project/.sdk/src/cmp/rb/ReadmeExplanation_rb.ts +42 -0
- package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +98 -0
- package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +31 -0
- package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +129 -0
- package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +99 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopHowto_rb.ts +24 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +55 -0
- package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +38 -0
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +260 -0
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +476 -0
- package/project/.sdk/src/cmp/rb/Test_rb.ts +50 -0
- package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +116 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityCreateOp.fragment.rb +25 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityListOp.fragment.rb +20 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityLoadOp.fragment.rb +26 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityRemoveOp.fragment.rb +26 -0
- package/project/.sdk/src/cmp/rb/fragment/EntityUpdateOp.fragment.rb +26 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +203 -0
- package/project/.sdk/src/cmp/rb/fragment/SdkError.fragment.rb +16 -0
- package/project/.sdk/src/cmp/rb/tsconfig.json +15 -0
- package/project/.sdk/src/cmp/rb/utility_rb.ts +84 -0
- package/project/.sdk/src/cmp/ts/ReadmeExplanation_ts.ts +34 -0
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +123 -0
- package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +1 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +159 -0
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +69 -54
- package/project/.sdk/src/cmp/ts/ReadmeTopHowto_ts.ts +25 -0
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +65 -0
- package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +36 -0
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +0 -3
- package/project/.sdk/tm/go/feature/log_feature.go +133 -0
- package/project/.sdk/tm/go/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/lua/LICENSE +22 -0
- package/project/.sdk/tm/lua/Makefile +10 -0
- package/project/.sdk/tm/lua/core/context.lua +208 -0
- package/project/.sdk/tm/lua/core/control.lua +17 -0
- package/project/.sdk/tm/lua/core/error.lua +30 -0
- package/project/.sdk/tm/lua/core/helpers.lua +30 -0
- package/project/.sdk/tm/lua/core/operation.lua +51 -0
- package/project/.sdk/tm/lua/core/response.lua +45 -0
- package/project/.sdk/tm/lua/core/result.lua +58 -0
- package/project/.sdk/tm/lua/core/spec.lua +29 -0
- package/project/.sdk/tm/lua/core/utility_type.lua +90 -0
- package/project/.sdk/tm/lua/feature/base_feature.lua +35 -0
- package/project/.sdk/tm/lua/feature/log_feature.lua +80 -0
- package/project/.sdk/tm/lua/feature/test_feature.lua +202 -0
- package/project/.sdk/tm/lua/src/feature/README.md +3 -0
- package/project/.sdk/tm/lua/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/lua/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/lua/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/lua/test/runner.lua +86 -0
- package/project/.sdk/tm/lua/utility/clean.lua +7 -0
- package/project/.sdk/tm/lua/utility/done.lua +19 -0
- package/project/.sdk/tm/lua/utility/feature_add.lua +8 -0
- package/project/.sdk/tm/lua/utility/feature_hook.lua +21 -0
- package/project/.sdk/tm/lua/utility/feature_init.lua +24 -0
- package/project/.sdk/tm/lua/utility/fetcher.lua +96 -0
- package/project/.sdk/tm/lua/utility/make_context.lua +9 -0
- package/project/.sdk/tm/lua/utility/make_error.lua +73 -0
- package/project/.sdk/tm/lua/utility/make_fetch_def.lua +43 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +116 -0
- package/project/.sdk/tm/lua/utility/make_point.lua +92 -0
- package/project/.sdk/tm/lua/utility/make_request.lua +58 -0
- package/project/.sdk/tm/lua/utility/make_response.lua +44 -0
- package/project/.sdk/tm/lua/utility/make_result.lua +51 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +72 -0
- package/project/.sdk/tm/lua/utility/make_url.lua +46 -0
- package/project/.sdk/tm/lua/utility/param.lua +68 -0
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +39 -0
- package/project/.sdk/tm/lua/utility/prepare_body.lua +14 -0
- package/project/.sdk/tm/lua/utility/prepare_headers.lua +20 -0
- package/project/.sdk/tm/lua/utility/prepare_method.lua +17 -0
- package/project/.sdk/tm/lua/utility/prepare_params.lua +36 -0
- package/project/.sdk/tm/lua/utility/prepare_path.lua +19 -0
- package/project/.sdk/tm/lua/utility/prepare_query.lua +41 -0
- package/project/.sdk/tm/lua/utility/register.lua +71 -0
- package/project/.sdk/tm/lua/utility/result_basic.lua +32 -0
- package/project/.sdk/tm/lua/utility/result_body.lua +17 -0
- package/project/.sdk/tm/lua/utility/result_headers.lua +22 -0
- package/project/.sdk/tm/lua/utility/struct/struct.lua +3417 -0
- package/project/.sdk/tm/lua/utility/transform_request.lua +31 -0
- package/project/.sdk/tm/lua/utility/transform_response.lua +44 -0
- package/project/.sdk/tm/php/LICENSE +22 -0
- package/project/.sdk/tm/php/Makefile +10 -0
- package/project/.sdk/tm/php/core/Context.php +139 -0
- package/project/.sdk/tm/php/core/Control.php +18 -0
- package/project/.sdk/tm/php/core/Error.php +37 -0
- package/project/.sdk/tm/php/core/Helpers.php +25 -0
- package/project/.sdk/tm/php/core/Operation.php +36 -0
- package/project/.sdk/tm/php/core/Response.php +30 -0
- package/project/.sdk/tm/php/core/Result.php +35 -0
- package/project/.sdk/tm/php/core/Spec.php +38 -0
- package/project/.sdk/tm/php/core/UtilityType.php +89 -0
- package/project/.sdk/tm/php/feature/BaseFeature.php +37 -0
- package/project/.sdk/tm/php/feature/LogFeature.php +65 -0
- package/project/.sdk/tm/php/feature/TestFeature.php +193 -0
- package/project/.sdk/tm/php/src/feature/README.md +3 -0
- package/project/.sdk/tm/php/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/php/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/php/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/php/test/Runner.php +89 -0
- package/project/.sdk/tm/php/utility/Clean.php +12 -0
- package/project/.sdk/tm/php/utility/Done.php +26 -0
- package/project/.sdk/tm/php/utility/FeatureAdd.php +12 -0
- package/project/.sdk/tm/php/utility/FeatureHook.php +23 -0
- package/project/.sdk/tm/php/utility/FeatureInit.php +25 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +101 -0
- package/project/.sdk/tm/php/utility/MakeContext.php +14 -0
- package/project/.sdk/tm/php/utility/MakeError.php +59 -0
- package/project/.sdk/tm/php/utility/MakeFetchDef.php +36 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +98 -0
- package/project/.sdk/tm/php/utility/MakePoint.php +87 -0
- package/project/.sdk/tm/php/utility/MakeRequest.php +57 -0
- package/project/.sdk/tm/php/utility/MakeResponse.php +43 -0
- package/project/.sdk/tm/php/utility/MakeResult.php +50 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +64 -0
- package/project/.sdk/tm/php/utility/MakeUrl.php +41 -0
- package/project/.sdk/tm/php/utility/Param.php +66 -0
- package/project/.sdk/tm/php/utility/PrepareAuth.php +33 -0
- package/project/.sdk/tm/php/utility/PrepareBody.php +15 -0
- package/project/.sdk/tm/php/utility/PrepareHeaders.php +18 -0
- package/project/.sdk/tm/php/utility/PrepareMethod.php +21 -0
- package/project/.sdk/tm/php/utility/PrepareParams.php +34 -0
- package/project/.sdk/tm/php/utility/PreparePath.php +20 -0
- package/project/.sdk/tm/php/utility/PrepareQuery.php +32 -0
- package/project/.sdk/tm/php/utility/Register.php +67 -0
- package/project/.sdk/tm/php/utility/ResultBasic.php +29 -0
- package/project/.sdk/tm/php/utility/ResultBody.php +17 -0
- package/project/.sdk/tm/php/utility/ResultHeaders.php +21 -0
- package/project/.sdk/tm/php/utility/TransformRequest.php +27 -0
- package/project/.sdk/tm/php/utility/TransformResponse.php +42 -0
- package/project/.sdk/tm/php/utility/struct/Struct.php +3431 -0
- package/project/.sdk/tm/py/Makefile +10 -0
- package/project/.sdk/tm/py/core/__init__.py +0 -0
- package/project/.sdk/tm/py/core/context.py +199 -0
- package/project/.sdk/tm/py/core/control.py +12 -0
- package/project/.sdk/tm/py/core/error.py +18 -0
- package/project/.sdk/tm/py/core/helpers.py +15 -0
- package/project/.sdk/tm/py/core/operation.py +37 -0
- package/project/.sdk/tm/py/core/response.py +34 -0
- package/project/.sdk/tm/py/core/result.py +44 -0
- package/project/.sdk/tm/py/core/spec.py +23 -0
- package/project/.sdk/tm/py/core/utility_type.py +82 -0
- package/project/.sdk/tm/py/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/feature/base_feature.py +61 -0
- package/project/.sdk/tm/py/feature/log_feature.py +84 -0
- package/project/.sdk/tm/py/feature/test_feature.py +164 -0
- package/project/.sdk/tm/py/src/feature/README.md +3 -0
- package/project/.sdk/tm/py/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/py/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py/test/__init__.py +0 -0
- package/project/.sdk/tm/py/test/runner.py +77 -0
- package/project/.sdk/tm/py/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/utility/clean.py +5 -0
- package/project/.sdk/tm/py/utility/done.py +14 -0
- package/project/.sdk/tm/py/utility/feature_add.py +6 -0
- package/project/.sdk/tm/py/utility/feature_hook.py +15 -0
- package/project/.sdk/tm/py/utility/feature_init.py +18 -0
- package/project/.sdk/tm/py/utility/fetcher.py +95 -0
- package/project/.sdk/tm/py/utility/make_context.py +7 -0
- package/project/.sdk/tm/py/utility/make_error.py +64 -0
- package/project/.sdk/tm/py/utility/make_fetch_def.py +37 -0
- package/project/.sdk/tm/py/utility/make_options.py +103 -0
- package/project/.sdk/tm/py/utility/make_point.py +74 -0
- package/project/.sdk/tm/py/utility/make_request.py +52 -0
- package/project/.sdk/tm/py/utility/make_response.py +36 -0
- package/project/.sdk/tm/py/utility/make_result.py +41 -0
- package/project/.sdk/tm/py/utility/make_spec.py +68 -0
- package/project/.sdk/tm/py/utility/make_url.py +34 -0
- package/project/.sdk/tm/py/utility/param.py +55 -0
- package/project/.sdk/tm/py/utility/prepare_auth.py +34 -0
- package/project/.sdk/tm/py/utility/prepare_body.py +11 -0
- package/project/.sdk/tm/py/utility/prepare_headers.py +17 -0
- package/project/.sdk/tm/py/utility/prepare_method.py +15 -0
- package/project/.sdk/tm/py/utility/prepare_params.py +28 -0
- package/project/.sdk/tm/py/utility/prepare_path.py +16 -0
- package/project/.sdk/tm/py/utility/prepare_query.py +33 -0
- package/project/.sdk/tm/py/utility/register.py +68 -0
- package/project/.sdk/tm/py/utility/result_basic.py +26 -0
- package/project/.sdk/tm/py/utility/result_body.py +13 -0
- package/project/.sdk/tm/py/utility/result_headers.py +17 -0
- package/project/.sdk/tm/py/utility/transform_request.py +27 -0
- package/project/.sdk/tm/py/utility/transform_response.py +39 -0
- package/project/.sdk/tm/py/utility/voxgig_struct/__init__.py +72 -0
- package/project/.sdk/tm/py/utility/voxgig_struct/voxgig_struct.py +2770 -0
- package/project/.sdk/tm/rb/Gemfile +4 -0
- package/project/.sdk/tm/rb/LICENSE +22 -0
- package/project/.sdk/tm/rb/Makefile +10 -0
- package/project/.sdk/tm/rb/core/context.rb +105 -0
- package/project/.sdk/tm/rb/core/control.rb +11 -0
- package/project/.sdk/tm/rb/core/error.rb +24 -0
- package/project/.sdk/tm/rb/core/helpers.rb +16 -0
- package/project/.sdk/tm/rb/core/operation.rb +26 -0
- package/project/.sdk/tm/rb/core/response.rb +20 -0
- package/project/.sdk/tm/rb/core/result.rb +23 -0
- package/project/.sdk/tm/rb/core/spec.rb +23 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +32 -0
- package/project/.sdk/tm/rb/feature/base_feature.rb +30 -0
- package/project/.sdk/tm/rb/feature/log_feature.rb +50 -0
- package/project/.sdk/tm/rb/feature/test_feature.rb +154 -0
- package/project/.sdk/tm/rb/src/feature/README.md +3 -0
- package/project/.sdk/tm/rb/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/rb/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/rb/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/rb/test/runner.rb +65 -0
- package/project/.sdk/tm/rb/utility/clean.rb +4 -0
- package/project/.sdk/tm/rb/utility/done.rb +14 -0
- package/project/.sdk/tm/rb/utility/feature_add.rb +6 -0
- package/project/.sdk/tm/rb/utility/feature_hook.rb +11 -0
- package/project/.sdk/tm/rb/utility/feature_init.rb +16 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +67 -0
- package/project/.sdk/tm/rb/utility/make_context.rb +7 -0
- package/project/.sdk/tm/rb/utility/make_error.rb +44 -0
- package/project/.sdk/tm/rb/utility/make_fetch_def.rb +24 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +57 -0
- package/project/.sdk/tm/rb/utility/make_point.rb +77 -0
- package/project/.sdk/tm/rb/utility/make_request.rb +44 -0
- package/project/.sdk/tm/rb/utility/make_response.rb +25 -0
- package/project/.sdk/tm/rb/utility/make_result.rb +33 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +50 -0
- package/project/.sdk/tm/rb/utility/make_url.rb +32 -0
- package/project/.sdk/tm/rb/utility/param.rb +48 -0
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +26 -0
- package/project/.sdk/tm/rb/utility/prepare_body.rb +6 -0
- package/project/.sdk/tm/rb/utility/prepare_headers.rb +11 -0
- package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -0
- package/project/.sdk/tm/rb/utility/prepare_params.rb +25 -0
- package/project/.sdk/tm/rb/utility/prepare_path.rb +13 -0
- package/project/.sdk/tm/rb/utility/prepare_query.rb +22 -0
- package/project/.sdk/tm/rb/utility/register.rb +63 -0
- package/project/.sdk/tm/rb/utility/result_basic.rb +23 -0
- package/project/.sdk/tm/rb/utility/result_body.rb +11 -0
- package/project/.sdk/tm/rb/utility/result_headers.rb +15 -0
- package/project/.sdk/tm/rb/utility/struct/voxgig_struct.rb +2256 -0
- package/project/.sdk/tm/rb/utility/transform_request.rb +15 -0
- package/project/.sdk/tm/rb/utility/transform_response.rb +23 -0
- package/src/cmp/Readme.ts +5 -5
- package/src/cmp/ReadmeEntity.ts +77 -25
- package/src/cmp/ReadmeExplanation.ts +333 -0
- package/src/cmp/ReadmeHowto.ts +28 -0
- package/src/cmp/ReadmeIntro.ts +10 -24
- package/src/cmp/ReadmeModel.ts +57 -88
- package/src/cmp/ReadmeOptions.ts +40 -11
- package/src/cmp/ReadmeQuick.ts +4 -1
- package/src/cmp/ReadmeRef.ts +1057 -40
- package/src/cmp/ReadmeTop.ts +213 -0
- package/src/sdkgen.ts +12 -0
- package/project/.sdk/tm/go/test/exists_test.go +0 -16
|
@@ -0,0 +1,3431 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
namespace Voxgig\Struct;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reference-stable wrapper for PHP arrays.
|
|
8
|
+
* PHP arrays are value types (copy-on-write), so storing them in injection
|
|
9
|
+
* state loses reference identity. ListRef wraps the array in an object
|
|
10
|
+
* (reference type) so mutations via setval/delprop propagate through the
|
|
11
|
+
* injection pipeline. Mirrors Go's ListRef[T] strategy.
|
|
12
|
+
*/
|
|
13
|
+
class ListRef implements \ArrayAccess, \Countable, \IteratorAggregate
|
|
14
|
+
{
|
|
15
|
+
public array $list;
|
|
16
|
+
|
|
17
|
+
public function __construct(array $list = [])
|
|
18
|
+
{
|
|
19
|
+
$this->list = $list;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function offsetExists(mixed $offset): bool
|
|
23
|
+
{
|
|
24
|
+
return isset($this->list[$offset]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public function offsetGet(mixed $offset): mixed
|
|
28
|
+
{
|
|
29
|
+
return $this->list[$offset] ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public function offsetSet(mixed $offset, mixed $value): void
|
|
33
|
+
{
|
|
34
|
+
if ($offset === null) {
|
|
35
|
+
$this->list[] = $value;
|
|
36
|
+
} else {
|
|
37
|
+
$this->list[$offset] = $value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public function offsetUnset(mixed $offset): void
|
|
42
|
+
{
|
|
43
|
+
array_splice($this->list, (int)$offset, 1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public function count(): int
|
|
47
|
+
{
|
|
48
|
+
return count($this->list);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public function getIterator(): \ArrayIterator
|
|
52
|
+
{
|
|
53
|
+
return new \ArrayIterator($this->list);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Class Struct
|
|
59
|
+
*
|
|
60
|
+
* Utility class for manipulating in-memory JSON-like data structures.
|
|
61
|
+
* These utilities implement functions similar to the TypeScript version,
|
|
62
|
+
* with emphasis on handling nodes, maps, lists, and special "undefined" values.
|
|
63
|
+
*/
|
|
64
|
+
class Struct
|
|
65
|
+
{
|
|
66
|
+
|
|
67
|
+
/* =======================
|
|
68
|
+
* String Constants
|
|
69
|
+
* =======================
|
|
70
|
+
*/
|
|
71
|
+
private const S_MKEYPRE = 'key:pre';
|
|
72
|
+
private const S_MKEYPOST = 'key:post';
|
|
73
|
+
private const S_MVAL = 'val';
|
|
74
|
+
private const S_MKEY = 'key';
|
|
75
|
+
|
|
76
|
+
private const S_DKEY = '`$KEY`';
|
|
77
|
+
private const S_DMETA = '`$META`';
|
|
78
|
+
private const S_DANNO = '`$ANNO`';
|
|
79
|
+
|
|
80
|
+
// Match TypeScript constants exactly
|
|
81
|
+
private const S_BKEY = '`$KEY`';
|
|
82
|
+
private const S_BANNO = '`$ANNO`';
|
|
83
|
+
private const S_DTOP = '$TOP';
|
|
84
|
+
private const S_DERRS = '$ERRS';
|
|
85
|
+
private const S_ERRS = '$ERRS';
|
|
86
|
+
|
|
87
|
+
private const S_array = 'array';
|
|
88
|
+
private const S_boolean = 'boolean';
|
|
89
|
+
private const S_function = 'function';
|
|
90
|
+
private const S_number = 'number';
|
|
91
|
+
private const S_object = 'object';
|
|
92
|
+
private const S_string = 'string';
|
|
93
|
+
private const S_null = 'null';
|
|
94
|
+
private const S_MT = '';
|
|
95
|
+
private const S_BT = '`';
|
|
96
|
+
private const S_DS = '$';
|
|
97
|
+
private const S_DT = '.';
|
|
98
|
+
private const S_CN = ':';
|
|
99
|
+
private const S_KEY = 'KEY';
|
|
100
|
+
public const S_BASE = 'base';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Standard undefined value represented by a unique string marker.
|
|
104
|
+
*
|
|
105
|
+
* NOTE: This marker should be chosen to minimize collision with real data.
|
|
106
|
+
*/
|
|
107
|
+
public const UNDEF = '__UNDEFINED__';
|
|
108
|
+
|
|
109
|
+
public const T_any = (1 << 31) - 1;
|
|
110
|
+
public const T_noval = 1 << 30;
|
|
111
|
+
public const T_boolean = 1 << 29;
|
|
112
|
+
public const T_decimal = 1 << 28;
|
|
113
|
+
public const T_integer = 1 << 27;
|
|
114
|
+
public const T_number = 1 << 26;
|
|
115
|
+
public const T_string = 1 << 25;
|
|
116
|
+
public const T_function = 1 << 24;
|
|
117
|
+
public const T_symbol = 1 << 23;
|
|
118
|
+
public const T_null = 1 << 22;
|
|
119
|
+
public const T_list = 1 << 14;
|
|
120
|
+
public const T_map = 1 << 13;
|
|
121
|
+
public const T_instance = 1 << 12;
|
|
122
|
+
public const T_scalar = 1 << 7;
|
|
123
|
+
public const T_node = 1 << 6;
|
|
124
|
+
|
|
125
|
+
public const DELETE = ['`$DELETE`' => true];
|
|
126
|
+
|
|
127
|
+
private const S_CM = ',';
|
|
128
|
+
|
|
129
|
+
private const TYPENAME = [
|
|
130
|
+
'any', 'noval', 'boolean', 'decimal', 'integer', 'number', 'string',
|
|
131
|
+
'function', 'symbol', 'null',
|
|
132
|
+
'', '', '', '', '', '', '',
|
|
133
|
+
'list', 'map', 'instance',
|
|
134
|
+
'', '', '', '',
|
|
135
|
+
'scalar', 'node',
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
public const SKIP = ['`$SKIP`' => true];
|
|
139
|
+
|
|
140
|
+
// Mode constants (bitfield) matching TypeScript canonical
|
|
141
|
+
public const M_KEYPRE = 1;
|
|
142
|
+
public const M_KEYPOST = 2;
|
|
143
|
+
public const M_VAL = 4;
|
|
144
|
+
|
|
145
|
+
public const MODENAME = [
|
|
146
|
+
self::M_VAL => 'val',
|
|
147
|
+
self::M_KEYPRE => 'key:pre',
|
|
148
|
+
self::M_KEYPOST => 'key:post',
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
private const PLACEMENT = [
|
|
152
|
+
self::M_VAL => 'value',
|
|
153
|
+
self::M_KEYPRE => 'key',
|
|
154
|
+
self::M_KEYPOST => 'key',
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
/* =======================
|
|
158
|
+
* Regular expressions for validation and transformation
|
|
159
|
+
* =======================
|
|
160
|
+
*/
|
|
161
|
+
private const R_META_PATH = '/^([^$]+)\$([=~])(.+)$/';
|
|
162
|
+
private const R_TRANSFORM_NAME = '/`\$([A-Z]+)`/';
|
|
163
|
+
|
|
164
|
+
/* =======================
|
|
165
|
+
* Private Helpers
|
|
166
|
+
* =======================
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Determines whether an array has sequential integer keys, i.e. a list.
|
|
171
|
+
*
|
|
172
|
+
* @param array $val
|
|
173
|
+
* @return bool True if the array is a list (i.e. sequential keys starting at 0).
|
|
174
|
+
*/
|
|
175
|
+
private static function isListHelper(array $val): bool
|
|
176
|
+
{
|
|
177
|
+
return array_keys($val) === range(0, count($val) - 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* =======================
|
|
181
|
+
* Type and Existence Checks
|
|
182
|
+
* =======================
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
public static function isnode(mixed $val): bool
|
|
186
|
+
{
|
|
187
|
+
if ($val === self::UNDEF || $val === null) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if ($val instanceof \Closure) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
if ($val instanceof ListRef) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return is_object($val) || is_array($val);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if a value is a map (associative array or object) rather than a list.
|
|
203
|
+
*
|
|
204
|
+
* @param mixed $val
|
|
205
|
+
* @return bool
|
|
206
|
+
*/
|
|
207
|
+
public static function ismap(mixed $val): bool
|
|
208
|
+
{
|
|
209
|
+
if ($val instanceof ListRef) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if ($val instanceof \Closure) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (is_object($val)) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
// Any PHP array that isn't a list is a map,
|
|
219
|
+
// but treat *empty* arrays as lists (not maps).
|
|
220
|
+
if (is_array($val)) {
|
|
221
|
+
if (count($val) === 0) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return !self::islist($val);
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if a value is a list (sequential array).
|
|
233
|
+
*
|
|
234
|
+
* @param mixed $val
|
|
235
|
+
* @return bool
|
|
236
|
+
*/
|
|
237
|
+
public static function islist(mixed $val): bool
|
|
238
|
+
{
|
|
239
|
+
if ($val instanceof ListRef) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (!is_array($val)) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
$i = 0;
|
|
246
|
+
foreach ($val as $k => $_) {
|
|
247
|
+
if ($k !== $i++) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check if a key is valid (non-empty string or integer/float).
|
|
256
|
+
*
|
|
257
|
+
* @param mixed $key
|
|
258
|
+
* @return bool
|
|
259
|
+
*/
|
|
260
|
+
public static function iskey(mixed $key): bool
|
|
261
|
+
{
|
|
262
|
+
if ($key === self::UNDEF) { // Explicit check for UNDEF
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
if (is_string($key)) {
|
|
266
|
+
return strlen($key) > 0;
|
|
267
|
+
}
|
|
268
|
+
return is_int($key) || is_float($key);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check if a value is empty.
|
|
272
|
+
* Considers undefined, null, empty string, empty array, or empty object.
|
|
273
|
+
*
|
|
274
|
+
* @param mixed $val
|
|
275
|
+
* @return bool
|
|
276
|
+
*/
|
|
277
|
+
public static function isempty(mixed $val): bool
|
|
278
|
+
{
|
|
279
|
+
if ($val === self::UNDEF || $val === null || $val === self::S_MT) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (is_array($val) && count($val) === 0) {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
if (is_object($val) && count(get_object_vars($val)) === 0) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if a value is callable.
|
|
293
|
+
*
|
|
294
|
+
* @param mixed $val
|
|
295
|
+
* @return bool
|
|
296
|
+
*/
|
|
297
|
+
public static function isfunc(mixed $val): bool
|
|
298
|
+
{
|
|
299
|
+
return is_callable($val);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public static function typify(mixed $value): int
|
|
303
|
+
{
|
|
304
|
+
if ($value === self::UNDEF) {
|
|
305
|
+
return self::T_noval;
|
|
306
|
+
}
|
|
307
|
+
if ($value === null) {
|
|
308
|
+
return self::T_scalar | self::T_null;
|
|
309
|
+
}
|
|
310
|
+
if (is_bool($value)) {
|
|
311
|
+
return self::T_scalar | self::T_boolean;
|
|
312
|
+
}
|
|
313
|
+
if (is_int($value)) {
|
|
314
|
+
return self::T_scalar | self::T_number | self::T_integer;
|
|
315
|
+
}
|
|
316
|
+
if (is_float($value)) {
|
|
317
|
+
return self::T_scalar | self::T_number | self::T_decimal;
|
|
318
|
+
}
|
|
319
|
+
if (is_string($value)) {
|
|
320
|
+
return self::T_scalar | self::T_string;
|
|
321
|
+
}
|
|
322
|
+
if ($value instanceof \Closure) {
|
|
323
|
+
return self::T_scalar | self::T_function;
|
|
324
|
+
}
|
|
325
|
+
if (is_callable($value) && !is_array($value) && !is_object($value)) {
|
|
326
|
+
return self::T_scalar | self::T_function;
|
|
327
|
+
}
|
|
328
|
+
if ($value instanceof ListRef) {
|
|
329
|
+
return self::T_node | self::T_list;
|
|
330
|
+
}
|
|
331
|
+
if (is_array($value)) {
|
|
332
|
+
if (self::islist($value)) {
|
|
333
|
+
return self::T_node | self::T_list;
|
|
334
|
+
} else {
|
|
335
|
+
return self::T_node | self::T_map;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (is_object($value)) {
|
|
339
|
+
return self::T_node | self::T_map;
|
|
340
|
+
}
|
|
341
|
+
return self::T_noval;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public static function typename(int $type): string
|
|
345
|
+
{
|
|
346
|
+
if ($type <= 0) {
|
|
347
|
+
return self::TYPENAME[0];
|
|
348
|
+
}
|
|
349
|
+
$clz = 31 - (int) floor(log($type, 2));
|
|
350
|
+
return self::TYPENAME[$clz] ?? self::TYPENAME[0];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get a defined value. Returns alt if val is undefined.
|
|
355
|
+
*/
|
|
356
|
+
public static function getdef(mixed $val, mixed $alt): mixed
|
|
357
|
+
{
|
|
358
|
+
if ($val === self::UNDEF || $val === null) {
|
|
359
|
+
return $alt;
|
|
360
|
+
}
|
|
361
|
+
return $val;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Replace a search string (all), or a regex pattern, in a source string.
|
|
366
|
+
*/
|
|
367
|
+
public static function replace(string $s, string|array $from, mixed $to): string
|
|
368
|
+
{
|
|
369
|
+
$rs = $s;
|
|
370
|
+
$ts = self::typify($s);
|
|
371
|
+
if (0 === (self::T_string & $ts)) {
|
|
372
|
+
$rs = self::stringify($s);
|
|
373
|
+
} elseif (0 < ((self::T_noval | self::T_null) & $ts)) {
|
|
374
|
+
$rs = self::S_MT;
|
|
375
|
+
}
|
|
376
|
+
if (is_string($from) && @preg_match($from, '') !== false && $from[0] === '/') {
|
|
377
|
+
return preg_replace($from, (string)$to, $rs);
|
|
378
|
+
}
|
|
379
|
+
return str_replace((string)$from, (string)$to, $rs);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Define a JSON Object using key-value arguments.
|
|
384
|
+
*/
|
|
385
|
+
public static function jm(mixed ...$kv): object
|
|
386
|
+
{
|
|
387
|
+
$kvsize = count($kv);
|
|
388
|
+
$o = new \stdClass();
|
|
389
|
+
for ($i = 0; $i < $kvsize; $i += 2) {
|
|
390
|
+
$k = $kv[$i] ?? ('$KEY' . $i);
|
|
391
|
+
$k = is_string($k) ? $k : self::stringify($k);
|
|
392
|
+
$o->$k = $kv[$i + 1] ?? null;
|
|
393
|
+
}
|
|
394
|
+
return $o;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Define a JSON Array using arguments.
|
|
399
|
+
*/
|
|
400
|
+
public static function jt(mixed ...$v): array
|
|
401
|
+
{
|
|
402
|
+
return array_values($v);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
public static function getprop(mixed $val, mixed $key, mixed $alt = self::UNDEF): mixed
|
|
406
|
+
{
|
|
407
|
+
// 1) undefined‐marker or invalid key → alt
|
|
408
|
+
if ($val === self::UNDEF || $key === self::UNDEF) {
|
|
409
|
+
return $alt;
|
|
410
|
+
}
|
|
411
|
+
if (!self::iskey($key)) {
|
|
412
|
+
return $alt;
|
|
413
|
+
}
|
|
414
|
+
if ($val === null) {
|
|
415
|
+
return $alt;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 2) ListRef branch
|
|
419
|
+
if ($val instanceof ListRef) {
|
|
420
|
+
$ki = is_numeric($key) ? (int)$key : -1;
|
|
421
|
+
$out = ($ki >= 0 && $ki < count($val->list)) ? $val->list[$ki] : $alt;
|
|
422
|
+
}
|
|
423
|
+
// 3) array branch
|
|
424
|
+
elseif (is_array($val) && array_key_exists($key, $val)) {
|
|
425
|
+
$out = $val[$key];
|
|
426
|
+
}
|
|
427
|
+
// 4) object branch: cast $key to string
|
|
428
|
+
elseif (is_object($val)) {
|
|
429
|
+
$prop = (string) $key;
|
|
430
|
+
if (property_exists($val, $prop)) {
|
|
431
|
+
$out = $val->$prop;
|
|
432
|
+
} else {
|
|
433
|
+
$out = $alt;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// 4) fallback
|
|
437
|
+
else {
|
|
438
|
+
$out = $alt;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 5) JSON‐null‐marker check
|
|
442
|
+
return ($out === self::UNDEF ? $alt : $out);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
public static function strkey(mixed $key = self::UNDEF): string
|
|
447
|
+
{
|
|
448
|
+
if ($key === self::UNDEF) {
|
|
449
|
+
return self::S_MT;
|
|
450
|
+
}
|
|
451
|
+
if (is_string($key)) {
|
|
452
|
+
return $key;
|
|
453
|
+
}
|
|
454
|
+
if (is_bool($key)) {
|
|
455
|
+
return self::S_MT;
|
|
456
|
+
}
|
|
457
|
+
if (is_int($key)) {
|
|
458
|
+
return (string) $key;
|
|
459
|
+
}
|
|
460
|
+
if (is_float($key)) {
|
|
461
|
+
return (string) floor($key);
|
|
462
|
+
}
|
|
463
|
+
return self::S_MT;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get a sorted list of keys from a node (map or list).
|
|
468
|
+
*
|
|
469
|
+
* @param mixed $val
|
|
470
|
+
* @return array
|
|
471
|
+
*/
|
|
472
|
+
public static function keysof(mixed $val): array
|
|
473
|
+
{
|
|
474
|
+
if (!self::isnode($val)) {
|
|
475
|
+
return [];
|
|
476
|
+
}
|
|
477
|
+
if (self::ismap($val)) {
|
|
478
|
+
$keys = is_array($val) ? array_keys($val) : array_keys(get_object_vars($val));
|
|
479
|
+
sort($keys, SORT_STRING);
|
|
480
|
+
return $keys;
|
|
481
|
+
} elseif ($val instanceof ListRef) {
|
|
482
|
+
return array_map('strval', array_keys($val->list));
|
|
483
|
+
} elseif (self::islist($val)) {
|
|
484
|
+
return array_map('strval', array_keys($val));
|
|
485
|
+
}
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Determine if a node has a defined property with the given key.
|
|
491
|
+
*
|
|
492
|
+
* @param mixed $val
|
|
493
|
+
* @param mixed $key
|
|
494
|
+
* @return bool
|
|
495
|
+
*/
|
|
496
|
+
public static function haskey(mixed $val = self::UNDEF, mixed $key = self::UNDEF): bool
|
|
497
|
+
{
|
|
498
|
+
// 1. Validate $val is a node
|
|
499
|
+
if (!self::isnode($val)) {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 2. Validate $key is a valid key
|
|
504
|
+
if (!self::iskey($key)) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 3. Check property existence
|
|
509
|
+
$marker = new \stdClass();
|
|
510
|
+
return self::getprop($val, $key, $marker) !== $marker;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
public static function items(mixed $val, ?callable $apply = null): array
|
|
514
|
+
{
|
|
515
|
+
$result = [];
|
|
516
|
+
if (self::islist($val)) {
|
|
517
|
+
foreach ($val as $k => $v) {
|
|
518
|
+
$result[] = [(string) $k, $v];
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
foreach (self::keysof($val) as $k) {
|
|
522
|
+
$result[] = [$k, self::getprop($val, $k)];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if ($apply !== null) {
|
|
526
|
+
$result = array_map($apply, $result);
|
|
527
|
+
}
|
|
528
|
+
return $result;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
public static function escre(?string $s): string
|
|
532
|
+
{
|
|
533
|
+
$s = $s ?? self::S_MT;
|
|
534
|
+
return preg_quote($s, '/');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
public static function escurl(?string $s): string
|
|
538
|
+
{
|
|
539
|
+
$s = $s ?? self::S_MT;
|
|
540
|
+
return rawurlencode($s);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
public static function joinurl(array $sarr): string
|
|
544
|
+
{
|
|
545
|
+
return self::join($sarr, '/', true);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
public static function filter(mixed $val, callable $check): array
|
|
549
|
+
{
|
|
550
|
+
$all = self::items($val);
|
|
551
|
+
$numall = self::size($all);
|
|
552
|
+
$out = [];
|
|
553
|
+
for ($i = 0; $i < $numall; $i++) {
|
|
554
|
+
if ($check($all[$i])) {
|
|
555
|
+
$out[] = $all[$i][1];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return $out;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
public static function join(mixed $arr, ?string $sep = null, ?bool $url = false): string
|
|
562
|
+
{
|
|
563
|
+
$sarr = self::size($arr);
|
|
564
|
+
$sepdef = $sep ?? self::S_CM;
|
|
565
|
+
$sepre = (1 === strlen($sepdef)) ? self::escre($sepdef) : '';
|
|
566
|
+
|
|
567
|
+
$filtered = self::filter($arr, function ($n) {
|
|
568
|
+
return (0 < (self::T_string & self::typify($n[1]))) && self::S_MT !== $n[1];
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
$mapped = self::filter(
|
|
572
|
+
self::items($filtered, function ($n) use ($sepre, $sepdef, $url, $sarr) {
|
|
573
|
+
$i = (int) $n[0];
|
|
574
|
+
$s = $n[1];
|
|
575
|
+
|
|
576
|
+
if ('' !== $sepre && self::S_MT !== $sepre) {
|
|
577
|
+
if ($url && 0 === $i) {
|
|
578
|
+
$s = preg_replace('/' . $sepre . '+$/', self::S_MT, $s);
|
|
579
|
+
return $s;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (0 < $i) {
|
|
583
|
+
$s = preg_replace('/^' . $sepre . '+/', self::S_MT, $s);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if ($i < $sarr - 1 || !$url) {
|
|
587
|
+
$s = preg_replace('/' . $sepre . '+$/', self::S_MT, $s);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
$s = preg_replace('/([^' . $sepre . '])' . $sepre . '+([^' . $sepre . '])/',
|
|
591
|
+
'$1' . $sepdef . '$2', $s);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return $s;
|
|
595
|
+
}),
|
|
596
|
+
function ($n) {
|
|
597
|
+
return self::S_MT !== $n[1];
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
return implode($sepdef, $mapped);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
public static function jsonify(mixed $val, mixed $flags = null): string
|
|
605
|
+
{
|
|
606
|
+
$str = 'null';
|
|
607
|
+
|
|
608
|
+
if ($val !== null && $val !== self::UNDEF && !($val instanceof \Closure)) {
|
|
609
|
+
if ($val instanceof ListRef) {
|
|
610
|
+
$val = self::cloneUnwrap($val);
|
|
611
|
+
}
|
|
612
|
+
$indent = self::getprop($flags, 'indent', 2);
|
|
613
|
+
try {
|
|
614
|
+
$encoded = json_encode($val, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
615
|
+
if ($encoded === false) {
|
|
616
|
+
return '__JSONIFY_FAILED__';
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// PHP's JSON_PRETTY_PRINT uses 4-space indents; convert to requested indent
|
|
620
|
+
$encoded = preg_replace_callback('/^( +)/m', function ($matches) use ($indent) {
|
|
621
|
+
$level = (int)(strlen($matches[1]) / 4);
|
|
622
|
+
return str_repeat(' ', $level * $indent);
|
|
623
|
+
}, $encoded);
|
|
624
|
+
|
|
625
|
+
$str = $encoded;
|
|
626
|
+
|
|
627
|
+
$offset = self::getprop($flags, 'offset', 0);
|
|
628
|
+
if (0 < $offset) {
|
|
629
|
+
$lines = explode("\n", $str);
|
|
630
|
+
$rest = array_slice($lines, 1);
|
|
631
|
+
$padded = self::filter(
|
|
632
|
+
self::items($rest, function ($n) use ($offset) {
|
|
633
|
+
return self::pad($n[1], 0 - $offset - self::size($n[1]));
|
|
634
|
+
}),
|
|
635
|
+
function ($n) { return true; }
|
|
636
|
+
);
|
|
637
|
+
$str = "{\n" . implode("\n", $padded);
|
|
638
|
+
}
|
|
639
|
+
} catch (\Exception $e) {
|
|
640
|
+
$str = '__JSONIFY_FAILED__';
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return $str;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* The integer size of the value. For arrays and strings, the length,
|
|
649
|
+
* for numbers, the integer part, for boolean, true is 1 and false is 0, for all other values, 0.
|
|
650
|
+
*/
|
|
651
|
+
public static function size(mixed $val): int
|
|
652
|
+
{
|
|
653
|
+
if ($val === null || $val === self::UNDEF) {
|
|
654
|
+
return 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (self::islist($val)) {
|
|
658
|
+
return count($val);
|
|
659
|
+
} elseif (self::ismap($val)) {
|
|
660
|
+
return count(get_object_vars($val));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (is_string($val)) {
|
|
664
|
+
return strlen($val);
|
|
665
|
+
} elseif (is_numeric($val)) {
|
|
666
|
+
return (int) floor((float) $val);
|
|
667
|
+
} elseif (is_bool($val)) {
|
|
668
|
+
return $val ? 1 : 0;
|
|
669
|
+
} else {
|
|
670
|
+
return 0;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Extract part of an array or string into a new value, from the start point to the end point.
|
|
676
|
+
* If no end is specified, extract to the full length of the value. Negative arguments count
|
|
677
|
+
* from the end of the value.
|
|
678
|
+
*/
|
|
679
|
+
public static function slice(mixed $val, ?int $start = null, ?int $end = null): mixed
|
|
680
|
+
{
|
|
681
|
+
if (is_numeric($val)) {
|
|
682
|
+
$start = $start ?? PHP_INT_MIN;
|
|
683
|
+
$end = ($end ?? PHP_INT_MAX) - 1;
|
|
684
|
+
$result = min(max((float) $val, $start), $end);
|
|
685
|
+
// Return integer if the original value was an integer
|
|
686
|
+
return is_int($val) ? (int) $result : $result;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
$vlen = self::size($val);
|
|
690
|
+
|
|
691
|
+
if ($end !== null && $start === null) {
|
|
692
|
+
$start = 0;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if ($start !== null) {
|
|
696
|
+
if ($start < 0) {
|
|
697
|
+
$end = $vlen + $start;
|
|
698
|
+
if ($end < 0) {
|
|
699
|
+
$end = 0;
|
|
700
|
+
}
|
|
701
|
+
$start = 0;
|
|
702
|
+
} elseif ($end !== null) {
|
|
703
|
+
if ($end < 0) {
|
|
704
|
+
$end = $vlen + $end;
|
|
705
|
+
if ($end < 0) {
|
|
706
|
+
$end = 0;
|
|
707
|
+
}
|
|
708
|
+
} elseif ($vlen < $end) {
|
|
709
|
+
$end = $vlen;
|
|
710
|
+
}
|
|
711
|
+
} else {
|
|
712
|
+
$end = $vlen;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if ($vlen < $start) {
|
|
716
|
+
$start = $vlen;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (-1 < $start && $start <= $end && $end <= $vlen) {
|
|
720
|
+
if ($val instanceof ListRef) {
|
|
721
|
+
$val = new ListRef(array_slice($val->list, $start, $end - $start));
|
|
722
|
+
} elseif (self::islist($val)) {
|
|
723
|
+
$val = array_slice($val, $start, $end - $start);
|
|
724
|
+
} elseif (is_string($val)) {
|
|
725
|
+
$val = substr($val, $start, $end - $start);
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
if ($val instanceof ListRef) {
|
|
729
|
+
$val = new ListRef([]);
|
|
730
|
+
} elseif (self::islist($val)) {
|
|
731
|
+
$val = [];
|
|
732
|
+
} elseif (is_string($val)) {
|
|
733
|
+
$val = self::S_MT;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return $val;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Pad a string with a character to a specified length.
|
|
743
|
+
*/
|
|
744
|
+
public static function pad(mixed $str, ?int $padding = null, ?string $padchar = null): string
|
|
745
|
+
{
|
|
746
|
+
$str = self::stringify($str);
|
|
747
|
+
$padding = $padding ?? 44;
|
|
748
|
+
$padchar = $padchar ?? ' ';
|
|
749
|
+
$padchar = ($padchar . ' ')[0]; // Get first character or space as fallback
|
|
750
|
+
|
|
751
|
+
if ($padding >= 0) {
|
|
752
|
+
return str_pad($str, $padding, $padchar, STR_PAD_RIGHT);
|
|
753
|
+
} else {
|
|
754
|
+
return str_pad($str, abs($padding), $padchar, STR_PAD_LEFT);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/* =======================
|
|
759
|
+
* Stringification and Cloning
|
|
760
|
+
* =======================
|
|
761
|
+
*/
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Recursively sorts a node (array or object) to ensure consistent stringification.
|
|
765
|
+
*
|
|
766
|
+
* @param mixed $val
|
|
767
|
+
* @return mixed
|
|
768
|
+
*/
|
|
769
|
+
private static function sort_obj(mixed $val): mixed
|
|
770
|
+
{
|
|
771
|
+
if (is_array($val)) {
|
|
772
|
+
if (self::islist($val)) {
|
|
773
|
+
return array_map([self::class, 'sort_obj'], $val);
|
|
774
|
+
} else {
|
|
775
|
+
ksort($val);
|
|
776
|
+
foreach ($val as $k => $v) {
|
|
777
|
+
$val[$k] = self::sort_obj($v);
|
|
778
|
+
}
|
|
779
|
+
return $val;
|
|
780
|
+
}
|
|
781
|
+
} elseif (is_object($val)) {
|
|
782
|
+
$arr = get_object_vars($val);
|
|
783
|
+
ksort($arr);
|
|
784
|
+
foreach ($arr as $k => $v) {
|
|
785
|
+
$arr[$k] = self::sort_obj($v);
|
|
786
|
+
}
|
|
787
|
+
return $arr;
|
|
788
|
+
}
|
|
789
|
+
return $val;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
public static function stringify(mixed $val, ?int $maxlen = null, mixed $pretty = null): string
|
|
793
|
+
{
|
|
794
|
+
if ($val === self::UNDEF) {
|
|
795
|
+
return $pretty ? '<>' : self::S_MT;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
$valstr = self::S_MT;
|
|
799
|
+
|
|
800
|
+
if (is_string($val)) {
|
|
801
|
+
$valstr = $val;
|
|
802
|
+
} else {
|
|
803
|
+
$original = $val;
|
|
804
|
+
// Unwrap ListRefs before JSON encoding
|
|
805
|
+
if ($val instanceof ListRef) {
|
|
806
|
+
$val = self::cloneUnwrap($val);
|
|
807
|
+
}
|
|
808
|
+
try {
|
|
809
|
+
$sorted = self::sort_obj($val);
|
|
810
|
+
$str = json_encode($sorted);
|
|
811
|
+
if ($str === false) {
|
|
812
|
+
$str = '__STRINGIFY_FAILED__';
|
|
813
|
+
}
|
|
814
|
+
$valstr = str_replace('"', '', $str);
|
|
815
|
+
|
|
816
|
+
if (is_object($original) && $valstr === '[]') {
|
|
817
|
+
$valstr = '{}';
|
|
818
|
+
}
|
|
819
|
+
} catch (\Exception $e) {
|
|
820
|
+
$valstr = '__STRINGIFY_FAILED__';
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if ($maxlen !== null && $maxlen > -1) {
|
|
825
|
+
$js = substr($valstr, 0, $maxlen);
|
|
826
|
+
$valstr = $maxlen < strlen($valstr)
|
|
827
|
+
? (substr($js, 0, $maxlen - 3) . '...')
|
|
828
|
+
: $valstr;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
return $valstr;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
public static function pathify(mixed $val, ?int $startin = null, ?int $endin = null): string
|
|
835
|
+
{
|
|
836
|
+
$UNDEF = self::UNDEF;
|
|
837
|
+
$S_MT = self::S_MT;
|
|
838
|
+
$S_CN = self::S_CN;
|
|
839
|
+
$S_DT = self::S_DT;
|
|
840
|
+
|
|
841
|
+
if (is_array($val) && (self::islist($val) || count($val) === 0)) {
|
|
842
|
+
$path = $val;
|
|
843
|
+
} elseif (is_string($val) || is_int($val) || is_float($val)) {
|
|
844
|
+
$path = [$val];
|
|
845
|
+
} else {
|
|
846
|
+
$path = $UNDEF;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
$start = ($startin === null || $startin < 0) ? 0 : $startin;
|
|
850
|
+
$end = ($endin === null || $endin < 0) ? 0 : $endin;
|
|
851
|
+
|
|
852
|
+
$pathstr = $UNDEF;
|
|
853
|
+
|
|
854
|
+
if ($path !== $UNDEF && $start >= 0) {
|
|
855
|
+
$len = count($path);
|
|
856
|
+
$length = max(0, $len - $end - $start);
|
|
857
|
+
$slice = array_slice($path, $start, $length);
|
|
858
|
+
|
|
859
|
+
if (count($slice) === 0) {
|
|
860
|
+
$pathstr = '<root>';
|
|
861
|
+
} else {
|
|
862
|
+
$parts = [];
|
|
863
|
+
foreach ($slice as $p) {
|
|
864
|
+
if (!self::iskey($p)) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
if (is_int($p) || is_float($p)) {
|
|
868
|
+
$parts[] = $S_MT . (string) floor($p);
|
|
869
|
+
} else {
|
|
870
|
+
$parts[] = str_replace('.', $S_MT, (string) $p);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
$pathstr = implode($S_DT, $parts);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if ($pathstr === $UNDEF) {
|
|
878
|
+
if ($val === $UNDEF || $val === null) {
|
|
879
|
+
$pathstr = '<unknown-path>';
|
|
880
|
+
} elseif (is_object($val) && count(get_object_vars($val)) === 0) {
|
|
881
|
+
// empty object
|
|
882
|
+
$pathstr = '<unknown-path:{}>';
|
|
883
|
+
} else {
|
|
884
|
+
// booleans, numbers, non-empty objects, etc.
|
|
885
|
+
$pathstr = '<unknown-path' . $S_CN . self::stringify($val, 47) . '>';
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return $pathstr;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
public static function flatten(mixed $list, ?int $depth = null): mixed
|
|
894
|
+
{
|
|
895
|
+
if (!self::islist($list)) {
|
|
896
|
+
return $list;
|
|
897
|
+
}
|
|
898
|
+
$depth = $depth ?? 1;
|
|
899
|
+
$result = [];
|
|
900
|
+
foreach ($list as $item) {
|
|
901
|
+
if (self::islist($item) && $depth > 0) {
|
|
902
|
+
$sub = self::flatten($item, $depth - 1);
|
|
903
|
+
foreach ($sub as $v) {
|
|
904
|
+
$result[] = $v;
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
$result[] = $item;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return $result;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
public static function clone(mixed $val): mixed
|
|
914
|
+
{
|
|
915
|
+
if ($val === self::UNDEF) {
|
|
916
|
+
return self::UNDEF;
|
|
917
|
+
}
|
|
918
|
+
$refs = [];
|
|
919
|
+
$replacer = function (mixed $v) use (&$refs, &$replacer): mixed {
|
|
920
|
+
if ($v instanceof \Closure) {
|
|
921
|
+
$refs[] = $v;
|
|
922
|
+
return '`$FUNCTION:' . (count($refs) - 1) . '`';
|
|
923
|
+
} elseif (is_callable($v) && !is_array($v) && !($v instanceof ListRef)) {
|
|
924
|
+
$refs[] = $v;
|
|
925
|
+
return '`$FUNCTION:' . (count($refs) - 1) . '`';
|
|
926
|
+
} elseif ($v instanceof ListRef) {
|
|
927
|
+
$newList = [];
|
|
928
|
+
foreach ($v->list as $item) {
|
|
929
|
+
$newList[] = $replacer($item);
|
|
930
|
+
}
|
|
931
|
+
return new ListRef($newList);
|
|
932
|
+
} elseif (is_array($v)) {
|
|
933
|
+
$result = [];
|
|
934
|
+
foreach ($v as $k => $item) {
|
|
935
|
+
$result[$k] = $replacer($item);
|
|
936
|
+
}
|
|
937
|
+
return $result;
|
|
938
|
+
} elseif (is_object($v)) {
|
|
939
|
+
$objVars = get_object_vars($v);
|
|
940
|
+
$result = new \stdClass();
|
|
941
|
+
foreach ($objVars as $k => $item) {
|
|
942
|
+
$result->$k = $replacer($item);
|
|
943
|
+
}
|
|
944
|
+
return $result;
|
|
945
|
+
} else {
|
|
946
|
+
return $v;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
$temp = $replacer($val);
|
|
950
|
+
$reviver = function (mixed $v) use (&$refs, &$reviver): mixed {
|
|
951
|
+
if (is_string($v)) {
|
|
952
|
+
if (preg_match('/^`\$FUNCTION:([0-9]+)`$/', $v, $matches)) {
|
|
953
|
+
return $refs[(int) $matches[1]];
|
|
954
|
+
}
|
|
955
|
+
return $v;
|
|
956
|
+
} elseif ($v instanceof ListRef) {
|
|
957
|
+
$newList = [];
|
|
958
|
+
foreach ($v->list as $item) {
|
|
959
|
+
$newList[] = $reviver($item);
|
|
960
|
+
}
|
|
961
|
+
return new ListRef($newList);
|
|
962
|
+
} elseif (is_array($v)) {
|
|
963
|
+
$result = [];
|
|
964
|
+
foreach ($v as $k => $item) {
|
|
965
|
+
$result[$k] = $reviver($item);
|
|
966
|
+
}
|
|
967
|
+
return $result;
|
|
968
|
+
} elseif (is_object($v)) {
|
|
969
|
+
$objVars = get_object_vars($v);
|
|
970
|
+
$result = new \stdClass();
|
|
971
|
+
foreach ($objVars as $k => $item) {
|
|
972
|
+
$result->$k = $reviver($item);
|
|
973
|
+
}
|
|
974
|
+
return $result;
|
|
975
|
+
} else {
|
|
976
|
+
return $v;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
return $reviver($temp);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Clone a value, wrapping all sequential arrays in ListRef for reference stability.
|
|
984
|
+
* Mirrors Go's CloneFlags(val, {wrap: true}).
|
|
985
|
+
*/
|
|
986
|
+
public static function cloneWrap(mixed $val): mixed
|
|
987
|
+
{
|
|
988
|
+
if ($val === null || $val === self::UNDEF) {
|
|
989
|
+
return $val;
|
|
990
|
+
}
|
|
991
|
+
if (is_callable($val) && !is_array($val) && !is_object($val)) {
|
|
992
|
+
return $val;
|
|
993
|
+
}
|
|
994
|
+
if ($val instanceof ListRef) {
|
|
995
|
+
$newList = [];
|
|
996
|
+
foreach ($val->list as $item) {
|
|
997
|
+
$newList[] = self::cloneWrap($item);
|
|
998
|
+
}
|
|
999
|
+
return new ListRef($newList);
|
|
1000
|
+
}
|
|
1001
|
+
if (is_array($val)) {
|
|
1002
|
+
if (self::isListHelper($val) || empty($val)) {
|
|
1003
|
+
$newList = [];
|
|
1004
|
+
foreach ($val as $item) {
|
|
1005
|
+
$newList[] = self::cloneWrap($item);
|
|
1006
|
+
}
|
|
1007
|
+
return new ListRef($newList);
|
|
1008
|
+
}
|
|
1009
|
+
// Assoc array (map-array) - clone as stdClass
|
|
1010
|
+
$result = new \stdClass();
|
|
1011
|
+
foreach ($val as $k => $v) {
|
|
1012
|
+
$result->$k = self::cloneWrap($v);
|
|
1013
|
+
}
|
|
1014
|
+
return $result;
|
|
1015
|
+
}
|
|
1016
|
+
if ($val instanceof \stdClass) {
|
|
1017
|
+
$result = new \stdClass();
|
|
1018
|
+
foreach (get_object_vars($val) as $k => $v) {
|
|
1019
|
+
$result->$k = self::cloneWrap($v);
|
|
1020
|
+
}
|
|
1021
|
+
return $result;
|
|
1022
|
+
}
|
|
1023
|
+
// Class instances and scalars returned as-is
|
|
1024
|
+
return $val;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Clone a value, unwrapping all ListRef back to plain arrays.
|
|
1029
|
+
* Mirrors Go's CloneFlags(val, {unwrap: true}).
|
|
1030
|
+
*/
|
|
1031
|
+
public static function cloneUnwrap(mixed $val, int $depth = 0): mixed
|
|
1032
|
+
{
|
|
1033
|
+
if ($depth > 32) {
|
|
1034
|
+
return $val;
|
|
1035
|
+
}
|
|
1036
|
+
if ($val instanceof ListRef) {
|
|
1037
|
+
$result = [];
|
|
1038
|
+
foreach ($val->list as $item) {
|
|
1039
|
+
$result[] = self::cloneUnwrap($item, $depth + 1);
|
|
1040
|
+
}
|
|
1041
|
+
return $result;
|
|
1042
|
+
}
|
|
1043
|
+
if ($val instanceof \stdClass) {
|
|
1044
|
+
$result = new \stdClass();
|
|
1045
|
+
foreach (get_object_vars($val) as $k => $v) {
|
|
1046
|
+
$result->$k = self::cloneUnwrap($v, $depth + 1);
|
|
1047
|
+
}
|
|
1048
|
+
return $result;
|
|
1049
|
+
}
|
|
1050
|
+
if (is_array($val)) {
|
|
1051
|
+
$result = [];
|
|
1052
|
+
foreach ($val as $k => $v) {
|
|
1053
|
+
$result[$k] = self::cloneUnwrap($v, $depth + 1);
|
|
1054
|
+
}
|
|
1055
|
+
return $result;
|
|
1056
|
+
}
|
|
1057
|
+
return $val;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* @internal
|
|
1062
|
+
* Set a property or list‐index on a "node" (stdClass or PHP array).
|
|
1063
|
+
* Respects undef‐marker removals, numeric vs string keys, and
|
|
1064
|
+
* list‐vs‐map semantics.
|
|
1065
|
+
*/
|
|
1066
|
+
public static function setprop(mixed &$parent, mixed $key, mixed $val): mixed
|
|
1067
|
+
{
|
|
1068
|
+
// only valid keys make sense
|
|
1069
|
+
if (!self::iskey($key)) {
|
|
1070
|
+
return $parent;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// ─── LISTREF ────────────────────────────────────────────────
|
|
1074
|
+
if ($parent instanceof ListRef) {
|
|
1075
|
+
if (!is_numeric($key)) {
|
|
1076
|
+
return $parent;
|
|
1077
|
+
}
|
|
1078
|
+
$keyI = (int) floor((float) $key);
|
|
1079
|
+
if ($val === self::UNDEF) {
|
|
1080
|
+
if ($keyI >= 0 && $keyI < count($parent->list)) {
|
|
1081
|
+
array_splice($parent->list, $keyI, 1);
|
|
1082
|
+
}
|
|
1083
|
+
} elseif ($keyI >= 0) {
|
|
1084
|
+
if ($keyI >= count($parent->list)) {
|
|
1085
|
+
$parent->list[] = $val;
|
|
1086
|
+
} else {
|
|
1087
|
+
$parent->list[$keyI] = $val;
|
|
1088
|
+
}
|
|
1089
|
+
} else {
|
|
1090
|
+
array_unshift($parent->list, $val);
|
|
1091
|
+
}
|
|
1092
|
+
return $parent;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// ─── OBJECT (map) ───────────────────────────────────────────
|
|
1096
|
+
if (is_object($parent)) {
|
|
1097
|
+
$keyStr = self::strkey($key);
|
|
1098
|
+
if ($val === self::UNDEF) {
|
|
1099
|
+
unset($parent->$keyStr);
|
|
1100
|
+
} else {
|
|
1101
|
+
$parent->$keyStr = $val;
|
|
1102
|
+
}
|
|
1103
|
+
return $parent;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// ─── ARRAY ──────────────────────────────────────────────────
|
|
1107
|
+
if (is_array($parent)) {
|
|
1108
|
+
if (!self::islist($parent)) {
|
|
1109
|
+
// map‐array
|
|
1110
|
+
$keyStr = self::strkey($key);
|
|
1111
|
+
if ($val === self::UNDEF) {
|
|
1112
|
+
unset($parent[$keyStr]);
|
|
1113
|
+
} elseif (ctype_digit((string) $key)) {
|
|
1114
|
+
// numeric string key: unshift (TS always merges maps by overwriting)
|
|
1115
|
+
$parent = [$keyStr => $val] + $parent;
|
|
1116
|
+
} else {
|
|
1117
|
+
$parent[$keyStr] = $val;
|
|
1118
|
+
}
|
|
1119
|
+
} else {
|
|
1120
|
+
// list‐array
|
|
1121
|
+
if (!is_numeric($key)) {
|
|
1122
|
+
return $parent;
|
|
1123
|
+
}
|
|
1124
|
+
$keyI = (int) floor((float) $key);
|
|
1125
|
+
if ($val === self::UNDEF) {
|
|
1126
|
+
if ($keyI >= 0 && $keyI < count($parent)) {
|
|
1127
|
+
array_splice($parent, $keyI, 1);
|
|
1128
|
+
}
|
|
1129
|
+
} elseif ($keyI >= 0) {
|
|
1130
|
+
if (count($parent) < $keyI) {
|
|
1131
|
+
$parent[] = $val;
|
|
1132
|
+
} else {
|
|
1133
|
+
$parent[$keyI] = $val;
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
array_unshift($parent, $val);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return $parent;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
private const MAXDEPTH = 32;
|
|
1146
|
+
|
|
1147
|
+
public static function walk(
|
|
1148
|
+
mixed $val,
|
|
1149
|
+
?callable $before = null,
|
|
1150
|
+
?callable $after = null,
|
|
1151
|
+
?int $maxdepth = null,
|
|
1152
|
+
mixed $key = null,
|
|
1153
|
+
mixed $parent = null,
|
|
1154
|
+
?array $path = null
|
|
1155
|
+
): mixed {
|
|
1156
|
+
if ($path === null) {
|
|
1157
|
+
$path = [];
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
$out = ($before !== null) ? $before($key, $val, $parent, $path) : $val;
|
|
1161
|
+
|
|
1162
|
+
$md = ($maxdepth !== null && $maxdepth >= 0) ? $maxdepth : self::MAXDEPTH;
|
|
1163
|
+
if (0 === $md || (count($path) > 0 && $md <= count($path))) {
|
|
1164
|
+
return $out;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
if (self::isnode($out)) {
|
|
1168
|
+
foreach (self::items($out) as [$childKey, $childVal]) {
|
|
1169
|
+
$childPath = self::flatten([$path, self::S_MT . $childKey]);
|
|
1170
|
+
$result = self::walk(
|
|
1171
|
+
$childVal, $before, $after, $md, $childKey, $out, $childPath
|
|
1172
|
+
);
|
|
1173
|
+
if (self::ismap($out)) {
|
|
1174
|
+
if (is_object($out)) {
|
|
1175
|
+
$out->{self::strkey($childKey)} = $result;
|
|
1176
|
+
} else {
|
|
1177
|
+
$out[self::strkey($childKey)] = $result;
|
|
1178
|
+
}
|
|
1179
|
+
} else {
|
|
1180
|
+
$out[(int) $childKey] = $result;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
$out = ($after !== null) ? $after($key, $out, $parent, $path) : $out;
|
|
1186
|
+
|
|
1187
|
+
return $out;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
public static function merge(mixed $val, ?int $maxdepth = null): mixed
|
|
1191
|
+
{
|
|
1192
|
+
$md = self::slice($maxdepth ?? self::MAXDEPTH, 0);
|
|
1193
|
+
|
|
1194
|
+
if (!self::islist($val)) {
|
|
1195
|
+
return $val;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
$list = $val;
|
|
1199
|
+
$lenlist = count($list);
|
|
1200
|
+
|
|
1201
|
+
if (0 === $lenlist) {
|
|
1202
|
+
return self::UNDEF;
|
|
1203
|
+
} elseif (1 === $lenlist) {
|
|
1204
|
+
return $list[0];
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
$out = self::getprop($list, 0, new \stdClass());
|
|
1208
|
+
|
|
1209
|
+
for ($oI = 1; $oI < $lenlist; $oI++) {
|
|
1210
|
+
$obj = $list[$oI];
|
|
1211
|
+
|
|
1212
|
+
if (!self::isnode($obj)) {
|
|
1213
|
+
$out = $obj;
|
|
1214
|
+
} else {
|
|
1215
|
+
$cur = [&$out];
|
|
1216
|
+
$dst = [&$out];
|
|
1217
|
+
|
|
1218
|
+
$before = function ($key, $val, $_parent, $path) use (&$cur, &$dst, $md) {
|
|
1219
|
+
$pI = self::size($path);
|
|
1220
|
+
|
|
1221
|
+
if ($md <= $pI) {
|
|
1222
|
+
self::setprop($cur[$pI - 1], $key, $val);
|
|
1223
|
+
} elseif (!self::isnode($val)) {
|
|
1224
|
+
$cur[$pI] = $val;
|
|
1225
|
+
} else {
|
|
1226
|
+
$dst[$pI] = 0 < $pI ? self::getprop($dst[$pI - 1], $key) : $dst[$pI];
|
|
1227
|
+
$tval = $dst[$pI];
|
|
1228
|
+
|
|
1229
|
+
if (self::UNDEF === $tval && 0 === (self::T_instance & self::typify($val))) {
|
|
1230
|
+
$cur[$pI] = self::islist($val) ? [] : new \stdClass();
|
|
1231
|
+
} elseif (self::typify($val) === self::typify($tval)) {
|
|
1232
|
+
$cur[$pI] = $tval;
|
|
1233
|
+
} else {
|
|
1234
|
+
$cur[$pI] = $val;
|
|
1235
|
+
$val = self::UNDEF;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return $val;
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
$after = function ($key, $_val, $_parent, $path) use (&$cur) {
|
|
1243
|
+
$cI = self::size($path);
|
|
1244
|
+
$value = $cur[$cI] ?? null;
|
|
1245
|
+
if ($cI > 0) {
|
|
1246
|
+
self::setprop($cur[$cI - 1], $key, $value);
|
|
1247
|
+
}
|
|
1248
|
+
return $value;
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
$out = self::walk($obj, $before, $after, $md);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if (0 === $md) {
|
|
1256
|
+
$out = self::getelem($list, -1);
|
|
1257
|
+
$out = self::islist($out) ? [] : (self::ismap($out) ? new \stdClass() : $out);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return $out;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
public static function getpath(
|
|
1264
|
+
mixed $store,
|
|
1265
|
+
mixed $path,
|
|
1266
|
+
mixed $injdef = null
|
|
1267
|
+
): mixed {
|
|
1268
|
+
// Convert path to array of parts
|
|
1269
|
+
$parts = is_array($path) ? $path :
|
|
1270
|
+
(is_string($path) ? explode('.', $path) :
|
|
1271
|
+
(is_numeric($path) ? [self::strkey($path)] : self::UNDEF));
|
|
1272
|
+
|
|
1273
|
+
if ($parts === self::UNDEF) {
|
|
1274
|
+
return self::UNDEF;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
$val = $store;
|
|
1278
|
+
$base = self::getprop($injdef, 'base');
|
|
1279
|
+
$src = self::getprop($store, $base, $store);
|
|
1280
|
+
$numparts = count($parts);
|
|
1281
|
+
$dparent = self::getprop($injdef, 'dparent');
|
|
1282
|
+
|
|
1283
|
+
// An empty path (incl empty string) just finds the src (base data)
|
|
1284
|
+
if ($path === null || $store === null || ($numparts === 1 && $parts[0] === '')) {
|
|
1285
|
+
$val = $src;
|
|
1286
|
+
} else if ($numparts > 0) {
|
|
1287
|
+
// Check for $ACTIONs
|
|
1288
|
+
if ($numparts === 1) {
|
|
1289
|
+
$val = self::getprop($store, $parts[0]);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (!self::isfunc($val)) {
|
|
1293
|
+
$val = $src;
|
|
1294
|
+
|
|
1295
|
+
// Check for meta path in first part
|
|
1296
|
+
if (preg_match('/^([^$]+)\$([=~])(.+)$/', $parts[0], $m) && $injdef && isset($injdef->meta)) {
|
|
1297
|
+
$val = self::getprop($injdef->meta, $m[1]);
|
|
1298
|
+
$parts[0] = $m[3];
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
$dpath = self::getprop($injdef, 'dpath');
|
|
1302
|
+
|
|
1303
|
+
for ($pI = 0; $val !== self::UNDEF && $pI < count($parts); $pI++) {
|
|
1304
|
+
$part = $parts[$pI];
|
|
1305
|
+
|
|
1306
|
+
if ($injdef && $part === '$KEY') {
|
|
1307
|
+
$part = self::getprop($injdef, 'key');
|
|
1308
|
+
} else if ($injdef && str_starts_with($part, '$GET:')) {
|
|
1309
|
+
// $GET:path$ -> get store value, use as path part (string)
|
|
1310
|
+
$getpath = substr($part, 5, -1);
|
|
1311
|
+
$getval = self::getpath($src, $getpath);
|
|
1312
|
+
$part = self::stringify($getval);
|
|
1313
|
+
} else if ($injdef && str_starts_with($part, '$REF:')) {
|
|
1314
|
+
// $REF:refpath$ -> get spec value, use as path part (string)
|
|
1315
|
+
$refpath = substr($part, 5, -1);
|
|
1316
|
+
$part = self::stringify(self::getpath(self::getprop($store, '$SPEC'), self::slice($part, 5, -1)));
|
|
1317
|
+
} else if ($injdef && str_starts_with($part, '$META:')) {
|
|
1318
|
+
// $META:metapath$ -> get meta value, use as path part (string)
|
|
1319
|
+
$part = self::stringify(self::getpath(self::getprop($injdef, 'meta'), substr($part, 6, -1)));
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// $$ escapes $
|
|
1323
|
+
$part = str_replace('$$', '$', $part);
|
|
1324
|
+
|
|
1325
|
+
if ($part === '') {
|
|
1326
|
+
$ascends = 0;
|
|
1327
|
+
while ($pI + 1 < count($parts) && $parts[$pI + 1] === '') {
|
|
1328
|
+
$ascends++;
|
|
1329
|
+
$pI++;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if ($injdef && $ascends > 0) {
|
|
1333
|
+
if ($pI === count($parts) - 1) {
|
|
1334
|
+
$ascends--;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if ($ascends === 0) {
|
|
1338
|
+
$val = $dparent;
|
|
1339
|
+
} else {
|
|
1340
|
+
$fullpath = self::flatten([self::slice($dpath, 0 - $ascends), array_slice($parts, $pI + 1)]);
|
|
1341
|
+
|
|
1342
|
+
if (is_array($dpath) && $ascends <= count($dpath)) {
|
|
1343
|
+
$val = self::getpath($store, $fullpath);
|
|
1344
|
+
} else {
|
|
1345
|
+
$val = self::UNDEF;
|
|
1346
|
+
}
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
} else {
|
|
1350
|
+
// Special case for single dot: use dparent if available
|
|
1351
|
+
if ($dparent !== null && $dparent !== self::UNDEF) {
|
|
1352
|
+
$val = $dparent;
|
|
1353
|
+
} else {
|
|
1354
|
+
$val = $src;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
} else {
|
|
1358
|
+
$val = self::getprop($val, $part);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Inj may provide a custom handler to modify found value
|
|
1365
|
+
$handler = self::getprop($injdef, 'handler');
|
|
1366
|
+
if ($injdef !== null && self::isfunc($handler)) {
|
|
1367
|
+
$ref = self::pathify($path);
|
|
1368
|
+
$val = call_user_func($handler, $injdef, $val, $ref, $store);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
return $val;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
public static function inject(
|
|
1376
|
+
mixed $val,
|
|
1377
|
+
mixed $store,
|
|
1378
|
+
mixed $injdef = null
|
|
1379
|
+
): mixed {
|
|
1380
|
+
$valtype = gettype($val);
|
|
1381
|
+
|
|
1382
|
+
/** @var Injection $inj */
|
|
1383
|
+
$inj = $injdef;
|
|
1384
|
+
|
|
1385
|
+
// Create state if at root of injection. The input value is placed
|
|
1386
|
+
// inside a virtual parent holder to simplify edge cases.
|
|
1387
|
+
if (self::UNDEF === $injdef || null === $injdef || !($injdef instanceof Injection)) {
|
|
1388
|
+
$inj = new Injection($val, (object) [self::S_DTOP => $val]);
|
|
1389
|
+
$inj->dparent = $store;
|
|
1390
|
+
$inj->errs = self::getprop($store, self::S_DERRS, []);
|
|
1391
|
+
if (!isset($inj->meta->__d)) {
|
|
1392
|
+
$inj->meta->__d = 0;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
if (self::UNDEF !== $injdef && null !== $injdef) {
|
|
1396
|
+
$inj->modify = (is_object($injdef) && property_exists($injdef, 'modify') && null !== $injdef->modify) ? $injdef->modify : $inj->modify;
|
|
1397
|
+
$inj->extra = (is_object($injdef) && property_exists($injdef, 'extra') && null !== $injdef->extra) ? $injdef->extra : ($inj->extra ?? null);
|
|
1398
|
+
$inj->meta = (is_object($injdef) && property_exists($injdef, 'meta') && null !== $injdef->meta) ? $injdef->meta : $inj->meta;
|
|
1399
|
+
$inj->handler = (is_object($injdef) && property_exists($injdef, 'handler') && null !== $injdef->handler) ? $injdef->handler : $inj->handler;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
$inj->descend();
|
|
1404
|
+
|
|
1405
|
+
// Descend into node.
|
|
1406
|
+
if (self::isnode($val)) {
|
|
1407
|
+
$nodekeys = self::keysof($val);
|
|
1408
|
+
|
|
1409
|
+
if (self::ismap($val)) {
|
|
1410
|
+
$nonDollar = [];
|
|
1411
|
+
$dollar = [];
|
|
1412
|
+
foreach ($nodekeys as $nk) {
|
|
1413
|
+
if (str_contains((string) $nk, self::S_DS)) {
|
|
1414
|
+
$dollar[] = $nk;
|
|
1415
|
+
} else {
|
|
1416
|
+
$nonDollar[] = $nk;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
$nodekeys = array_merge($nonDollar, $dollar);
|
|
1420
|
+
} else {
|
|
1421
|
+
$nodekeys = self::keysof($val);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
for ($nkI = 0; $nkI < count($nodekeys); $nkI++) {
|
|
1425
|
+
$childinj = $inj->child($nkI, $nodekeys);
|
|
1426
|
+
$nodekey = $childinj->key;
|
|
1427
|
+
$childinj->mode = self::M_KEYPRE;
|
|
1428
|
+
|
|
1429
|
+
// Perform the key:pre mode injection on the child key.
|
|
1430
|
+
$prekey = self::_injectstr($nodekey, $store, $childinj);
|
|
1431
|
+
|
|
1432
|
+
// The injection may modify child processing.
|
|
1433
|
+
$nkI = $childinj->keyI;
|
|
1434
|
+
$nodekeys = $childinj->keys;
|
|
1435
|
+
|
|
1436
|
+
// Prevent further processing by returning an undefined prekey
|
|
1437
|
+
if (self::UNDEF !== $prekey) {
|
|
1438
|
+
$childinj->val = self::getprop($val, $prekey);
|
|
1439
|
+
$childinj->mode = self::M_VAL;
|
|
1440
|
+
|
|
1441
|
+
// Perform the val mode injection on the child value.
|
|
1442
|
+
// NOTE: return value is not used.
|
|
1443
|
+
self::inject($childinj->val, $store, $childinj);
|
|
1444
|
+
|
|
1445
|
+
// The injection may modify child processing.
|
|
1446
|
+
$nkI = $childinj->keyI;
|
|
1447
|
+
$nodekeys = $childinj->keys;
|
|
1448
|
+
|
|
1449
|
+
// Perform the key:post mode injection on the child key.
|
|
1450
|
+
$childinj->mode = self::M_KEYPOST;
|
|
1451
|
+
self::_injectstr($nodekey, $store, $childinj);
|
|
1452
|
+
|
|
1453
|
+
// The injection may modify child processing.
|
|
1454
|
+
$nkI = $childinj->keyI;
|
|
1455
|
+
$nodekeys = $childinj->keys;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// PHP: arrays are value types; propagate child mutations back to val & parent.
|
|
1459
|
+
// Skip sync if a transform modified an ancestor (checked via prior chain).
|
|
1460
|
+
if (is_array($val) && is_array($childinj->parent)) {
|
|
1461
|
+
// Check that the grandparent (inj->parent) still references our list.
|
|
1462
|
+
// If a transform like $REF replaced/deleted it, the stored value will differ.
|
|
1463
|
+
$storedVal = self::getprop($inj->parent, $inj->key);
|
|
1464
|
+
if (is_array($storedVal)) {
|
|
1465
|
+
$val = $childinj->parent;
|
|
1466
|
+
$inj->val = $val;
|
|
1467
|
+
self::setprop($inj->parent, $inj->key, $val);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
// Inject paths into string scalars.
|
|
1473
|
+
else if ($valtype === 'string') {
|
|
1474
|
+
$inj->mode = self::M_VAL;
|
|
1475
|
+
$val = self::_injectstr($val, $store, $inj);
|
|
1476
|
+
if (self::SKIP !== $val) {
|
|
1477
|
+
$inj->setval($val);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// Custom modification.
|
|
1482
|
+
if ($inj->modify && self::SKIP !== $val) {
|
|
1483
|
+
$mkey = $inj->key;
|
|
1484
|
+
$mparent = $inj->parent;
|
|
1485
|
+
$mval = self::getprop($mparent, $mkey);
|
|
1486
|
+
|
|
1487
|
+
call_user_func(
|
|
1488
|
+
$inj->modify,
|
|
1489
|
+
$mval,
|
|
1490
|
+
$mkey,
|
|
1491
|
+
$mparent,
|
|
1492
|
+
$inj,
|
|
1493
|
+
$store
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
$inj->val = $val;
|
|
1498
|
+
|
|
1499
|
+
// Original val reference may no longer be correct.
|
|
1500
|
+
// This return value is only used as the top level result.
|
|
1501
|
+
return self::getprop($inj->parent, self::S_DTOP);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
|
|
1505
|
+
private static function _injectstr(
|
|
1506
|
+
string $val,
|
|
1507
|
+
mixed $store,
|
|
1508
|
+
?object $inj = null
|
|
1509
|
+
): mixed {
|
|
1510
|
+
// Can't inject into non-strings
|
|
1511
|
+
if ($val === self::S_MT) {
|
|
1512
|
+
return self::S_MT;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
$out = $val;
|
|
1516
|
+
|
|
1517
|
+
// Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`", "``"
|
|
1518
|
+
$m = preg_match('/^`(\$[A-Z]+|[^`]*)[0-9]*`$/', $val, $matches);
|
|
1519
|
+
|
|
1520
|
+
// Full string of the val is an injection.
|
|
1521
|
+
if ($m) {
|
|
1522
|
+
if ($inj !== null) {
|
|
1523
|
+
$inj->full = true;
|
|
1524
|
+
}
|
|
1525
|
+
$pathref = $matches[1];
|
|
1526
|
+
|
|
1527
|
+
// Special escapes inside injection.
|
|
1528
|
+
if (strlen($pathref) > 3) {
|
|
1529
|
+
$pathref = str_replace('\\.', '.', $pathref);
|
|
1530
|
+
$pathref = str_replace('$BT', self::S_BT, $pathref);
|
|
1531
|
+
$pathref = str_replace('$DS', self::S_DS, $pathref);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Get the extracted path reference.
|
|
1535
|
+
$out = self::getpath($store, $pathref, $inj);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
// Check for injections within the string.
|
|
1539
|
+
$out = preg_replace_callback('/`([^`]+)`/', function($matches) use ($store, $inj) {
|
|
1540
|
+
$ref = $matches[1];
|
|
1541
|
+
|
|
1542
|
+
if (strlen($ref) > 3) {
|
|
1543
|
+
$ref = str_replace('\\.', '.', $ref);
|
|
1544
|
+
$ref = str_replace('$BT', self::S_BT, $ref);
|
|
1545
|
+
$ref = str_replace('$DS', self::S_DS, $ref);
|
|
1546
|
+
}
|
|
1547
|
+
if ($inj !== null) {
|
|
1548
|
+
$inj->full = false;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
$found = self::getpath($store, $ref, $inj);
|
|
1552
|
+
|
|
1553
|
+
// Ensure inject value is a string.
|
|
1554
|
+
if ($found === self::UNDEF) {
|
|
1555
|
+
return self::S_MT;
|
|
1556
|
+
}
|
|
1557
|
+
if (is_string($found)) {
|
|
1558
|
+
return $found;
|
|
1559
|
+
}
|
|
1560
|
+
return json_encode($found instanceof ListRef ? self::cloneUnwrap($found) : $found);
|
|
1561
|
+
}, $val);
|
|
1562
|
+
|
|
1563
|
+
// Also call the inj handler on the entire string, providing the
|
|
1564
|
+
// option for custom injection.
|
|
1565
|
+
if ($inj !== null && is_callable($inj->handler)) {
|
|
1566
|
+
$inj->full = true;
|
|
1567
|
+
$out = call_user_func($inj->handler, $inj, $out, $val, $store);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
return $out;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
public static function _injecthandler(
|
|
1576
|
+
object $inj,
|
|
1577
|
+
mixed $val,
|
|
1578
|
+
string $ref,
|
|
1579
|
+
mixed $store
|
|
1580
|
+
): mixed {
|
|
1581
|
+
$out = $val;
|
|
1582
|
+
|
|
1583
|
+
// Check if val is a function (command transforms)
|
|
1584
|
+
$iscmd = self::isfunc($val) && (self::UNDEF === $ref || str_starts_with($ref, self::S_DS));
|
|
1585
|
+
|
|
1586
|
+
// Only call val function if it is a special command ($NAME format).
|
|
1587
|
+
if ($iscmd) {
|
|
1588
|
+
$out = call_user_func($val, $inj, $val, $ref, $store);
|
|
1589
|
+
}
|
|
1590
|
+
// Update parent with value. Ensures references remain in node tree.
|
|
1591
|
+
elseif (self::M_VAL === $inj->mode && $inj->full) {
|
|
1592
|
+
$inj->setval($val);
|
|
1593
|
+
}
|
|
1594
|
+
return $out;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
private static function _injecthandler_getpath(
|
|
1598
|
+
object $state,
|
|
1599
|
+
mixed $val,
|
|
1600
|
+
string $ref,
|
|
1601
|
+
mixed $store
|
|
1602
|
+
): mixed {
|
|
1603
|
+
return self::_injecthandler($state, $val, $ref, $store);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* @internal
|
|
1608
|
+
* Delete a key from a map or list.
|
|
1609
|
+
*/
|
|
1610
|
+
public static function transform_DELETE(
|
|
1611
|
+
object $state,
|
|
1612
|
+
mixed $val,
|
|
1613
|
+
mixed $ref,
|
|
1614
|
+
mixed $store
|
|
1615
|
+
): mixed {
|
|
1616
|
+
// _setparentprop(state, UNDEF)
|
|
1617
|
+
$state->setval(self::UNDEF);
|
|
1618
|
+
return self::UNDEF;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* @internal
|
|
1623
|
+
* Copy value from source data.
|
|
1624
|
+
*/
|
|
1625
|
+
public static function transform_COPY(
|
|
1626
|
+
object $state,
|
|
1627
|
+
mixed $val,
|
|
1628
|
+
mixed $ref,
|
|
1629
|
+
mixed $store
|
|
1630
|
+
): mixed {
|
|
1631
|
+
if (self::M_VAL !== $state->mode) {
|
|
1632
|
+
return self::UNDEF;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
$out = self::getprop($state->dparent, $state->key);
|
|
1636
|
+
$state->setval($out);
|
|
1637
|
+
|
|
1638
|
+
return $out;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* @internal
|
|
1643
|
+
* As a value, inject the key of the parent node.
|
|
1644
|
+
* As a key, defines the name of the key property in the source object.
|
|
1645
|
+
*/
|
|
1646
|
+
public static function transform_KEY(
|
|
1647
|
+
object $state,
|
|
1648
|
+
mixed $val,
|
|
1649
|
+
mixed $ref,
|
|
1650
|
+
mixed $store
|
|
1651
|
+
): mixed {
|
|
1652
|
+
// only in "val" mode do anything
|
|
1653
|
+
if (self::M_VAL !== $state->mode) {
|
|
1654
|
+
return self::UNDEF;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// if parent has a "$KEY" override, use that
|
|
1658
|
+
$keyspec = self::getprop($state->parent, self::S_DKEY);
|
|
1659
|
+
if ($keyspec !== self::UNDEF) {
|
|
1660
|
+
// remove the marker
|
|
1661
|
+
self::setprop($state->parent, self::S_DKEY, self::UNDEF);
|
|
1662
|
+
return self::getprop($state->dparent, $keyspec);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// otherwise pull from $ANNO.KEY or fallback to the path index
|
|
1666
|
+
$meta = self::getprop($state->parent, self::S_BANNO);
|
|
1667
|
+
$idx = count($state->path) - 2;
|
|
1668
|
+
return self::getprop(
|
|
1669
|
+
$meta,
|
|
1670
|
+
self::S_KEY,
|
|
1671
|
+
self::getprop($state->path, $idx)
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/**
|
|
1676
|
+
* @internal
|
|
1677
|
+
* Store meta data about a node. Does nothing itself, just used by other transforms.
|
|
1678
|
+
*/
|
|
1679
|
+
public static function transform_META(
|
|
1680
|
+
object $state,
|
|
1681
|
+
mixed $val,
|
|
1682
|
+
mixed $ref,
|
|
1683
|
+
mixed $store
|
|
1684
|
+
): mixed {
|
|
1685
|
+
// remove the $META marker
|
|
1686
|
+
self::setprop($state->parent, self::S_DMETA, self::UNDEF);
|
|
1687
|
+
return self::UNDEF;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* @internal
|
|
1692
|
+
* Store annotation data about a node. Does nothing itself, just used by other transforms.
|
|
1693
|
+
*/
|
|
1694
|
+
public static function transform_ANNO(
|
|
1695
|
+
object $state,
|
|
1696
|
+
mixed $val,
|
|
1697
|
+
mixed $ref,
|
|
1698
|
+
mixed $store
|
|
1699
|
+
): mixed {
|
|
1700
|
+
// remove the $ANNO marker
|
|
1701
|
+
self::setprop($state->parent, self::S_BANNO, self::UNDEF);
|
|
1702
|
+
return self::UNDEF;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* @internal
|
|
1707
|
+
* Merge a list of objects into the current object.
|
|
1708
|
+
*/
|
|
1709
|
+
public static function transform_MERGE(
|
|
1710
|
+
object $state,
|
|
1711
|
+
mixed $val,
|
|
1712
|
+
mixed $ref,
|
|
1713
|
+
mixed $store
|
|
1714
|
+
): mixed {
|
|
1715
|
+
$mode = $state->mode;
|
|
1716
|
+
$key = $state->key;
|
|
1717
|
+
$parent = $state->parent;
|
|
1718
|
+
|
|
1719
|
+
// Ensures $MERGE is removed from parent list (val mode).
|
|
1720
|
+
$out = self::UNDEF;
|
|
1721
|
+
|
|
1722
|
+
if (self::M_KEYPRE === $mode) {
|
|
1723
|
+
$out = $key;
|
|
1724
|
+
}
|
|
1725
|
+
// Operate after child values have been transformed.
|
|
1726
|
+
elseif (self::M_KEYPOST === $mode) {
|
|
1727
|
+
$out = $key;
|
|
1728
|
+
|
|
1729
|
+
$args = self::getprop($parent, $key);
|
|
1730
|
+
$args = self::islist($args) ? (($args instanceof ListRef) ? $args->list : $args) : [$args];
|
|
1731
|
+
|
|
1732
|
+
// Remove the $MERGE command from a parent map.
|
|
1733
|
+
$state->setval(self::UNDEF);
|
|
1734
|
+
|
|
1735
|
+
// Literals in the parent have precedence, but we still merge onto
|
|
1736
|
+
// the parent object, so that node tree references are not changed.
|
|
1737
|
+
$mergelist = self::flatten([[$parent], $args, [clone $parent]]);
|
|
1738
|
+
|
|
1739
|
+
self::merge($mergelist);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
return $out;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
|
|
1746
|
+
public static function transform_EACH(
|
|
1747
|
+
object $state,
|
|
1748
|
+
mixed $_val,
|
|
1749
|
+
string $_ref,
|
|
1750
|
+
mixed $store
|
|
1751
|
+
): mixed {
|
|
1752
|
+
// Remove remaining keys to avoid spurious processing.
|
|
1753
|
+
$state->keys = array_slice($state->keys, 0, 1);
|
|
1754
|
+
|
|
1755
|
+
if (self::M_VAL !== $state->mode) {
|
|
1756
|
+
return self::UNDEF;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Get arguments: ['`$EACH`', 'source-path', child-template]
|
|
1760
|
+
$srcpath = self::getprop($state->parent, 1);
|
|
1761
|
+
$child = self::clone(self::getprop($state->parent, 2));
|
|
1762
|
+
|
|
1763
|
+
// Source data.
|
|
1764
|
+
$srcstore = self::getprop($store, $state->base, $store);
|
|
1765
|
+
$src = self::getpath($srcstore, $srcpath, $state);
|
|
1766
|
+
|
|
1767
|
+
// Create parallel data structures: source entries :: child templates
|
|
1768
|
+
$tcur = [];
|
|
1769
|
+
$tval = [];
|
|
1770
|
+
|
|
1771
|
+
$tkey = self::getelem($state->path, -2);
|
|
1772
|
+
$target = self::getelem($state->nodes, -2) ?? self::getelem($state->nodes, -1);
|
|
1773
|
+
|
|
1774
|
+
// Create clones of the child template for each value of the current source.
|
|
1775
|
+
if (self::islist($src)) {
|
|
1776
|
+
$srcArr = ($src instanceof ListRef) ? $src->list : (array) $src;
|
|
1777
|
+
$tval = array_map(function($_) use ($child) {
|
|
1778
|
+
return self::clone($child);
|
|
1779
|
+
}, $srcArr);
|
|
1780
|
+
} elseif (self::ismap($src)) {
|
|
1781
|
+
$tval = [];
|
|
1782
|
+
foreach (self::items($src) as $item) {
|
|
1783
|
+
$template = self::merge([
|
|
1784
|
+
self::clone($child),
|
|
1785
|
+
(object) [self::S_BANNO => (object) [self::S_KEY => $item[0]]]
|
|
1786
|
+
], 1);
|
|
1787
|
+
$tval[] = $template;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
$rval = [];
|
|
1792
|
+
|
|
1793
|
+
if (0 < self::size($tval)) {
|
|
1794
|
+
$tcur = (null == $src) ? self::UNDEF : ($src instanceof ListRef ? $src->list : array_values((array) $src));
|
|
1795
|
+
|
|
1796
|
+
$ckey = self::getelem($state->path, -2);
|
|
1797
|
+
|
|
1798
|
+
$tpath = self::slice($state->path, -1);
|
|
1799
|
+
$dpath = self::flatten([self::S_DTOP, explode(self::S_DT, $srcpath), '$:' . $ckey]);
|
|
1800
|
+
|
|
1801
|
+
// Parent structure.
|
|
1802
|
+
$tcur = (object) [$ckey => $tcur];
|
|
1803
|
+
|
|
1804
|
+
if (1 < self::size($tpath)) {
|
|
1805
|
+
$pkey = self::getelem($state->path, -3, self::S_DTOP);
|
|
1806
|
+
$tcur = (object) [$pkey => $tcur];
|
|
1807
|
+
$dpath[] = '$:' . $pkey;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
$tinj = $state->child(0, [$ckey]);
|
|
1811
|
+
$tinj->path = $tpath;
|
|
1812
|
+
$tinj->nodes = self::slice($state->nodes, -1);
|
|
1813
|
+
|
|
1814
|
+
$tinj->parent = self::getelem($tinj->nodes, -1);
|
|
1815
|
+
self::setprop($tinj->parent, $ckey, $tval);
|
|
1816
|
+
|
|
1817
|
+
$tinj->val = $tval;
|
|
1818
|
+
$tinj->dpath = $dpath;
|
|
1819
|
+
$tinj->dparent = $tcur;
|
|
1820
|
+
|
|
1821
|
+
self::inject($tval, $store, $tinj);
|
|
1822
|
+
$rval = $tinj->val;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// Update ancestors.
|
|
1826
|
+
self::setprop($target, $tkey, $rval);
|
|
1827
|
+
|
|
1828
|
+
// Prevent callee from damaging first list entry (since we are in `val` mode).
|
|
1829
|
+
return $rval[0] ?? self::UNDEF;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
|
|
1835
|
+
/** @internal */
|
|
1836
|
+
public static function transform_PACK(
|
|
1837
|
+
object $state,
|
|
1838
|
+
mixed $_val,
|
|
1839
|
+
string $_ref,
|
|
1840
|
+
mixed $store
|
|
1841
|
+
): mixed {
|
|
1842
|
+
$mode = $state->mode;
|
|
1843
|
+
$key = $state->key;
|
|
1844
|
+
$path = $state->path;
|
|
1845
|
+
$parent = $state->parent;
|
|
1846
|
+
$nodes = $state->nodes;
|
|
1847
|
+
|
|
1848
|
+
// Only run in key:pre mode.
|
|
1849
|
+
if (self::M_KEYPRE !== $mode) {
|
|
1850
|
+
return self::UNDEF;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// Get arguments.
|
|
1854
|
+
$args = self::getprop($parent, $key);
|
|
1855
|
+
if (!is_array($args) || count($args) < 2) {
|
|
1856
|
+
return self::UNDEF;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
$srcpath = $args[0];
|
|
1860
|
+
$origchildspec = self::clone($args[1]);
|
|
1861
|
+
|
|
1862
|
+
// Find key and target node.
|
|
1863
|
+
$tkey = self::getelem($path, -2);
|
|
1864
|
+
$pathsize = self::size($path);
|
|
1865
|
+
$target = self::getelem($nodes, $pathsize - 2) ?? self::getelem($nodes, $pathsize - 1);
|
|
1866
|
+
|
|
1867
|
+
// Source data
|
|
1868
|
+
$srcstore = self::getprop($store, $state->base, $store);
|
|
1869
|
+
$src = self::getpath($srcstore, $srcpath, $state);
|
|
1870
|
+
|
|
1871
|
+
// Prepare source as a list.
|
|
1872
|
+
if (!self::islist($src)) {
|
|
1873
|
+
if (self::ismap($src)) {
|
|
1874
|
+
$newSrc = [];
|
|
1875
|
+
foreach (self::items($src) as $item) {
|
|
1876
|
+
self::setprop($item[1], self::S_BANNO, (object) [self::S_KEY => $item[0]]);
|
|
1877
|
+
$newSrc[] = $item[1];
|
|
1878
|
+
}
|
|
1879
|
+
$src = $newSrc;
|
|
1880
|
+
} else {
|
|
1881
|
+
return self::UNDEF;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
if (null == $src) {
|
|
1886
|
+
return self::UNDEF;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// Get keypath.
|
|
1890
|
+
$keypath = self::getprop($origchildspec, self::S_BKEY);
|
|
1891
|
+
$childspec = self::delprop($origchildspec, self::S_BKEY);
|
|
1892
|
+
|
|
1893
|
+
$child = $childspec;
|
|
1894
|
+
|
|
1895
|
+
// Build parallel target object.
|
|
1896
|
+
$tval = new \stdClass();
|
|
1897
|
+
|
|
1898
|
+
foreach (self::items($src) as $item) {
|
|
1899
|
+
$srckey = $item[0];
|
|
1900
|
+
$srcnode = $item[1];
|
|
1901
|
+
|
|
1902
|
+
$nkey = $srckey;
|
|
1903
|
+
if (self::UNDEF !== $keypath) {
|
|
1904
|
+
if (is_string($keypath) && str_starts_with($keypath, '`')) {
|
|
1905
|
+
$nkey = self::inject($keypath, self::merge([new \stdClass(), $store, (object) ['$TOP' => $srcnode]], 1));
|
|
1906
|
+
} else {
|
|
1907
|
+
$nkey = self::getpath($srcnode, $keypath, $state);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
$tchild = self::clone($child);
|
|
1912
|
+
self::setprop($tval, $nkey, $tchild);
|
|
1913
|
+
|
|
1914
|
+
$anno = self::getprop($srcnode, self::S_BANNO);
|
|
1915
|
+
if (self::UNDEF === $anno) {
|
|
1916
|
+
self::delprop($tchild, self::S_BANNO);
|
|
1917
|
+
} else {
|
|
1918
|
+
self::setprop($tchild, self::S_BANNO, $anno);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
$rval = new \stdClass();
|
|
1923
|
+
|
|
1924
|
+
if (!self::isempty($tval)) {
|
|
1925
|
+
// Build parallel source object.
|
|
1926
|
+
$tsrc = new \stdClass();
|
|
1927
|
+
foreach ($src as $i => $n) {
|
|
1928
|
+
$kn = null;
|
|
1929
|
+
if (self::UNDEF === $keypath) {
|
|
1930
|
+
$kn = $i;
|
|
1931
|
+
} elseif (is_string($keypath) && str_starts_with($keypath, '`')) {
|
|
1932
|
+
$kn = self::inject($keypath, self::merge([new \stdClass(), $store, (object) ['$TOP' => $n]], 1));
|
|
1933
|
+
} else {
|
|
1934
|
+
$kn = self::getpath($n, $keypath, $state);
|
|
1935
|
+
}
|
|
1936
|
+
self::setprop($tsrc, $kn, $n);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
$tpath = self::slice($state->path, -1);
|
|
1940
|
+
|
|
1941
|
+
$ckey = self::getelem($state->path, -2);
|
|
1942
|
+
$dpath = self::flatten([self::S_DTOP, explode(self::S_DT, $srcpath), '$:' . $ckey]);
|
|
1943
|
+
|
|
1944
|
+
$tcur = (object) [$ckey => $tsrc];
|
|
1945
|
+
|
|
1946
|
+
if (1 < self::size($tpath)) {
|
|
1947
|
+
$pkey = self::getelem($state->path, -3, self::S_DTOP);
|
|
1948
|
+
$tcur = (object) [$pkey => $tcur];
|
|
1949
|
+
$dpath[] = '$:' . $pkey;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
$tinj = $state->child(0, [$ckey]);
|
|
1953
|
+
$tinj->path = $tpath;
|
|
1954
|
+
$tinj->nodes = self::slice($state->nodes, -1);
|
|
1955
|
+
|
|
1956
|
+
$tinj->parent = self::getelem($tinj->nodes, -1);
|
|
1957
|
+
$tinj->val = $tval;
|
|
1958
|
+
|
|
1959
|
+
$tinj->dpath = $dpath;
|
|
1960
|
+
$tinj->dparent = $tcur;
|
|
1961
|
+
|
|
1962
|
+
self::inject($tval, $store, $tinj);
|
|
1963
|
+
$rval = $tinj->val;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// Update ancestors.
|
|
1967
|
+
self::setprop($target, $tkey, $rval);
|
|
1968
|
+
|
|
1969
|
+
// Drop transform key.
|
|
1970
|
+
return self::UNDEF;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
|
|
1974
|
+
/** @internal */
|
|
1975
|
+
public static function transform_REF(object $state, mixed $_val, string $_ref, mixed $store): mixed
|
|
1976
|
+
{
|
|
1977
|
+
$nodes = $state->nodes;
|
|
1978
|
+
|
|
1979
|
+
if (self::M_VAL !== $state->mode) {
|
|
1980
|
+
return self::UNDEF;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Get arguments: ['`$REF`', 'ref-path'].
|
|
1984
|
+
$refpath = self::getprop($state->parent, 1);
|
|
1985
|
+
$state->keyI = self::size($state->keys);
|
|
1986
|
+
|
|
1987
|
+
// Spec reference.
|
|
1988
|
+
$specFn = self::getprop($store, '$SPEC');
|
|
1989
|
+
$spec = is_callable($specFn) ? $specFn() : self::UNDEF;
|
|
1990
|
+
|
|
1991
|
+
$dpath = self::slice($state->path, 1);
|
|
1992
|
+
$ref = self::getpath($spec, $refpath, (object) [
|
|
1993
|
+
'dpath' => $dpath,
|
|
1994
|
+
'dparent' => self::getpath($spec, $dpath),
|
|
1995
|
+
]);
|
|
1996
|
+
|
|
1997
|
+
$hasSubRef = false;
|
|
1998
|
+
if (self::isnode($ref)) {
|
|
1999
|
+
self::walk($ref, function ($_k, $v) use (&$hasSubRef) {
|
|
2000
|
+
if ($v === '`$REF`') {
|
|
2001
|
+
$hasSubRef = true;
|
|
2002
|
+
}
|
|
2003
|
+
return $v;
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
$tref = self::cloneWrap($ref);
|
|
2008
|
+
|
|
2009
|
+
$cpath = self::slice($state->path, -3);
|
|
2010
|
+
$tpath = self::slice($state->path, -1);
|
|
2011
|
+
$tcur = self::getpath($store, $cpath);
|
|
2012
|
+
$tval = self::getpath($store, $tpath);
|
|
2013
|
+
$rval = self::UNDEF;
|
|
2014
|
+
|
|
2015
|
+
if (!$hasSubRef || self::UNDEF !== $tval) {
|
|
2016
|
+
$tinj = $state->child(0, [self::getelem($tpath, -1)]);
|
|
2017
|
+
|
|
2018
|
+
$tinj->path = $tpath;
|
|
2019
|
+
$tinj->nodes = self::slice($state->nodes, -1);
|
|
2020
|
+
$tinj->parent = self::getelem($nodes, -2);
|
|
2021
|
+
$tinj->val = $tref;
|
|
2022
|
+
|
|
2023
|
+
$tinj->dpath = self::flatten([$cpath]);
|
|
2024
|
+
$tinj->dparent = $tcur;
|
|
2025
|
+
|
|
2026
|
+
$injResult = self::inject($tref, $store, $tinj);
|
|
2027
|
+
|
|
2028
|
+
// If inject returned SKIP, use tref (mutated in place) not tinj->val (which may be SKIP)
|
|
2029
|
+
if ($injResult === self::SKIP || $tinj->val === self::SKIP) {
|
|
2030
|
+
$rval = is_object($tref) ? $tref : self::UNDEF;
|
|
2031
|
+
} else {
|
|
2032
|
+
$rval = $tinj->val;
|
|
2033
|
+
}
|
|
2034
|
+
} else {
|
|
2035
|
+
$rval = self::UNDEF;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
$grandparent = $state->setval($rval, 2);
|
|
2039
|
+
|
|
2040
|
+
// PHP: arrays in nodes are copies, so ancestor setval on arrays doesn't propagate.
|
|
2041
|
+
// Sync the prior injection's parent if it's an array.
|
|
2042
|
+
if ($state->prior && is_array($state->prior->parent)) {
|
|
2043
|
+
$akey = self::getelem($state->path, -2);
|
|
2044
|
+
if (self::UNDEF === $rval) {
|
|
2045
|
+
$state->prior->parent = self::delprop($state->prior->parent, $akey);
|
|
2046
|
+
} else {
|
|
2047
|
+
self::setprop($state->prior->parent, $akey, $rval);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if (self::islist($grandparent) && $state->prior) {
|
|
2052
|
+
$state->prior->keyI--;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
return $_val;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
private static array $FORMATTER = [];
|
|
2060
|
+
|
|
2061
|
+
private static function _getFormatters(): array
|
|
2062
|
+
{
|
|
2063
|
+
if (empty(self::$FORMATTER)) {
|
|
2064
|
+
self::$FORMATTER = [
|
|
2065
|
+
'identity' => fn($_k, $v) => $v,
|
|
2066
|
+
'upper' => fn($_k, $v) => self::isnode($v) ? $v : strtoupper('' . $v),
|
|
2067
|
+
'lower' => fn($_k, $v) => self::isnode($v) ? $v : strtolower('' . $v),
|
|
2068
|
+
'string' => fn($_k, $v) => self::isnode($v) ? $v : ('' . $v),
|
|
2069
|
+
'number' => function ($_k, $v) {
|
|
2070
|
+
if (self::isnode($v)) {
|
|
2071
|
+
return $v;
|
|
2072
|
+
}
|
|
2073
|
+
$n = is_numeric($v) ? $v + 0 : 0;
|
|
2074
|
+
return $n;
|
|
2075
|
+
},
|
|
2076
|
+
'integer' => function ($_k, $v) {
|
|
2077
|
+
if (self::isnode($v)) {
|
|
2078
|
+
return $v;
|
|
2079
|
+
}
|
|
2080
|
+
$n = is_numeric($v) ? (int)$v : 0;
|
|
2081
|
+
return $n;
|
|
2082
|
+
},
|
|
2083
|
+
'concat' => function ($k, $v) {
|
|
2084
|
+
if (null === $k && self::islist($v)) {
|
|
2085
|
+
$parts = self::items($v, fn($n) => self::isnode($n[1]) ? '' : ('' . $n[1]));
|
|
2086
|
+
return self::join($parts, '');
|
|
2087
|
+
}
|
|
2088
|
+
return $v;
|
|
2089
|
+
},
|
|
2090
|
+
];
|
|
2091
|
+
}
|
|
2092
|
+
return self::$FORMATTER;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
|
|
2096
|
+
/** @internal */
|
|
2097
|
+
public static function transform_FORMAT(object $inj, mixed $_val, string $_ref, mixed $store): mixed
|
|
2098
|
+
{
|
|
2099
|
+
// Remove remaining keys to avoid spurious processing.
|
|
2100
|
+
self::slice($inj->keys, 0, 1, true);
|
|
2101
|
+
|
|
2102
|
+
if (self::M_VAL !== $inj->mode) {
|
|
2103
|
+
return self::UNDEF;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
// Get arguments: ['`$FORMAT`', 'name', child].
|
|
2107
|
+
$name = self::getprop($inj->parent, 1);
|
|
2108
|
+
$child = self::getprop($inj->parent, 2);
|
|
2109
|
+
|
|
2110
|
+
// Source data.
|
|
2111
|
+
$tkey = self::getelem($inj->path, -2);
|
|
2112
|
+
$target = self::getelem($inj->nodes, -2, fn() => self::getelem($inj->nodes, -1));
|
|
2113
|
+
|
|
2114
|
+
$cinj = self::injectChild($child, $store, $inj);
|
|
2115
|
+
$resolved = $cinj->val;
|
|
2116
|
+
|
|
2117
|
+
$formatters = self::_getFormatters();
|
|
2118
|
+
$formatter = (0 < (self::T_function & self::typify($name))) ? $name : ($formatters[$name] ?? self::UNDEF);
|
|
2119
|
+
|
|
2120
|
+
if (self::UNDEF === $formatter) {
|
|
2121
|
+
$inj->errs[] = '$FORMAT: unknown format: ' . $name . '.';
|
|
2122
|
+
return self::UNDEF;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
$out = self::walk($resolved, $formatter);
|
|
2126
|
+
|
|
2127
|
+
self::setprop($target, $tkey, $out);
|
|
2128
|
+
|
|
2129
|
+
return $out;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
|
|
2133
|
+
/** @internal */
|
|
2134
|
+
public static function transform_APPLY(object $inj, mixed $_val, string $_ref, mixed $store): mixed
|
|
2135
|
+
{
|
|
2136
|
+
$ijname = 'APPLY';
|
|
2137
|
+
|
|
2138
|
+
if (!self::checkPlacement(self::M_VAL, $ijname, self::T_list, $inj)) {
|
|
2139
|
+
return self::UNDEF;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
$args = self::slice($inj->parent, 1);
|
|
2143
|
+
$argsList = [];
|
|
2144
|
+
if (self::islist($args)) {
|
|
2145
|
+
if ($args instanceof ListRef) {
|
|
2146
|
+
$argsList = $args->list;
|
|
2147
|
+
} else {
|
|
2148
|
+
$argsList = $args;
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
[$err, $apply, $child] = self::injectorArgs([self::T_function, self::T_any], $argsList);
|
|
2152
|
+
if (self::UNDEF !== $err) {
|
|
2153
|
+
$inj->errs[] = '$' . $ijname . ': ' . $err;
|
|
2154
|
+
return self::UNDEF;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
$tkey = self::getelem($inj->path, -2);
|
|
2158
|
+
$target = self::getelem($inj->nodes, -2, fn() => self::getelem($inj->nodes, -1));
|
|
2159
|
+
|
|
2160
|
+
$cinj = self::injectChild($child, $store, $inj);
|
|
2161
|
+
$resolved = $cinj->val;
|
|
2162
|
+
|
|
2163
|
+
$out = call_user_func($apply, $resolved, $store, $cinj);
|
|
2164
|
+
|
|
2165
|
+
self::setprop($target, $tkey, $out);
|
|
2166
|
+
|
|
2167
|
+
return $out;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
|
|
2171
|
+
/**
|
|
2172
|
+
* Transform data using a spec.
|
|
2173
|
+
*
|
|
2174
|
+
* @param mixed $data Source data (not mutated)
|
|
2175
|
+
* @param mixed $spec Transform spec (JSON-like)
|
|
2176
|
+
* @param array<mixed>|object|null $extra extra transforms or data
|
|
2177
|
+
* @param callable|null $modify optional per-value hook
|
|
2178
|
+
*/
|
|
2179
|
+
public static function transform(
|
|
2180
|
+
mixed $data,
|
|
2181
|
+
mixed $spec,
|
|
2182
|
+
mixed $injdef = null
|
|
2183
|
+
): mixed {
|
|
2184
|
+
// Support injdef object pattern or backward compat (extra data passed directly)
|
|
2185
|
+
$extra = null;
|
|
2186
|
+
$modify = null;
|
|
2187
|
+
$errs = null;
|
|
2188
|
+
if (is_object($injdef) && (
|
|
2189
|
+
property_exists($injdef, 'extra') ||
|
|
2190
|
+
property_exists($injdef, 'modify') ||
|
|
2191
|
+
property_exists($injdef, 'errs') ||
|
|
2192
|
+
property_exists($injdef, 'meta') ||
|
|
2193
|
+
property_exists($injdef, 'handler')
|
|
2194
|
+
)) {
|
|
2195
|
+
// New injdef pattern: { extra, modify, errs, meta, handler }
|
|
2196
|
+
$extra = property_exists($injdef, 'extra') ? $injdef->extra : null;
|
|
2197
|
+
$modify = property_exists($injdef, 'modify') ? $injdef->modify : null;
|
|
2198
|
+
$errs = property_exists($injdef, 'errs') ? $injdef->errs : null;
|
|
2199
|
+
} else {
|
|
2200
|
+
// Backward compat: treat 3rd arg as extra data/store directly
|
|
2201
|
+
$extra = $injdef;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// 1) clone spec, wrapping arrays in ListRef for reference stability (Go pattern)
|
|
2205
|
+
$specClone = self::cloneWrap($spec);
|
|
2206
|
+
|
|
2207
|
+
// 2) split extra into data vs transforms
|
|
2208
|
+
$extraTransforms = [];
|
|
2209
|
+
$extraData = [];
|
|
2210
|
+
|
|
2211
|
+
foreach ((array) ($extra ?? []) as $k => $v) {
|
|
2212
|
+
if (str_starts_with((string) $k, self::S_DS)) {
|
|
2213
|
+
$extraTransforms[$k] = $v;
|
|
2214
|
+
} else {
|
|
2215
|
+
$extraData[$k] = $v;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// 3) build the combined store
|
|
2220
|
+
$dataClone = self::merge([
|
|
2221
|
+
self::cloneWrap($extraData),
|
|
2222
|
+
self::cloneWrap($data),
|
|
2223
|
+
]);
|
|
2224
|
+
|
|
2225
|
+
$store = (object) array_merge(
|
|
2226
|
+
[
|
|
2227
|
+
self::S_DTOP => $dataClone,
|
|
2228
|
+
'$BT' => fn() => self::S_BT,
|
|
2229
|
+
'$DS' => fn() => self::S_DS,
|
|
2230
|
+
'$WHEN' => fn() => (new \DateTime)->format(\DateTime::ATOM),
|
|
2231
|
+
'$DELETE' => [self::class, 'transform_DELETE'],
|
|
2232
|
+
'$COPY' => [self::class, 'transform_COPY'],
|
|
2233
|
+
'$KEY' => [self::class, 'transform_KEY'],
|
|
2234
|
+
'$META' => [self::class, 'transform_META'],
|
|
2235
|
+
'$ANNO' => [self::class, 'transform_ANNO'],
|
|
2236
|
+
'$MERGE' => [self::class, 'transform_MERGE'],
|
|
2237
|
+
'$EACH' => [self::class, 'transform_EACH'],
|
|
2238
|
+
'$PACK' => [self::class, 'transform_PACK'],
|
|
2239
|
+
'$SPEC' => fn() => $spec,
|
|
2240
|
+
'$REF' => [self::class, 'transform_REF'],
|
|
2241
|
+
'$FORMAT' => [self::class, 'transform_FORMAT'],
|
|
2242
|
+
'$APPLY' => [self::class, 'transform_APPLY'],
|
|
2243
|
+
],
|
|
2244
|
+
$extraTransforms
|
|
2245
|
+
);
|
|
2246
|
+
|
|
2247
|
+
// 4) run inject to do the transform
|
|
2248
|
+
$injectOpts = new \stdClass();
|
|
2249
|
+
if ($modify !== null) {
|
|
2250
|
+
$injectOpts->modify = $modify;
|
|
2251
|
+
}
|
|
2252
|
+
if (is_object($injdef) && property_exists($injdef, 'handler') && $injdef->handler !== null) {
|
|
2253
|
+
$injectOpts->handler = $injdef->handler;
|
|
2254
|
+
}
|
|
2255
|
+
if (is_object($injdef) && property_exists($injdef, 'meta') && $injdef->meta !== null) {
|
|
2256
|
+
$injectOpts->meta = $injdef->meta;
|
|
2257
|
+
}
|
|
2258
|
+
if (is_object($injdef) && property_exists($injdef, 'errs') && $injdef->errs !== null) {
|
|
2259
|
+
$injectOpts->errs = $injdef->errs;
|
|
2260
|
+
}
|
|
2261
|
+
$result = self::inject($specClone, $store, $injectOpts);
|
|
2262
|
+
|
|
2263
|
+
// When a child transform (e.g. $REF) deletes the key, inject returns SKIP; return mutated spec
|
|
2264
|
+
if ($result === self::SKIP) {
|
|
2265
|
+
return self::cloneUnwrap($specClone);
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
return self::cloneUnwrap($result);
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
/**
|
|
2272
|
+
* Remove unresolved $REF list entries from a list spec.
|
|
2273
|
+
* This handles PHP's value-type arrays where in-place mutation via references doesn't propagate.
|
|
2274
|
+
*/
|
|
2275
|
+
private static function _cleanRefEntries(array $list): array {
|
|
2276
|
+
$cleaned = [];
|
|
2277
|
+
foreach ($list as $item) {
|
|
2278
|
+
if (self::islist($item) && count($item) >= 1 && self::getprop($item, 0) === '`$REF`') {
|
|
2279
|
+
// This is an unresolved $REF entry - remove it
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if (self::islist($item)) {
|
|
2283
|
+
$item = self::_cleanRefEntries($item);
|
|
2284
|
+
}
|
|
2285
|
+
$cleaned[] = $item;
|
|
2286
|
+
}
|
|
2287
|
+
return $cleaned;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
/** @internal */
|
|
2291
|
+
private static function _invalidTypeMsg(array $path, string $needtype, int $vt, mixed $v): string
|
|
2292
|
+
{
|
|
2293
|
+
$vs = $v === null ? 'no value' : self::stringify($v);
|
|
2294
|
+
return 'Expected ' .
|
|
2295
|
+
(1 < self::size($path) ? ('field ' . self::pathify($path, 1) . ' to be ') : '') .
|
|
2296
|
+
$needtype . ', but found ' .
|
|
2297
|
+
($v !== null ? self::typename($vt) . ': ' : '') . $vs . '.';
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
/* =======================
|
|
2301
|
+
* Validation Functions
|
|
2302
|
+
* =======================
|
|
2303
|
+
|
|
2304
|
+
/**
|
|
2305
|
+
* A required string value.
|
|
2306
|
+
*/
|
|
2307
|
+
public static function validate_STRING(object $inj): mixed
|
|
2308
|
+
{
|
|
2309
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2310
|
+
|
|
2311
|
+
$t = self::typify($out);
|
|
2312
|
+
if (0 === (self::T_string & $t)) {
|
|
2313
|
+
$msg = self::_invalidTypeMsg($inj->path, self::S_string, $t, $out);
|
|
2314
|
+
$inj->errs[] = $msg;
|
|
2315
|
+
return self::UNDEF;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
if (self::S_MT === $out) {
|
|
2319
|
+
$msg = 'Empty string at ' . self::pathify($inj->path, 1);
|
|
2320
|
+
$inj->errs[] = $msg;
|
|
2321
|
+
return self::UNDEF;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
return $out;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/**
|
|
2328
|
+
* A required number value (int or float).
|
|
2329
|
+
*/
|
|
2330
|
+
public static function validate_NUMBER(object $inj): mixed
|
|
2331
|
+
{
|
|
2332
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2333
|
+
|
|
2334
|
+
$t = self::typify($out);
|
|
2335
|
+
if (0 === (self::T_number & $t)) {
|
|
2336
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_number, $t, $out);
|
|
2337
|
+
return self::UNDEF;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
return $out;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
/**
|
|
2344
|
+
* A required boolean value.
|
|
2345
|
+
*/
|
|
2346
|
+
public static function validate_BOOLEAN(object $inj): mixed
|
|
2347
|
+
{
|
|
2348
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2349
|
+
|
|
2350
|
+
$t = self::typify($out);
|
|
2351
|
+
if (0 === (self::T_boolean & $t)) {
|
|
2352
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_boolean, $t, $out);
|
|
2353
|
+
return self::UNDEF;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
return $out;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
/**
|
|
2360
|
+
* A required object (map) value (contents not validated).
|
|
2361
|
+
*/
|
|
2362
|
+
public static function validate_OBJECT(object $inj): mixed
|
|
2363
|
+
{
|
|
2364
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2365
|
+
|
|
2366
|
+
$t = self::typify($out);
|
|
2367
|
+
if (0 === (self::T_map & $t)) {
|
|
2368
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_object, $t, $out);
|
|
2369
|
+
return self::UNDEF;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
return $out;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* A required array (list) value (contents not validated).
|
|
2377
|
+
*/
|
|
2378
|
+
public static function validate_ARRAY(object $inj): mixed
|
|
2379
|
+
{
|
|
2380
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2381
|
+
|
|
2382
|
+
$t = self::typify($out);
|
|
2383
|
+
if (0 === (self::T_list & $t)) {
|
|
2384
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, 'list', $t, $out);
|
|
2385
|
+
return self::UNDEF;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
return $out;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
/**
|
|
2392
|
+
* A required function value.
|
|
2393
|
+
*/
|
|
2394
|
+
public static function validate_FUNCTION(object $inj): mixed
|
|
2395
|
+
{
|
|
2396
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2397
|
+
|
|
2398
|
+
$t = self::typify($out);
|
|
2399
|
+
if (0 === (self::T_function & $t)) {
|
|
2400
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::S_function, $t, $out);
|
|
2401
|
+
return self::UNDEF;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
return $out;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* Generic type validator. Validates against any type name via TYPENAME lookup.
|
|
2409
|
+
*/
|
|
2410
|
+
public static function validate_TYPE(object $inj, mixed $_val = null, ?string $ref = null): mixed
|
|
2411
|
+
{
|
|
2412
|
+
$tname = strtolower(substr($ref ?? '', 1));
|
|
2413
|
+
$idx = array_search($tname, self::TYPENAME);
|
|
2414
|
+
$typev = ($idx !== false) ? (1 << (31 - $idx)) : 0;
|
|
2415
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2416
|
+
|
|
2417
|
+
$t = self::typify($out);
|
|
2418
|
+
if (0 === ($t & $typev)) {
|
|
2419
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, $tname, $t, $out);
|
|
2420
|
+
return self::UNDEF;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
return $out;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* Allow any value.
|
|
2428
|
+
*/
|
|
2429
|
+
public static function validate_ANY(object $inj): mixed
|
|
2430
|
+
{
|
|
2431
|
+
$out = self::getprop($inj->dparent, $inj->key);
|
|
2432
|
+
return $out;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
/**
|
|
2436
|
+
* Specify child values for map or list.
|
|
2437
|
+
* Map syntax: {'`$CHILD`': child-template }
|
|
2438
|
+
* List syntax: ['`$CHILD`', child-template ]
|
|
2439
|
+
*/
|
|
2440
|
+
public static function validate_CHILD(object $inj): mixed
|
|
2441
|
+
{
|
|
2442
|
+
$mode = $inj->mode;
|
|
2443
|
+
$key = $inj->key;
|
|
2444
|
+
$parent = $inj->parent;
|
|
2445
|
+
$keys = $inj->keys ?? [];
|
|
2446
|
+
$path = $inj->path;
|
|
2447
|
+
|
|
2448
|
+
// Map syntax.
|
|
2449
|
+
if (self::M_KEYPRE === $mode) {
|
|
2450
|
+
$childtm = self::getprop($parent, $key);
|
|
2451
|
+
|
|
2452
|
+
// Get corresponding current object.
|
|
2453
|
+
$pkey = self::getprop($path, count($path) - 2);
|
|
2454
|
+
$tval = self::getprop($inj->dparent, $pkey);
|
|
2455
|
+
|
|
2456
|
+
if (self::UNDEF == $tval) {
|
|
2457
|
+
$tval = new \stdClass();
|
|
2458
|
+
} elseif (!self::ismap($tval)) {
|
|
2459
|
+
$inj->errs[] = self::_invalidTypeMsg(
|
|
2460
|
+
self::slice($inj->path, 0, -1), self::S_object, self::typify($tval), $tval);
|
|
2461
|
+
return self::UNDEF;
|
|
2462
|
+
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
$ckeys = self::keysof($tval);
|
|
2466
|
+
foreach ($ckeys as $ckey) {
|
|
2467
|
+
self::setprop($parent, $ckey, self::clone($childtm));
|
|
2468
|
+
// NOTE: modifying inj! This extends the child value loop in inject.
|
|
2469
|
+
$keys[] = $ckey;
|
|
2470
|
+
}
|
|
2471
|
+
$inj->keys = $keys;
|
|
2472
|
+
|
|
2473
|
+
// Remove $CHILD to cleanup output.
|
|
2474
|
+
$inj->setval(self::UNDEF);
|
|
2475
|
+
return self::UNDEF;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// List syntax.
|
|
2479
|
+
if (self::M_VAL === $mode) {
|
|
2480
|
+
if (!self::islist($parent)) {
|
|
2481
|
+
// $CHILD was not inside a list.
|
|
2482
|
+
$inj->errs[] = 'Invalid $CHILD as value';
|
|
2483
|
+
return self::UNDEF;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
$childtm = self::getprop($parent, 1);
|
|
2487
|
+
|
|
2488
|
+
if (self::UNDEF === $inj->dparent) {
|
|
2489
|
+
// Empty list as default.
|
|
2490
|
+
while (count($parent) > 0) {
|
|
2491
|
+
array_pop($parent);
|
|
2492
|
+
}
|
|
2493
|
+
return self::UNDEF;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
if (!self::islist($inj->dparent)) {
|
|
2497
|
+
$msg = self::_invalidTypeMsg(
|
|
2498
|
+
self::slice($inj->path, 0, -1), self::S_array, self::typify($inj->dparent), $inj->dparent);
|
|
2499
|
+
$inj->errs[] = $msg;
|
|
2500
|
+
$inj->keyI = count($parent);
|
|
2501
|
+
return $inj->dparent;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// Clone children and reset inj key index.
|
|
2505
|
+
foreach ($inj->dparent as $i => $n) {
|
|
2506
|
+
$parent[$i] = self::clone($childtm);
|
|
2507
|
+
}
|
|
2508
|
+
// Adjust array length
|
|
2509
|
+
while (count($parent) > count($inj->dparent)) {
|
|
2510
|
+
array_pop($parent);
|
|
2511
|
+
}
|
|
2512
|
+
$inj->keyI = 0;
|
|
2513
|
+
$out = self::getprop($inj->dparent, 0);
|
|
2514
|
+
return $out;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
return self::UNDEF;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
/**
|
|
2521
|
+
* Match at least one of the specified shapes.
|
|
2522
|
+
* Syntax: ['`$ONE`', alt0, alt1, ...]
|
|
2523
|
+
*/
|
|
2524
|
+
public static function validate_ONE(
|
|
2525
|
+
object $inj,
|
|
2526
|
+
mixed $_val,
|
|
2527
|
+
string $_ref,
|
|
2528
|
+
mixed $store
|
|
2529
|
+
): mixed {
|
|
2530
|
+
$mode = $inj->mode;
|
|
2531
|
+
$parent = $inj->parent;
|
|
2532
|
+
$keyI = $inj->keyI;
|
|
2533
|
+
|
|
2534
|
+
// Only operate in val mode, since parent is a list.
|
|
2535
|
+
if (self::M_VAL === $mode) {
|
|
2536
|
+
if (!self::islist($parent) || 0 !== $keyI) {
|
|
2537
|
+
$inj->errs[] = 'The $ONE validator at field ' .
|
|
2538
|
+
self::pathify($inj->path, 1, 1) .
|
|
2539
|
+
' must be the first element of an array.';
|
|
2540
|
+
return self::UNDEF;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
$inj->keyI = count($inj->keys ?? []);
|
|
2544
|
+
|
|
2545
|
+
// Clean up structure, replacing [$ONE, ...] with current
|
|
2546
|
+
$inj->setval($inj->dparent, 2);
|
|
2547
|
+
|
|
2548
|
+
$inj->path = self::slice($inj->path, 0, -1);
|
|
2549
|
+
$inj->key = self::getelem($inj->path, -1);
|
|
2550
|
+
|
|
2551
|
+
$tvals = self::slice($parent, 1);
|
|
2552
|
+
if (0 === count($tvals)) {
|
|
2553
|
+
$inj->errs[] = 'The $ONE validator at field ' .
|
|
2554
|
+
self::pathify($inj->path, 1, 1) .
|
|
2555
|
+
' must have at least one argument.';
|
|
2556
|
+
return self::UNDEF;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// See if we can find a match.
|
|
2560
|
+
foreach ($tvals as $tval) {
|
|
2561
|
+
// If match, then errs.length = 0
|
|
2562
|
+
$terrs = [];
|
|
2563
|
+
|
|
2564
|
+
$vstore = array_merge((array) $store, [self::S_DTOP => $inj->dparent]);
|
|
2565
|
+
|
|
2566
|
+
$vcurrent = self::validate($inj->dparent, $tval, (object) [
|
|
2567
|
+
'extra' => $vstore,
|
|
2568
|
+
'errs' => $terrs,
|
|
2569
|
+
'meta' => $inj->meta,
|
|
2570
|
+
]);
|
|
2571
|
+
|
|
2572
|
+
$inj->setval($vcurrent, 2);
|
|
2573
|
+
|
|
2574
|
+
// Accept current value if there was a match
|
|
2575
|
+
if (0 === count($terrs)) {
|
|
2576
|
+
return self::UNDEF;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// There was no match.
|
|
2581
|
+
$tvArr = ($tvals instanceof ListRef) ? $tvals->list : (is_array($tvals) ? $tvals : []);
|
|
2582
|
+
$valdesc = implode(', ', array_map(fn($v) => self::stringify($v), $tvArr));
|
|
2583
|
+
$valdesc = preg_replace(self::R_TRANSFORM_NAME, '$1', strtolower($valdesc));
|
|
2584
|
+
|
|
2585
|
+
$inj->errs[] = self::_invalidTypeMsg(
|
|
2586
|
+
$inj->path,
|
|
2587
|
+
(1 < count($tvals) ? 'one of ' : '') . $valdesc,
|
|
2588
|
+
self::typify($inj->dparent), $inj->dparent);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
return self::UNDEF;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* Match exactly one of the specified values.
|
|
2596
|
+
*/
|
|
2597
|
+
public static function validate_EXACT(object $inj): mixed
|
|
2598
|
+
{
|
|
2599
|
+
$mode = $inj->mode;
|
|
2600
|
+
$parent = $inj->parent;
|
|
2601
|
+
$key = $inj->key;
|
|
2602
|
+
$keyI = $inj->keyI;
|
|
2603
|
+
|
|
2604
|
+
// Only operate in val mode, since parent is a list.
|
|
2605
|
+
if (self::M_VAL === $mode) {
|
|
2606
|
+
if (!self::islist($parent) || 0 !== $keyI) {
|
|
2607
|
+
$inj->errs[] = 'The $EXACT validator at field ' .
|
|
2608
|
+
self::pathify($inj->path, 1, 1) .
|
|
2609
|
+
' must be the first element of an array.';
|
|
2610
|
+
return self::UNDEF;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
$inj->keyI = count($inj->keys ?? []);
|
|
2614
|
+
|
|
2615
|
+
// Clean up structure, replacing [$EXACT, ...] with current data parent
|
|
2616
|
+
$inj->setval($inj->dparent, 2);
|
|
2617
|
+
|
|
2618
|
+
$inj->path = self::slice($inj->path, 0, count($inj->path) - 1);
|
|
2619
|
+
$inj->key = self::getelem($inj->path, -1);
|
|
2620
|
+
|
|
2621
|
+
$tvals = self::slice($parent, 1);
|
|
2622
|
+
if (0 === count($tvals)) {
|
|
2623
|
+
$inj->errs[] = 'The $EXACT validator at field ' .
|
|
2624
|
+
self::pathify($inj->path, 1, 1) .
|
|
2625
|
+
' must have at least one argument.';
|
|
2626
|
+
return self::UNDEF;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
// See if we can find an exact value match.
|
|
2630
|
+
$currentstr = null;
|
|
2631
|
+
foreach ($tvals as $tval) {
|
|
2632
|
+
$exactmatch = $tval === $inj->dparent;
|
|
2633
|
+
|
|
2634
|
+
if (!$exactmatch && self::isnode($tval)) {
|
|
2635
|
+
$currentstr = $currentstr ?? self::stringify($inj->dparent);
|
|
2636
|
+
$tvalstr = self::stringify($tval);
|
|
2637
|
+
$exactmatch = $tvalstr === $currentstr;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
if ($exactmatch) {
|
|
2641
|
+
return self::UNDEF;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
$tvArr = ($tvals instanceof ListRef) ? $tvals->list : (is_array($tvals) ? $tvals : []);
|
|
2646
|
+
$valdesc = implode(', ', array_map(fn($v) => self::stringify($v), $tvArr));
|
|
2647
|
+
$valdesc = preg_replace(self::R_TRANSFORM_NAME, '$1', strtolower($valdesc));
|
|
2648
|
+
|
|
2649
|
+
$inj->errs[] = self::_invalidTypeMsg(
|
|
2650
|
+
$inj->path,
|
|
2651
|
+
(1 < count($inj->path) ? '' : 'value ') .
|
|
2652
|
+
'exactly equal to ' . (1 === count($tvals) ? '' : 'one of ') . $valdesc,
|
|
2653
|
+
self::typify($inj->dparent), $inj->dparent);
|
|
2654
|
+
} else {
|
|
2655
|
+
self::delprop($parent, $key);
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
return self::UNDEF;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* This is the "modify" argument to inject. Use this to perform
|
|
2663
|
+
* generic validation. Runs *after* any special commands.
|
|
2664
|
+
*/
|
|
2665
|
+
private static function _validation(
|
|
2666
|
+
mixed $pval,
|
|
2667
|
+
mixed $key = null,
|
|
2668
|
+
mixed $parent = null,
|
|
2669
|
+
object $inj = null,
|
|
2670
|
+
mixed $store = null
|
|
2671
|
+
): void {
|
|
2672
|
+
if (self::UNDEF === $inj) {
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
if ($pval === self::SKIP) {
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
// select needs exact matches
|
|
2681
|
+
$exact = self::getprop($inj->meta, '`$EXACT`');
|
|
2682
|
+
|
|
2683
|
+
// Current val to verify.
|
|
2684
|
+
$cval = self::getprop($inj->dparent, $key);
|
|
2685
|
+
|
|
2686
|
+
if (self::UNDEF === $inj || (!$exact && self::UNDEF === $cval)) {
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
$ptype = self::typify($pval);
|
|
2691
|
+
|
|
2692
|
+
// Delete any special commands remaining.
|
|
2693
|
+
if (0 < (self::T_string & $ptype) && str_contains($pval, self::S_DS)) {
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
$ctype = self::typify($cval);
|
|
2698
|
+
|
|
2699
|
+
// PHP empty [] is ambiguous (list vs map). When the spec expects a
|
|
2700
|
+
// map, treat an empty array/ListRef in the data as an empty map so
|
|
2701
|
+
// that validation does not produce a spurious type-mismatch error.
|
|
2702
|
+
// Go/Lua don't hit this because they have distinct map/list types.
|
|
2703
|
+
if (0 < (self::T_map & $ptype) &&
|
|
2704
|
+
(is_array($cval) || $cval instanceof \Voxgig\Struct\ListRef) &&
|
|
2705
|
+
0 === count($cval)) {
|
|
2706
|
+
$cval = new \stdClass();
|
|
2707
|
+
$ctype = self::typify($cval);
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// Type mismatch.
|
|
2711
|
+
if ($ptype !== $ctype && self::UNDEF !== $pval) {
|
|
2712
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::typename($ptype), $ctype, $cval);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
if (self::ismap($cval)) {
|
|
2717
|
+
if (!self::ismap($pval)) {
|
|
2718
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::typename($ptype), $ctype, $cval);
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
$ckeys = self::keysof($cval);
|
|
2723
|
+
$pkeys = self::keysof($pval);
|
|
2724
|
+
|
|
2725
|
+
// Empty spec object {} means object can be open (any keys).
|
|
2726
|
+
if (0 < count($pkeys) && true !== self::getprop($pval, '`$OPEN`')) {
|
|
2727
|
+
$badkeys = [];
|
|
2728
|
+
foreach ($ckeys as $ckey) {
|
|
2729
|
+
if (!self::haskey($pval, $ckey)) {
|
|
2730
|
+
$badkeys[] = $ckey;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
// Closed object, so reject extra keys not in shape.
|
|
2735
|
+
if (0 < count($badkeys)) {
|
|
2736
|
+
$msg = 'Unexpected keys at field ' . self::pathify($inj->path, 1) . ': ' . implode(', ', $badkeys);
|
|
2737
|
+
$inj->errs[] = $msg;
|
|
2738
|
+
}
|
|
2739
|
+
} else {
|
|
2740
|
+
// Object is open, so merge in extra keys.
|
|
2741
|
+
self::merge([$pval, $cval]);
|
|
2742
|
+
if (self::isnode($pval)) {
|
|
2743
|
+
self::delprop($pval, '`$OPEN`');
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
} elseif (self::islist($cval)) {
|
|
2747
|
+
if (!self::islist($pval)) {
|
|
2748
|
+
$inj->errs[] = self::_invalidTypeMsg($inj->path, self::typename($ptype), $ctype, $cval);
|
|
2749
|
+
}
|
|
2750
|
+
} elseif ($exact) {
|
|
2751
|
+
if ($cval !== $pval) {
|
|
2752
|
+
$pathmsg = 1 < self::size($inj->path) ? 'at field ' . self::pathify($inj->path, 1) . ': ' : '';
|
|
2753
|
+
$inj->errs[] = 'Value ' . $pathmsg . $cval . ' should equal ' . $pval . '.';
|
|
2754
|
+
}
|
|
2755
|
+
} else {
|
|
2756
|
+
// Spec value was a default, copy over data
|
|
2757
|
+
self::setprop($parent, $key, $cval);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
/**
|
|
2762
|
+
* Validation handler for injection.
|
|
2763
|
+
*/
|
|
2764
|
+
private static function _validatehandler(
|
|
2765
|
+
object $inj,
|
|
2766
|
+
mixed $val,
|
|
2767
|
+
string $ref,
|
|
2768
|
+
mixed $store
|
|
2769
|
+
): mixed {
|
|
2770
|
+
$out = $val;
|
|
2771
|
+
|
|
2772
|
+
$m = preg_match(self::R_META_PATH, $ref, $matches);
|
|
2773
|
+
$ismetapath = null != $m;
|
|
2774
|
+
|
|
2775
|
+
if ($ismetapath) {
|
|
2776
|
+
if ('=' === $matches[2]) {
|
|
2777
|
+
$inj->setval(['`$EXACT`', $val]);
|
|
2778
|
+
} else {
|
|
2779
|
+
$inj->setval($val);
|
|
2780
|
+
}
|
|
2781
|
+
$inj->keyI = -1;
|
|
2782
|
+
|
|
2783
|
+
$out = self::SKIP;
|
|
2784
|
+
} else {
|
|
2785
|
+
$out = self::_injecthandler($inj, $val, $ref, $store);
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
return $out;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
/**
|
|
2792
|
+
* Validate a data structure against a shape specification.
|
|
2793
|
+
* The shape specification follows the "by example" principle.
|
|
2794
|
+
*
|
|
2795
|
+
* @param mixed $data Source data to validate
|
|
2796
|
+
* @param mixed $spec Validation specification
|
|
2797
|
+
* @param mixed $injdef Optional injection definition with extra validators, etc.
|
|
2798
|
+
* @return mixed Validated data
|
|
2799
|
+
*/
|
|
2800
|
+
public static function validate(mixed $data, mixed $spec, mixed $injdef = null): mixed
|
|
2801
|
+
{
|
|
2802
|
+
$extra = is_object($injdef) && property_exists($injdef, 'extra') ? $injdef->extra : null;
|
|
2803
|
+
|
|
2804
|
+
$collect = null != $injdef && property_exists($injdef, 'errs');
|
|
2805
|
+
$errs = (is_object($injdef) && property_exists($injdef, 'errs')) ? $injdef->errs : [];
|
|
2806
|
+
|
|
2807
|
+
$store = array_merge([
|
|
2808
|
+
// Remove the transform commands.
|
|
2809
|
+
'$DELETE' => null,
|
|
2810
|
+
'$COPY' => null,
|
|
2811
|
+
'$KEY' => null,
|
|
2812
|
+
'$META' => null,
|
|
2813
|
+
'$MERGE' => null,
|
|
2814
|
+
'$EACH' => null,
|
|
2815
|
+
'$PACK' => null,
|
|
2816
|
+
|
|
2817
|
+
'$STRING' => [self::class, 'validate_STRING'],
|
|
2818
|
+
'$NUMBER' => [self::class, 'validate_TYPE'],
|
|
2819
|
+
'$INTEGER' => [self::class, 'validate_TYPE'],
|
|
2820
|
+
'$DECIMAL' => [self::class, 'validate_TYPE'],
|
|
2821
|
+
'$BOOLEAN' => [self::class, 'validate_TYPE'],
|
|
2822
|
+
'$NULL' => [self::class, 'validate_TYPE'],
|
|
2823
|
+
'$NIL' => [self::class, 'validate_TYPE'],
|
|
2824
|
+
'$MAP' => [self::class, 'validate_TYPE'],
|
|
2825
|
+
'$LIST' => [self::class, 'validate_TYPE'],
|
|
2826
|
+
'$FUNCTION' => [self::class, 'validate_TYPE'],
|
|
2827
|
+
'$INSTANCE' => [self::class, 'validate_TYPE'],
|
|
2828
|
+
'$ANY' => [self::class, 'validate_ANY'],
|
|
2829
|
+
'$CHILD' => [self::class, 'validate_CHILD'],
|
|
2830
|
+
'$ONE' => [self::class, 'validate_ONE'],
|
|
2831
|
+
'$EXACT' => [self::class, 'validate_EXACT'],
|
|
2832
|
+
|
|
2833
|
+
// A special top level value to collect errors.
|
|
2834
|
+
'$ERRS' => $errs,
|
|
2835
|
+
], (array) ($extra ?? []));
|
|
2836
|
+
|
|
2837
|
+
$meta = is_object($injdef) && property_exists($injdef, 'meta') ? $injdef->meta : null;
|
|
2838
|
+
|
|
2839
|
+
$transformOpts = new \stdClass();
|
|
2840
|
+
$transformOpts->extra = $store;
|
|
2841
|
+
$transformOpts->modify = [self::class, '_validation'];
|
|
2842
|
+
$transformOpts->handler = [self::class, '_validatehandler'];
|
|
2843
|
+
if ($meta !== null) {
|
|
2844
|
+
$transformOpts->meta = $meta;
|
|
2845
|
+
}
|
|
2846
|
+
$transformOpts->errs = $errs;
|
|
2847
|
+
$out = self::transform($data, $spec, $transformOpts);
|
|
2848
|
+
|
|
2849
|
+
$generr = (0 < count($errs) && !$collect);
|
|
2850
|
+
if ($generr) {
|
|
2851
|
+
throw new \Exception('Invalid data: ' . implode(' | ', $errs));
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
return $out;
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
/**
|
|
2858
|
+
* Select children from a top-level object that match a MongoDB-style query.
|
|
2859
|
+
* Supports $and, $or, and equality comparisons.
|
|
2860
|
+
* For arrays, children are elements; for objects, children are values.
|
|
2861
|
+
*
|
|
2862
|
+
* @param mixed $query The query specification
|
|
2863
|
+
* @param mixed $children The object or array to search in
|
|
2864
|
+
* @return array Array of matching children
|
|
2865
|
+
*/
|
|
2866
|
+
public static function select(mixed $children, mixed $query): array
|
|
2867
|
+
{
|
|
2868
|
+
if (!self::isnode($children)) {
|
|
2869
|
+
return [];
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
if (self::ismap($children)) {
|
|
2873
|
+
$children = array_map(function($n) {
|
|
2874
|
+
self::setprop($n[1], self::S_DKEY, $n[0]);
|
|
2875
|
+
return $n[1];
|
|
2876
|
+
}, self::items($children));
|
|
2877
|
+
} else {
|
|
2878
|
+
$children = array_map(function($n, $i) {
|
|
2879
|
+
if (self::ismap($n)) {
|
|
2880
|
+
self::setprop($n, self::S_DKEY, $i);
|
|
2881
|
+
}
|
|
2882
|
+
return $n;
|
|
2883
|
+
}, $children, array_keys($children));
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
$results = [];
|
|
2887
|
+
$injdef = (object) [
|
|
2888
|
+
'errs' => [],
|
|
2889
|
+
'meta' => (object) ['`$EXACT`' => true],
|
|
2890
|
+
'extra' => [
|
|
2891
|
+
'$AND' => [self::class, 'select_AND'],
|
|
2892
|
+
'$OR' => [self::class, 'select_OR'],
|
|
2893
|
+
'$NOT' => [self::class, 'select_NOT'],
|
|
2894
|
+
'$GT' => [self::class, 'select_CMP'],
|
|
2895
|
+
'$LT' => [self::class, 'select_CMP'],
|
|
2896
|
+
'$GTE' => [self::class, 'select_CMP'],
|
|
2897
|
+
'$LTE' => [self::class, 'select_CMP'],
|
|
2898
|
+
'$LIKE' => [self::class, 'select_CMP'],
|
|
2899
|
+
]
|
|
2900
|
+
];
|
|
2901
|
+
|
|
2902
|
+
$q = self::clone($query);
|
|
2903
|
+
|
|
2904
|
+
self::walk($q, function($k, $v) {
|
|
2905
|
+
if (self::ismap($v)) {
|
|
2906
|
+
self::setprop($v, '`$OPEN`', self::getprop($v, '`$OPEN`', true));
|
|
2907
|
+
}
|
|
2908
|
+
return $v;
|
|
2909
|
+
});
|
|
2910
|
+
|
|
2911
|
+
foreach ($children as $child) {
|
|
2912
|
+
$injdef->errs = [];
|
|
2913
|
+
self::validate($child, self::clone($q), $injdef);
|
|
2914
|
+
|
|
2915
|
+
if (count($injdef->errs) === 0) {
|
|
2916
|
+
$results[] = $child;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
return $results;
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
/**
|
|
2924
|
+
* Helper method for $AND operator in select queries
|
|
2925
|
+
*/
|
|
2926
|
+
private static function select_AND(object $state, mixed $_val, mixed $_ref, mixed $store): mixed
|
|
2927
|
+
{
|
|
2928
|
+
if (self::M_KEYPRE === $state->mode) {
|
|
2929
|
+
$terms = self::getprop($state->parent, $state->key);
|
|
2930
|
+
|
|
2931
|
+
$ppath = self::slice($state->path, -1);
|
|
2932
|
+
$point = self::getpath($store, $ppath);
|
|
2933
|
+
|
|
2934
|
+
$vstore = self::merge([(object) [], $store], 1);
|
|
2935
|
+
$vstore->{'$TOP'} = $point;
|
|
2936
|
+
|
|
2937
|
+
foreach ($terms as $term) {
|
|
2938
|
+
$terrs = [];
|
|
2939
|
+
self::validate($point, $term, (object) [
|
|
2940
|
+
'extra' => $vstore,
|
|
2941
|
+
'errs' => $terrs,
|
|
2942
|
+
'meta' => $state->meta,
|
|
2943
|
+
]);
|
|
2944
|
+
|
|
2945
|
+
if (count($terrs) !== 0) {
|
|
2946
|
+
$state->errs[] = 'AND:' . self::pathify($ppath) . ': ' . self::stringify($point) . ' fail:' . self::stringify($terms);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
$gkey = self::getelem($state->path, -2);
|
|
2951
|
+
$gp = self::getelem($state->nodes, -2);
|
|
2952
|
+
self::setprop($gp, $gkey, $point);
|
|
2953
|
+
}
|
|
2954
|
+
return null;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
/**
|
|
2958
|
+
* Helper method for $OR operator in select queries
|
|
2959
|
+
*/
|
|
2960
|
+
private static function select_OR(object $state, mixed $_val, mixed $_ref, mixed $store): mixed
|
|
2961
|
+
{
|
|
2962
|
+
if (self::M_KEYPRE === $state->mode) {
|
|
2963
|
+
$terms = self::getprop($state->parent, $state->key);
|
|
2964
|
+
|
|
2965
|
+
$ppath = self::slice($state->path, -1);
|
|
2966
|
+
$point = self::getpath($store, $ppath);
|
|
2967
|
+
|
|
2968
|
+
$vstore = self::merge([(object) [], $store], 1);
|
|
2969
|
+
$vstore->{'$TOP'} = $point;
|
|
2970
|
+
|
|
2971
|
+
foreach ($terms as $term) {
|
|
2972
|
+
$terrs = [];
|
|
2973
|
+
self::validate($point, $term, (object) [
|
|
2974
|
+
'extra' => $vstore,
|
|
2975
|
+
'errs' => $terrs,
|
|
2976
|
+
'meta' => $state->meta,
|
|
2977
|
+
]);
|
|
2978
|
+
|
|
2979
|
+
if (count($terrs) === 0) {
|
|
2980
|
+
$gkey = self::getelem($state->path, -2);
|
|
2981
|
+
$gp = self::getelem($state->nodes, -2);
|
|
2982
|
+
self::setprop($gp, $gkey, $point);
|
|
2983
|
+
|
|
2984
|
+
return null;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
$state->errs[] = 'OR:' . self::pathify($ppath) . ': ' . self::stringify($point) . ' fail:' . self::stringify($terms);
|
|
2989
|
+
}
|
|
2990
|
+
return null;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
/**
|
|
2994
|
+
* Helper method for $NOT operator in select queries
|
|
2995
|
+
*/
|
|
2996
|
+
private static function select_NOT(object $state, mixed $_val, mixed $_ref, mixed $store): mixed
|
|
2997
|
+
{
|
|
2998
|
+
if (self::M_KEYPRE === $state->mode) {
|
|
2999
|
+
$term = self::getprop($state->parent, $state->key);
|
|
3000
|
+
|
|
3001
|
+
$ppath = self::slice($state->path, -1);
|
|
3002
|
+
$point = self::getpath($store, $ppath);
|
|
3003
|
+
|
|
3004
|
+
$vstore = self::merge([(object) [], $store], 1);
|
|
3005
|
+
$vstore->{'$TOP'} = $point;
|
|
3006
|
+
|
|
3007
|
+
$terrs = [];
|
|
3008
|
+
self::validate($point, $term, (object) [
|
|
3009
|
+
'extra' => $vstore,
|
|
3010
|
+
'errs' => $terrs,
|
|
3011
|
+
'meta' => $state->meta,
|
|
3012
|
+
]);
|
|
3013
|
+
|
|
3014
|
+
if (count($terrs) === 0) {
|
|
3015
|
+
$state->errs[] = 'NOT:' . self::pathify($ppath) . ': ' . self::stringify($point) . ' fail:' . self::stringify($term);
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
$gkey = self::getelem($state->path, -2);
|
|
3019
|
+
$gp = self::getelem($state->nodes, -2);
|
|
3020
|
+
self::setprop($gp, $gkey, $point);
|
|
3021
|
+
}
|
|
3022
|
+
return null;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
/**
|
|
3026
|
+
* Helper method for comparison operators in select queries
|
|
3027
|
+
*/
|
|
3028
|
+
private static function select_CMP(object $state, mixed $_val, mixed $ref, mixed $store): mixed
|
|
3029
|
+
{
|
|
3030
|
+
if (self::M_KEYPRE === $state->mode) {
|
|
3031
|
+
$term = self::getprop($state->parent, $state->key);
|
|
3032
|
+
$gkey = self::getelem($state->path, -2);
|
|
3033
|
+
|
|
3034
|
+
$ppath = self::slice($state->path, -1);
|
|
3035
|
+
$point = self::getpath($store, $ppath);
|
|
3036
|
+
|
|
3037
|
+
$pass = false;
|
|
3038
|
+
|
|
3039
|
+
if ('$GT' === $ref && $point > $term) {
|
|
3040
|
+
$pass = true;
|
|
3041
|
+
}
|
|
3042
|
+
elseif ('$LT' === $ref && $point < $term) {
|
|
3043
|
+
$pass = true;
|
|
3044
|
+
}
|
|
3045
|
+
elseif ('$GTE' === $ref && $point >= $term) {
|
|
3046
|
+
$pass = true;
|
|
3047
|
+
}
|
|
3048
|
+
elseif ('$LTE' === $ref && $point <= $term) {
|
|
3049
|
+
$pass = true;
|
|
3050
|
+
}
|
|
3051
|
+
elseif ('$LIKE' === $ref && preg_match('/' . $term . '/', self::stringify($point))) {
|
|
3052
|
+
$pass = true;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
if ($pass) {
|
|
3056
|
+
// Update spec to match found value so that _validate does not complain
|
|
3057
|
+
$gp = self::getelem($state->nodes, -2);
|
|
3058
|
+
self::setprop($gp, $gkey, $point);
|
|
3059
|
+
}
|
|
3060
|
+
else {
|
|
3061
|
+
$state->errs[] = 'CMP: ' . self::pathify($ppath) . ': ' . self::stringify($point) .
|
|
3062
|
+
' fail:' . $ref . ' ' . self::stringify($term);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
/**
|
|
3069
|
+
* Get element from array by index, supporting negative indices
|
|
3070
|
+
* The key should be an integer, or a string that can parse to an integer only.
|
|
3071
|
+
* Negative integers count from the end of the list.
|
|
3072
|
+
*/
|
|
3073
|
+
public static function getelem(mixed $val, mixed $key, mixed $alt = self::UNDEF): mixed
|
|
3074
|
+
{
|
|
3075
|
+
$out = self::UNDEF;
|
|
3076
|
+
|
|
3077
|
+
if ($val === self::UNDEF || $key === self::UNDEF) {
|
|
3078
|
+
return $alt === self::UNDEF ? null : (is_callable($alt) ? $alt() : $alt);
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
if (self::islist($val)) {
|
|
3082
|
+
$listArr = ($val instanceof ListRef) ? $val->list : $val;
|
|
3083
|
+
$listLen = count($listArr);
|
|
3084
|
+
if (is_string($key)) {
|
|
3085
|
+
if (!preg_match('/^[-0-9]+$/', $key)) {
|
|
3086
|
+
$out = self::UNDEF;
|
|
3087
|
+
} else {
|
|
3088
|
+
$nkey = (int) $key;
|
|
3089
|
+
if ($nkey < 0) {
|
|
3090
|
+
$nkey = $listLen + $nkey;
|
|
3091
|
+
}
|
|
3092
|
+
$out = ($nkey >= 0 && $nkey < $listLen) ? $listArr[$nkey] : self::UNDEF;
|
|
3093
|
+
}
|
|
3094
|
+
} elseif (is_int($key)) {
|
|
3095
|
+
$nkey = $key;
|
|
3096
|
+
if ($nkey < 0) {
|
|
3097
|
+
$nkey = $listLen + $nkey;
|
|
3098
|
+
}
|
|
3099
|
+
$out = ($nkey >= 0 && $nkey < $listLen) ? $listArr[$nkey] : self::UNDEF;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
if ($out === self::UNDEF) {
|
|
3104
|
+
if ($alt === self::UNDEF) {
|
|
3105
|
+
return null;
|
|
3106
|
+
}
|
|
3107
|
+
return is_callable($alt) ? $alt() : $alt;
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
return $out;
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
/**
|
|
3114
|
+
* Safely delete a property from an object or array element.
|
|
3115
|
+
* Undefined arguments and invalid keys are ignored.
|
|
3116
|
+
* Returns the (possibly modified) parent.
|
|
3117
|
+
* For objects, the property is deleted using unset.
|
|
3118
|
+
* For arrays, the element at the index is removed and remaining elements are shifted down.
|
|
3119
|
+
*/
|
|
3120
|
+
public static function delprop(mixed $parent, mixed $key): mixed
|
|
3121
|
+
{
|
|
3122
|
+
if (!self::iskey($key)) {
|
|
3123
|
+
return $parent;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
if ($parent instanceof ListRef) {
|
|
3127
|
+
$keyI = (int)$key;
|
|
3128
|
+
if (!is_numeric($key)) {
|
|
3129
|
+
return $parent;
|
|
3130
|
+
}
|
|
3131
|
+
if ($keyI >= 0 && $keyI < count($parent->list)) {
|
|
3132
|
+
array_splice($parent->list, $keyI, 1);
|
|
3133
|
+
}
|
|
3134
|
+
return $parent;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
if (self::ismap($parent)) {
|
|
3138
|
+
$key = self::strkey($key);
|
|
3139
|
+
unset($parent->$key);
|
|
3140
|
+
}
|
|
3141
|
+
elseif (self::islist($parent)) {
|
|
3142
|
+
// Ensure key is an integer
|
|
3143
|
+
$keyI = (int)$key;
|
|
3144
|
+
if (!is_numeric($key) || (string)$keyI !== (string)$key) {
|
|
3145
|
+
return $parent;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
// Delete list element at position keyI, shifting later elements down
|
|
3149
|
+
if ($keyI >= 0 && $keyI < count($parent)) {
|
|
3150
|
+
for ($pI = $keyI; $pI < count($parent) - 1; $pI++) {
|
|
3151
|
+
$parent[$pI] = $parent[$pI + 1];
|
|
3152
|
+
}
|
|
3153
|
+
array_pop($parent);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
return $parent;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
|
|
3161
|
+
public static function setpath(
|
|
3162
|
+
mixed $store,
|
|
3163
|
+
mixed $path,
|
|
3164
|
+
mixed $val,
|
|
3165
|
+
mixed $injdef = null
|
|
3166
|
+
): mixed {
|
|
3167
|
+
$pathType = self::typify($path);
|
|
3168
|
+
|
|
3169
|
+
$parts = (0 < (self::T_list & $pathType)) ? $path :
|
|
3170
|
+
((0 < (self::T_string & $pathType)) ? explode('.', $path) :
|
|
3171
|
+
((0 < (self::T_number & $pathType)) ? [$path] : self::UNDEF));
|
|
3172
|
+
|
|
3173
|
+
if (self::UNDEF === $parts) {
|
|
3174
|
+
return self::UNDEF;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
$base = self::getprop($injdef, self::S_BASE);
|
|
3178
|
+
$numparts = self::size($parts);
|
|
3179
|
+
$parent = self::getprop($store, $base, $store);
|
|
3180
|
+
|
|
3181
|
+
for ($pI = 0; $pI < $numparts - 1; $pI++) {
|
|
3182
|
+
$partKey = self::getelem($parts, $pI);
|
|
3183
|
+
$nextParent = self::getprop($parent, $partKey);
|
|
3184
|
+
if (!self::isnode($nextParent)) {
|
|
3185
|
+
$nextParent = (0 < (self::T_number & self::typify(self::getelem($parts, $pI + 1))))
|
|
3186
|
+
? [] : new \stdClass();
|
|
3187
|
+
self::setprop($parent, $partKey, $nextParent);
|
|
3188
|
+
}
|
|
3189
|
+
$parent = $nextParent;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
if ($val === self::DELETE) {
|
|
3193
|
+
self::delprop($parent, self::getelem($parts, -1));
|
|
3194
|
+
} else {
|
|
3195
|
+
self::setprop($parent, self::getelem($parts, -1), $val);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
return $parent;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
|
|
3202
|
+
public static function checkPlacement(
|
|
3203
|
+
int $modes,
|
|
3204
|
+
string $ijname,
|
|
3205
|
+
int $parentTypes,
|
|
3206
|
+
object $inj
|
|
3207
|
+
): bool {
|
|
3208
|
+
if (0 === ($modes & $inj->mode)) {
|
|
3209
|
+
$modeItems = array_filter(
|
|
3210
|
+
[self::M_KEYPRE, self::M_KEYPOST, self::M_VAL],
|
|
3211
|
+
fn($m) => $modes & $m
|
|
3212
|
+
);
|
|
3213
|
+
$placementNames = array_map(fn($m) => self::PLACEMENT[$m] ?? '', $modeItems);
|
|
3214
|
+
$inj->errs[] = '$' . $ijname . ': invalid placement as ' . (self::PLACEMENT[$inj->mode] ?? '') .
|
|
3215
|
+
', expected: ' . implode(',', $placementNames) . '.';
|
|
3216
|
+
return false;
|
|
3217
|
+
}
|
|
3218
|
+
if (!self::isempty($parentTypes)) {
|
|
3219
|
+
$ptype = self::typify($inj->parent);
|
|
3220
|
+
if (0 === ($parentTypes & $ptype)) {
|
|
3221
|
+
$inj->errs[] = '$' . $ijname . ': invalid placement in parent ' . self::typename($ptype) .
|
|
3222
|
+
', expected: ' . self::typename($parentTypes) . '.';
|
|
3223
|
+
return false;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
return true;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
|
|
3230
|
+
public static function injectorArgs(array $argTypes, array $args): array
|
|
3231
|
+
{
|
|
3232
|
+
$numargs = self::size($argTypes);
|
|
3233
|
+
$found = array_fill(0, 1 + $numargs, self::UNDEF);
|
|
3234
|
+
$found[0] = self::UNDEF;
|
|
3235
|
+
for ($argI = 0; $argI < $numargs; $argI++) {
|
|
3236
|
+
$arg = $args[$argI] ?? self::UNDEF;
|
|
3237
|
+
$argType = self::typify($arg);
|
|
3238
|
+
if (0 === ($argTypes[$argI] & $argType)) {
|
|
3239
|
+
$found[0] = 'invalid argument: ' . self::stringify($arg, 22) .
|
|
3240
|
+
' (' . self::typename($argType) . ' at position ' . (1 + $argI) .
|
|
3241
|
+
') is not of type: ' . self::typename($argTypes[$argI]) . '.';
|
|
3242
|
+
break;
|
|
3243
|
+
}
|
|
3244
|
+
$found[1 + $argI] = $arg;
|
|
3245
|
+
}
|
|
3246
|
+
return $found;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
|
|
3250
|
+
public static function injectChild(mixed $child, mixed $store, object $inj): object
|
|
3251
|
+
{
|
|
3252
|
+
$cinj = $inj;
|
|
3253
|
+
|
|
3254
|
+
// Replace ['`$FORMAT`',...] with child
|
|
3255
|
+
if (null !== $inj->prior) {
|
|
3256
|
+
if (null !== $inj->prior->prior) {
|
|
3257
|
+
$cinj = $inj->prior->prior->child($inj->prior->keyI, $inj->prior->keys);
|
|
3258
|
+
$cinj->val = $child;
|
|
3259
|
+
self::setprop($cinj->parent, $inj->prior->key, $child);
|
|
3260
|
+
}
|
|
3261
|
+
else {
|
|
3262
|
+
$cinj = $inj->prior->child($inj->keyI, $inj->keys);
|
|
3263
|
+
$cinj->val = $child;
|
|
3264
|
+
self::setprop($cinj->parent, $inj->key, $child);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
self::inject($child, $store, $cinj);
|
|
3269
|
+
|
|
3270
|
+
return $cinj;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
|
|
3276
|
+
class Injection
|
|
3277
|
+
{
|
|
3278
|
+
|
|
3279
|
+
public int $mode;
|
|
3280
|
+
public bool $full;
|
|
3281
|
+
public int $keyI;
|
|
3282
|
+
public array $keys;
|
|
3283
|
+
public string $key;
|
|
3284
|
+
public mixed $val;
|
|
3285
|
+
public mixed $parent;
|
|
3286
|
+
public array $path;
|
|
3287
|
+
public array $nodes;
|
|
3288
|
+
/** @var callable */
|
|
3289
|
+
public mixed $handler;
|
|
3290
|
+
public array $errs;
|
|
3291
|
+
public object $meta;
|
|
3292
|
+
public mixed $dparent;
|
|
3293
|
+
public array $dpath;
|
|
3294
|
+
public string $base;
|
|
3295
|
+
/** @var callable|null */
|
|
3296
|
+
public mixed $modify;
|
|
3297
|
+
public ?Injection $prior;
|
|
3298
|
+
public mixed $extra;
|
|
3299
|
+
|
|
3300
|
+
public function __construct(mixed $val, mixed $parent)
|
|
3301
|
+
{
|
|
3302
|
+
$this->val = $val;
|
|
3303
|
+
$this->parent = $parent;
|
|
3304
|
+
$this->errs = [];
|
|
3305
|
+
|
|
3306
|
+
$this->dparent = Struct::UNDEF;
|
|
3307
|
+
$this->dpath = ['$TOP'];
|
|
3308
|
+
|
|
3309
|
+
$this->mode = Struct::M_VAL;
|
|
3310
|
+
$this->full = false;
|
|
3311
|
+
$this->keyI = 0;
|
|
3312
|
+
$this->keys = ['$TOP'];
|
|
3313
|
+
$this->key = '$TOP';
|
|
3314
|
+
$this->path = ['$TOP'];
|
|
3315
|
+
$this->nodes = [$parent];
|
|
3316
|
+
$this->handler = [Struct::class, '_injecthandler'];
|
|
3317
|
+
$this->base = '$TOP';
|
|
3318
|
+
$this->meta = (object) [];
|
|
3319
|
+
$this->modify = null;
|
|
3320
|
+
$this->prior = null;
|
|
3321
|
+
$this->extra = null;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
|
|
3325
|
+
public function __toString(): string
|
|
3326
|
+
{
|
|
3327
|
+
return $this->toString();
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
public function toString(?string $prefix = null): string
|
|
3331
|
+
{
|
|
3332
|
+
return 'INJ' . (null === $prefix ? '' : '/' . $prefix) . ':' .
|
|
3333
|
+
Struct::pad(Struct::pathify($this->path, 1)) .
|
|
3334
|
+
(Struct::MODENAME[$this->mode] ?? '') . ($this->full ? '/full' : '') . ':' .
|
|
3335
|
+
'key=' . $this->keyI . '/' . $this->key . '/' . '[' . implode(',', $this->keys) . ']' .
|
|
3336
|
+
' p=' . Struct::stringify($this->parent, -1, 1) .
|
|
3337
|
+
' m=' . Struct::stringify($this->meta, -1, 1) .
|
|
3338
|
+
' d/' . Struct::pathify($this->dpath, 1) . '=' . Struct::stringify($this->dparent, -1, 1) .
|
|
3339
|
+
' r=' . Struct::stringify(Struct::getprop($this->nodes[0] ?? null, '$TOP'), -1, 1);
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
|
|
3343
|
+
public function descend(): mixed
|
|
3344
|
+
{
|
|
3345
|
+
if (!isset($this->meta->__d)) {
|
|
3346
|
+
$this->meta->__d = 0;
|
|
3347
|
+
}
|
|
3348
|
+
$this->meta->__d++;
|
|
3349
|
+
$parentkey = Struct::getelem($this->path, -2);
|
|
3350
|
+
|
|
3351
|
+
// Resolve current node in store for local paths.
|
|
3352
|
+
if (Struct::UNDEF === $this->dparent) {
|
|
3353
|
+
|
|
3354
|
+
// Even if there's no data, dpath should continue to match path, so that
|
|
3355
|
+
// relative paths work properly.
|
|
3356
|
+
if (1 < Struct::size($this->dpath)) {
|
|
3357
|
+
$this->dpath = Struct::flatten([$this->dpath, $parentkey]);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
else {
|
|
3361
|
+
// this->dparent is the containing node of the current store value.
|
|
3362
|
+
if (null !== $parentkey && Struct::UNDEF !== $parentkey) {
|
|
3363
|
+
$this->dparent = Struct::getprop($this->dparent, $parentkey);
|
|
3364
|
+
|
|
3365
|
+
$lastpart = Struct::getelem($this->dpath, -1);
|
|
3366
|
+
if ($lastpart === '$:' . $parentkey) {
|
|
3367
|
+
$this->dpath = Struct::slice($this->dpath, -1);
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
$this->dpath = Struct::flatten([$this->dpath, $parentkey]);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
return $this->dparent;
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
|
|
3379
|
+
public function child(int $keyI, array $keys): Injection
|
|
3380
|
+
{
|
|
3381
|
+
$key = Struct::strkey($keys[$keyI] ?? null);
|
|
3382
|
+
$val = $this->val;
|
|
3383
|
+
|
|
3384
|
+
$cinj = new Injection(Struct::getprop($val, $key), $val);
|
|
3385
|
+
$cinj->keyI = $keyI;
|
|
3386
|
+
$cinj->keys = $keys;
|
|
3387
|
+
$cinj->key = $key;
|
|
3388
|
+
|
|
3389
|
+
$cinj->path = Struct::flatten([Struct::getdef($this->path, []), $key]);
|
|
3390
|
+
$cinj->nodes = Struct::flatten([Struct::getdef($this->nodes, []), [$val]]);
|
|
3391
|
+
|
|
3392
|
+
$cinj->mode = $this->mode;
|
|
3393
|
+
$cinj->handler = $this->handler;
|
|
3394
|
+
$cinj->modify = $this->modify;
|
|
3395
|
+
$cinj->base = $this->base;
|
|
3396
|
+
$cinj->meta = $this->meta;
|
|
3397
|
+
$cinj->errs = &$this->errs;
|
|
3398
|
+
$cinj->prior = $this;
|
|
3399
|
+
|
|
3400
|
+
$cinj->dpath = Struct::flatten([$this->dpath]);
|
|
3401
|
+
$cinj->dparent = $this->dparent;
|
|
3402
|
+
|
|
3403
|
+
return $cinj;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
|
|
3407
|
+
public function setval(mixed $val, ?int $ancestor = null): mixed
|
|
3408
|
+
{
|
|
3409
|
+
$parent = Struct::UNDEF;
|
|
3410
|
+
if (null === $ancestor || $ancestor < 2) {
|
|
3411
|
+
if (Struct::UNDEF === $val) {
|
|
3412
|
+
$this->parent = Struct::delprop($this->parent, $this->key);
|
|
3413
|
+
$parent = $this->parent;
|
|
3414
|
+
} else {
|
|
3415
|
+
$parent = Struct::setprop($this->parent, $this->key, $val);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
else {
|
|
3419
|
+
$aval = Struct::getelem($this->nodes, 0 - $ancestor);
|
|
3420
|
+
$akey = Struct::getelem($this->path, 0 - $ancestor);
|
|
3421
|
+
if (Struct::UNDEF === $val) {
|
|
3422
|
+
$parent = Struct::delprop($aval, $akey);
|
|
3423
|
+
} else {
|
|
3424
|
+
$parent = Struct::setprop($aval, $akey, $val);
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
return $parent;
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
?>
|