@voxgig/sdkgen-langpack 0.0.1
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/.sdk/model/target/dart.aon +63 -0
- package/.sdk/model/target/haskell.aon +72 -0
- package/.sdk/model/target/lean.aon +91 -0
- package/.sdk/src/cmp/dart/Config_dart.ts +248 -0
- package/.sdk/src/cmp/dart/EntityBase_dart.ts +34 -0
- package/.sdk/src/cmp/dart/EntityOperation_dart.ts +43 -0
- package/.sdk/src/cmp/dart/EntityTypes_dart.ts +207 -0
- package/.sdk/src/cmp/dart/Entity_dart.ts +87 -0
- package/.sdk/src/cmp/dart/Gitignore_dart.ts +34 -0
- package/.sdk/src/cmp/dart/MainEntity_dart.ts +33 -0
- package/.sdk/src/cmp/dart/Main_dart.ts +178 -0
- package/.sdk/src/cmp/dart/Package_dart.ts +82 -0
- package/.sdk/src/cmp/dart/ReadmeEntity_dart.ts +177 -0
- package/.sdk/src/cmp/dart/ReadmeExamplesTest_dart.ts +238 -0
- package/.sdk/src/cmp/dart/ReadmeExplanation_dart.ts +53 -0
- package/.sdk/src/cmp/dart/ReadmeHowto_dart.ts +152 -0
- package/.sdk/src/cmp/dart/ReadmeInstall_dart.ts +59 -0
- package/.sdk/src/cmp/dart/ReadmeIntro_dart.ts +65 -0
- package/.sdk/src/cmp/dart/ReadmeModel_dart.ts +151 -0
- package/.sdk/src/cmp/dart/ReadmeOptions_dart.ts +63 -0
- package/.sdk/src/cmp/dart/ReadmeQuick_dart.ts +225 -0
- package/.sdk/src/cmp/dart/ReadmeRef_dart.ts +397 -0
- package/.sdk/src/cmp/dart/ReadmeTopHowto_dart.ts +24 -0
- package/.sdk/src/cmp/dart/ReadmeTopQuick_dart.ts +106 -0
- package/.sdk/src/cmp/dart/ReadmeTopTest_dart.ts +78 -0
- package/.sdk/src/cmp/dart/SdkError_dart.ts +42 -0
- package/.sdk/src/cmp/dart/TestDirect_dart.ts +516 -0
- package/.sdk/src/cmp/dart/TestEntity_dart.ts +628 -0
- package/.sdk/src/cmp/dart/Test_dart.ts +138 -0
- package/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +98 -0
- package/.sdk/src/cmp/dart/fragment/Config.fragment.dart +70 -0
- package/.sdk/src/cmp/dart/fragment/Direct.test.fragment.dart +25 -0
- package/.sdk/src/cmp/dart/fragment/Entity.fragment.dart +29 -0
- package/.sdk/src/cmp/dart/fragment/Entity.test.fragment.dart +28 -0
- package/.sdk/src/cmp/dart/fragment/EntityBase.fragment.dart +302 -0
- package/.sdk/src/cmp/dart/fragment/EntityCreateOp.fragment.dart +98 -0
- package/.sdk/src/cmp/dart/fragment/EntityListOp.fragment.dart +90 -0
- package/.sdk/src/cmp/dart/fragment/EntityLoadOp.fragment.dart +102 -0
- package/.sdk/src/cmp/dart/fragment/EntityRemoveOp.fragment.dart +108 -0
- package/.sdk/src/cmp/dart/fragment/EntityUpdateOp.fragment.dart +102 -0
- package/.sdk/src/cmp/dart/fragment/Main.fragment.dart +339 -0
- package/.sdk/src/cmp/dart/fragment/SdkError.fragment.dart +26 -0
- package/.sdk/src/cmp/dart/tsconfig.json +15 -0
- package/.sdk/src/cmp/dart/utility_dart.ts +155 -0
- package/.sdk/src/cmp/haskell/Config_haskell.ts +98 -0
- package/.sdk/src/cmp/haskell/Entity_haskell.ts +13 -0
- package/.sdk/src/cmp/haskell/Gitignore_haskell.ts +27 -0
- package/.sdk/src/cmp/haskell/Main_haskell.ts +123 -0
- package/.sdk/src/cmp/haskell/Package_haskell.ts +60 -0
- package/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +186 -0
- package/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +135 -0
- package/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +48 -0
- package/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +168 -0
- package/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +51 -0
- package/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +67 -0
- package/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +162 -0
- package/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +74 -0
- package/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +241 -0
- package/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +408 -0
- package/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +31 -0
- package/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +104 -0
- package/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +80 -0
- package/.sdk/src/cmp/haskell/Test_haskell.ts +297 -0
- package/.sdk/src/cmp/haskell/tsconfig.json +15 -0
- package/.sdk/src/cmp/haskell/utility_haskell.ts +166 -0
- package/.sdk/src/cmp/lean/Config_lean.ts +93 -0
- package/.sdk/src/cmp/lean/Entity_lean.ts +9 -0
- package/.sdk/src/cmp/lean/Gitignore_lean.ts +18 -0
- package/.sdk/src/cmp/lean/Main_lean.ts +120 -0
- package/.sdk/src/cmp/lean/Package_lean.ts +93 -0
- package/.sdk/src/cmp/lean/ReadmeEntity_lean.ts +29 -0
- package/.sdk/src/cmp/lean/ReadmeExplanation_lean.ts +19 -0
- package/.sdk/src/cmp/lean/ReadmeHowto_lean.ts +25 -0
- package/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +39 -0
- package/.sdk/src/cmp/lean/ReadmeIntro_lean.ts +25 -0
- package/.sdk/src/cmp/lean/ReadmeModel_lean.ts +24 -0
- package/.sdk/src/cmp/lean/ReadmeOptions_lean.ts +22 -0
- package/.sdk/src/cmp/lean/ReadmeQuick_lean.ts +50 -0
- package/.sdk/src/cmp/lean/ReadmeRef_lean.ts +40 -0
- package/.sdk/src/cmp/lean/ReadmeTopHowto_lean.ts +16 -0
- package/.sdk/src/cmp/lean/ReadmeTopQuick_lean.ts +23 -0
- package/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +19 -0
- package/.sdk/src/cmp/lean/Test_lean.ts +261 -0
- package/.sdk/src/cmp/lean/utility_lean.ts +129 -0
- package/.sdk/tm/dart/LICENSE +22 -0
- package/.sdk/tm/dart/Makefile +50 -0
- package/.sdk/tm/dart/lib/Context.dart +150 -0
- package/.sdk/tm/dart/lib/Control.dart +15 -0
- package/.sdk/tm/dart/lib/Operation.dart +31 -0
- package/.sdk/tm/dart/lib/Point.dart +51 -0
- package/.sdk/tm/dart/lib/Response.dart +32 -0
- package/.sdk/tm/dart/lib/Result.dart +39 -0
- package/.sdk/tm/dart/lib/Spec.dart +49 -0
- package/.sdk/tm/dart/lib/feature/audit/AuditFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/base/BaseFeature.dart +76 -0
- package/.sdk/tm/dart/lib/feature/cache/CacheFeature.dart +152 -0
- package/.sdk/tm/dart/lib/feature/clienttrack/ClienttrackFeature.dart +104 -0
- package/.sdk/tm/dart/lib/feature/cost/CostFeature.dart +446 -0
- package/.sdk/tm/dart/lib/feature/debug/DebugFeature.dart +153 -0
- package/.sdk/tm/dart/lib/feature/idempotency/IdempotencyFeature.dart +92 -0
- package/.sdk/tm/dart/lib/feature/log/LogFeature.dart +85 -0
- package/.sdk/tm/dart/lib/feature/metrics/MetricsFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/netsim/NetsimFeature.dart +186 -0
- package/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +240 -0
- package/.sdk/tm/dart/lib/feature/proxy/ProxyFeature.dart +118 -0
- package/.sdk/tm/dart/lib/feature/ratelimit/RatelimitFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/rbac/RbacFeature.dart +121 -0
- package/.sdk/tm/dart/lib/feature/retry/RetryFeature.dart +155 -0
- package/.sdk/tm/dart/lib/feature/secrets/SecretsFeature.dart +649 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/capability.dart +105 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/catalog.dart +46 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/config.dart +297 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/depend.dart +198 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/env.dart +144 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/export.dart +58 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/graph.dart +160 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/host.dart +1067 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/order.dart +172 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/plugin.dart +23 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/point.dart +117 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/ref.dart +105 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/resolve.dart +58 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/types.dart +201 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/version.dart +116 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/aws.dart +285 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/azuresecrets.dart +178 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/boru.dart +145 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/crypto.dart +194 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/doppler.dart +101 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/gcpsecrets.dart +143 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/hashicorp.dart +190 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/httpjson.dart +363 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/infisical.dart +149 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/onepassword.dart +146 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/secretspec.dart +116 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/sigv4.dart +237 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/addr.dart +138 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/json.dart +276 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/provider.dart +31 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/providers.dart +243 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/sekreto.dart +730 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/spec.dart +200 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/support.dart +217 -0
- package/.sdk/tm/dart/lib/feature/streaming/StreamingFeature.dart +97 -0
- package/.sdk/tm/dart/lib/feature/telemetry/TelemetryFeature.dart +136 -0
- package/.sdk/tm/dart/lib/feature/test/TestFeature.dart +328 -0
- package/.sdk/tm/dart/lib/feature/timeout/TimeoutFeature.dart +104 -0
- package/.sdk/tm/dart/lib/utility/CleanUtility.dart +12 -0
- package/.sdk/tm/dart/lib/utility/DoneUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/ErrUtility.dart +46 -0
- package/.sdk/tm/dart/lib/utility/FeatureAddUtility.dart +35 -0
- package/.sdk/tm/dart/lib/utility/FeatureHookUtility.dart +20 -0
- package/.sdk/tm/dart/lib/utility/FeatureInitUtility.dart +9 -0
- package/.sdk/tm/dart/lib/utility/FetcherUtility.dart +104 -0
- package/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/.sdk/tm/dart/lib/utility/MakeContextUtility.dart +6 -0
- package/.sdk/tm/dart/lib/utility/MakeErrorUtility.dart +71 -0
- package/.sdk/tm/dart/lib/utility/MakeFetchDefUtility.dart +42 -0
- package/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +233 -0
- package/.sdk/tm/dart/lib/utility/MakePointUtility.dart +130 -0
- package/.sdk/tm/dart/lib/utility/MakeRequestUtility.dart +66 -0
- package/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +59 -0
- package/.sdk/tm/dart/lib/utility/MakeResultUtility.dart +49 -0
- package/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +77 -0
- package/.sdk/tm/dart/lib/utility/MakeUrlUtility.dart +51 -0
- package/.sdk/tm/dart/lib/utility/ParamUtility.dart +56 -0
- package/.sdk/tm/dart/lib/utility/PrepareAuthUtility.dart +46 -0
- package/.sdk/tm/dart/lib/utility/PrepareBodyUtility.dart +19 -0
- package/.sdk/tm/dart/lib/utility/PrepareHeadersUtility.dart +11 -0
- package/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +30 -0
- package/.sdk/tm/dart/lib/utility/PrepareParamsUtility.dart +23 -0
- package/.sdk/tm/dart/lib/utility/PreparePathUtility.dart +9 -0
- package/.sdk/tm/dart/lib/utility/PrepareQueryUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/ResultBasicUtility.dart +29 -0
- package/.sdk/tm/dart/lib/utility/ResultBodyUtility.dart +15 -0
- package/.sdk/tm/dart/lib/utility/ResultHeadersUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/StructUtility.dart +127 -0
- package/.sdk/tm/dart/lib/utility/TransformRequestUtility.dart +28 -0
- package/.sdk/tm/dart/lib/utility/TransformResponseUtility.dart +44 -0
- package/.sdk/tm/dart/lib/utility/Utility.dart +239 -0
- package/.sdk/tm/dart/lib/utility/voxgig_struct.dart +2462 -0
- package/.sdk/tm/dart/src/feature/README.md +3 -0
- package/.sdk/tm/dart/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/cost/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/secrets/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/dart/test/custom_test.dart +65 -0
- package/.sdk/tm/dart/test/exists_test.dart +12 -0
- package/.sdk/tm/dart/test/feature/harness.dart +347 -0
- package/.sdk/tm/dart/test/feature/secrets/secrets_test.dart +1107 -0
- package/.sdk/tm/dart/test/feature_test.dart +1193 -0
- package/.sdk/tm/dart/test/harness.dart +153 -0
- package/.sdk/tm/dart/test/netsim_test.dart +48 -0
- package/.sdk/tm/dart/test/omni.dart +520 -0
- package/.sdk/tm/dart/test/omni_smoke_test.dart +143 -0
- package/.sdk/tm/dart/test/pipeline_test.dart +608 -0
- package/.sdk/tm/dart/test/primary_test.dart +476 -0
- package/.sdk/tm/dart/test/sdk-test-control.json +19 -0
- package/.sdk/tm/dart/test/struct_test.dart +468 -0
- package/.sdk/tm/dart/test/utility.dart +198 -0
- package/.sdk/tm/dart/test/vendor/omni/omni.dart +7 -0
- package/.sdk/tm/dart/test/vendor/omni/runner.dart +653 -0
- package/.sdk/tm/dart/test/vendor/omni/util.dart +232 -0
- package/.sdk/tm/haskell/LICENSE +21 -0
- package/.sdk/tm/haskell/Makefile +22 -0
- package/.sdk/tm/haskell/VERSION +1 -0
- package/.sdk/tm/haskell/src/SdkFeatures.hs +1440 -0
- package/.sdk/tm/haskell/src/SdkHelpers.hs +337 -0
- package/.sdk/tm/haskell/src/SdkJson.hs +111 -0
- package/.sdk/tm/haskell/src/SdkRuntime.hs +1279 -0
- package/.sdk/tm/haskell/src/SdkTypes.hs +195 -0
- package/.sdk/tm/haskell/src/VoxgigStruct.hs +2299 -0
- package/.sdk/tm/haskell/src/Vregex.hs +237 -0
- package/.sdk/tm/haskell/src/feature/README.md +5 -0
- package/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/haskell/test/Harness.hs +281 -0
- package/.sdk/tm/haskell/test/OmniResolver.hs +252 -0
- package/.sdk/tm/haskell/test/Runner.hs +49 -0
- package/.sdk/tm/haskell/test/StructCorpus.hs +449 -0
- package/.sdk/tm/haskell/test/TCustomUtility.hs +38 -0
- package/.sdk/tm/haskell/test/TFeature.hs +794 -0
- package/.sdk/tm/haskell/test/TNetsim.hs +50 -0
- package/.sdk/tm/haskell/test/TPipeline.hs +334 -0
- package/.sdk/tm/haskell/test/TPrimaryCorpus.hs +263 -0
- package/.sdk/tm/haskell/test/TPrimaryUtility.hs +386 -0
- package/.sdk/tm/haskell/test/Testutil.hs +59 -0
- package/.sdk/tm/haskell/test/vendor/omni/Omni.hs +1147 -0
- package/.sdk/tm/lean/LICENSE +21 -0
- package/.sdk/tm/lean/Makefile +32 -0
- package/.sdk/tm/lean/VERSION +1 -0
- package/.sdk/tm/lean/src/SdkFeature.lean +171 -0
- package/.sdk/tm/lean/src/SdkFeatures.lean +787 -0
- package/.sdk/tm/lean/src/SdkJson.lean +134 -0
- package/.sdk/tm/lean/src/SdkRuntime.lean +374 -0
- package/.sdk/tm/lean/src/SdkUtility.lean +809 -0
- package/.sdk/tm/lean/src/VoxgigStruct.lean +2880 -0
- package/.sdk/tm/lean/src/Vregex.lean +412 -0
- package/.sdk/tm/lean/src/feature/README.md +5 -0
- package/.sdk/tm/lean/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/cost/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/lean/test/OmniResolver.lean +358 -0
- package/.sdk/tm/lean/test/OmniSmoke.lean +174 -0
- package/.sdk/tm/lean/test/StructCorpus.lean +368 -0
- package/.sdk/tm/lean/test/TFeature.lean +281 -0
- package/.sdk/tm/lean/test/TPrimaryUtility.lean +227 -0
- package/.sdk/tm/lean/test/vendor/omni/Omni.lean +997 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/package.json +78 -0
- package/sdkgen-package.json +22 -0
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
-- VENDORED: @voxgig/omni sdk-20260908-1556-0 (lean/Omni.lean)
|
|
2
|
+
-- Source: https://github.com/voxgig/omni @ 274708cc2d12b21707d975543953f845f8444be0 [tag: sdk-20260908-1556-0]
|
|
3
|
+
-- License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
/-
|
|
5
|
+
Omni: the shared multi-language test runner (Lean 4 port).
|
|
6
|
+
|
|
7
|
+
A test spec is plain JSON. The same spec file drives the same tests in
|
|
8
|
+
every language that ships an omni port.
|
|
9
|
+
|
|
10
|
+
Port of the canonical TypeScript implementation
|
|
11
|
+
(typescript/src/Runner.ts). Behaviour must match, case for case.
|
|
12
|
+
|
|
13
|
+
Lean has no exceptions in pure code, so a subject reports failure as
|
|
14
|
+
`Except.error message`, and a failing check is returned the same way.
|
|
15
|
+
-/
|
|
16
|
+
|
|
17
|
+
import Lean.Data.Json
|
|
18
|
+
|
|
19
|
+
open Lean
|
|
20
|
+
|
|
21
|
+
namespace Omni
|
|
22
|
+
|
|
23
|
+
/-- Value is JSON null. -/
|
|
24
|
+
def nullmark : String := "__NULL__"
|
|
25
|
+
/-- Value is not present. -/
|
|
26
|
+
def undefmark : String := "__UNDEF__"
|
|
27
|
+
/-- Value exists (not undefined). -/
|
|
28
|
+
def existsmark : String := "__EXISTS__"
|
|
29
|
+
|
|
30
|
+
/-- A JSON value, or absence. `none` is the marker for "no value at all",
|
|
31
|
+
as distinct from `some Json.null`.
|
|
32
|
+
|
|
33
|
+
`Lean.Json` stores objects in a SORTED tree, so key order is not preserved:
|
|
34
|
+
a spec whose maps are written out of key order reaches a consumer reordered.
|
|
35
|
+
Every map in voxgig/struct's corpus happens to be key-sorted, so nothing
|
|
36
|
+
notices today - but a consumer whose own maps are insertion-ordered (most
|
|
37
|
+
are) cannot rely on that. omni-swift carried the same defect and its value
|
|
38
|
+
model was its own to change (#32); this one is Lean's. -/
|
|
39
|
+
abbrev Val := Option Json
|
|
40
|
+
|
|
41
|
+
def ismap (val : Val) : Bool :=
|
|
42
|
+
match val with
|
|
43
|
+
| some (.obj _) => true
|
|
44
|
+
| _ => false
|
|
45
|
+
|
|
46
|
+
def islist (val : Val) : Bool :=
|
|
47
|
+
match val with
|
|
48
|
+
| some (.arr _) => true
|
|
49
|
+
| _ => false
|
|
50
|
+
|
|
51
|
+
def isnode (val : Val) : Bool := ismap val || islist val
|
|
52
|
+
|
|
53
|
+
def isabsent (val : Val) : Bool := val.isNone
|
|
54
|
+
|
|
55
|
+
def isnull (val : Val) : Bool :=
|
|
56
|
+
match val with
|
|
57
|
+
| some .null => true
|
|
58
|
+
| _ => false
|
|
59
|
+
|
|
60
|
+
def isnone (val : Val) : Bool := isabsent val || isnull val
|
|
61
|
+
|
|
62
|
+
def isnum (val : Val) : Bool :=
|
|
63
|
+
match val with
|
|
64
|
+
| some (.num _) => true
|
|
65
|
+
| _ => false
|
|
66
|
+
|
|
67
|
+
def isstr (val : Val) : Bool :=
|
|
68
|
+
match val with
|
|
69
|
+
| some (.str _) => true
|
|
70
|
+
| _ => false
|
|
71
|
+
|
|
72
|
+
def asnum (val : Val) : Option Float :=
|
|
73
|
+
match val with
|
|
74
|
+
| some (.num n) => some n.toFloat
|
|
75
|
+
| _ => none
|
|
76
|
+
|
|
77
|
+
def asstr (val : Val) : Option String :=
|
|
78
|
+
match val with
|
|
79
|
+
| some (.str s) => some s
|
|
80
|
+
| _ => none
|
|
81
|
+
|
|
82
|
+
def aslist (val : Val) : Option (Array Json) :=
|
|
83
|
+
match val with
|
|
84
|
+
| some (.arr a) => some a
|
|
85
|
+
| _ => none
|
|
86
|
+
|
|
87
|
+
def asmap (val : Val) : Option (List (String × Json)) :=
|
|
88
|
+
match val with
|
|
89
|
+
| some (.obj kvs) => some (kvs.toArray.map (fun kv => (kv.1, kv.2))).toList
|
|
90
|
+
| _ => none
|
|
91
|
+
|
|
92
|
+
/-- Read a map entry. Returns `none` (absent) when missing.
|
|
93
|
+
|
|
94
|
+
`getObjVal?` rather than reaching into the `.obj` payload directly: the
|
|
95
|
+
container behind it is Lean's own business and it has already changed once
|
|
96
|
+
(`RBNode` through v4.16, `Std.TreeMap.Raw` by v4.32), taking `find`'s
|
|
97
|
+
signature with it. A consumer pinned to a newer toolchain than this one -
|
|
98
|
+
struct/lean is on v4.32 - could not compile the runner at all. -/
|
|
99
|
+
def jget (val : Val) (key : String) : Val :=
|
|
100
|
+
match val with
|
|
101
|
+
| some json => (json.getObjVal? key).toOption
|
|
102
|
+
| _ => none
|
|
103
|
+
|
|
104
|
+
/-- Is a map key present at all (even with a null value)? -/
|
|
105
|
+
def jhas (val : Val) (key : String) : Bool := (jget val key).isSome
|
|
106
|
+
|
|
107
|
+
/-- A copy of a map with one entry set (no-op for non-maps). -/
|
|
108
|
+
def jset (val : Json) (key : String) (entry : Json) : Json :=
|
|
109
|
+
match val with
|
|
110
|
+
| .obj _ => val.setObjVal! key entry
|
|
111
|
+
| other => other
|
|
112
|
+
|
|
113
|
+
def jnum (val : Int) : Json := Json.num (JsonNumber.fromInt val)
|
|
114
|
+
def jstr (val : String) : Json := Json.str val
|
|
115
|
+
def jbool (val : Bool) : Json := Json.bool val
|
|
116
|
+
def jlist (entries : List Json) : Json := Json.arr entries.toArray
|
|
117
|
+
def jmap (entries : List (String × Json)) : Json := Json.mkObj entries
|
|
118
|
+
|
|
119
|
+
/-- Render a number the same way in every port: 5.0 prints as 5. -/
|
|
120
|
+
def numstr (val : Float) : String :=
|
|
121
|
+
if val.isNaN || val.isInf then "null"
|
|
122
|
+
else
|
|
123
|
+
-- Lean prints a Float as "5.000000"; trim to the shortest exact form,
|
|
124
|
+
-- so that 5.0 renders as 5 in every port.
|
|
125
|
+
let text := toString val
|
|
126
|
+
if text.contains '.' then
|
|
127
|
+
let trimmed := text.dropRightWhile (· == '0')
|
|
128
|
+
if trimmed.endsWith "." then trimmed.dropRight 1 else trimmed
|
|
129
|
+
else text
|
|
130
|
+
|
|
131
|
+
/-- Render a string as a JSON string literal. -/
|
|
132
|
+
def quote (val : String) : String :=
|
|
133
|
+
let escape (ch : Char) : String :=
|
|
134
|
+
if ch == '"' then "\\\""
|
|
135
|
+
else if ch == '\\' then "\\\\"
|
|
136
|
+
else if ch == '\n' then "\\n"
|
|
137
|
+
else if ch == '\r' then "\\r"
|
|
138
|
+
else if ch == '\t' then "\\t"
|
|
139
|
+
else if ch.toNat < 0x20 then "\\u00" ++ (Nat.toDigits 16 ch.toNat).asString
|
|
140
|
+
else ch.toString
|
|
141
|
+
"\"" ++ (val.toList.map escape |> String.join) ++ "\""
|
|
142
|
+
|
|
143
|
+
/-- Compact JSON text with map keys sorted, identical in every port. -/
|
|
144
|
+
partial def jsonstr (val : Val) : String :=
|
|
145
|
+
match val with
|
|
146
|
+
| none => "undefined"
|
|
147
|
+
| some .null => "null"
|
|
148
|
+
| some (.bool flag) => if flag then "true" else "false"
|
|
149
|
+
| some (.num n) => numstr n.toFloat
|
|
150
|
+
| some (.str text) => quote text
|
|
151
|
+
| some (.arr entries) =>
|
|
152
|
+
"[" ++ String.intercalate "," (entries.toList.map (fun e => jsonstr (some e))) ++ "]"
|
|
153
|
+
| some (.obj kvs) =>
|
|
154
|
+
-- Lean's object is key-ordered already, which is what omni wants.
|
|
155
|
+
let pairs := kvs.toArray.toList.map (fun kv => quote kv.1 ++ ":" ++ jsonstr (some kv.2))
|
|
156
|
+
"{" ++ String.intercalate "," pairs ++ "}"
|
|
157
|
+
|
|
158
|
+
/-- Strings verbatim, everything else as compact JSON. -/
|
|
159
|
+
def stringify (val : Val) : String :=
|
|
160
|
+
match asstr val with
|
|
161
|
+
| some text => text
|
|
162
|
+
| none => jsonstr val
|
|
163
|
+
|
|
164
|
+
/-- Render a path as a dotted string. -/
|
|
165
|
+
def pathify (path : List String) : String := String.intercalate "." path
|
|
166
|
+
|
|
167
|
+
/-- Deep copy a JSON value (Lean values are immutable). -/
|
|
168
|
+
def clone (val : Val) : Val := val
|
|
169
|
+
|
|
170
|
+
/-- Deep structural equality: numbers by value, bools never numbers. -/
|
|
171
|
+
partial def deepequal (a b : Val) : Bool :=
|
|
172
|
+
match a, b with
|
|
173
|
+
| none, none => true
|
|
174
|
+
| some x, some y =>
|
|
175
|
+
match x, y with
|
|
176
|
+
| .null, .null => true
|
|
177
|
+
| .bool p, .bool q => p == q
|
|
178
|
+
| .num p, .num q => p.toFloat == q.toFloat
|
|
179
|
+
| .str p, .str q => p == q
|
|
180
|
+
| .arr p, .arr q =>
|
|
181
|
+
p.size == q.size &&
|
|
182
|
+
(List.range p.size).all (fun index => deepequal (some p[index]!) (some q[index]!))
|
|
183
|
+
| .obj p, .obj q =>
|
|
184
|
+
let pl := p.toArray.toList
|
|
185
|
+
let ql := q.toArray.toList
|
|
186
|
+
pl.length == ql.length &&
|
|
187
|
+
pl.all (fun kv =>
|
|
188
|
+
match (Json.obj q).getObjVal? kv.1 with
|
|
189
|
+
| .ok other => deepequal (some kv.2) (some other)
|
|
190
|
+
| .error _ => false)
|
|
191
|
+
| _, _ => false
|
|
192
|
+
| _, _ => false
|
|
193
|
+
|
|
194
|
+
/-- Read a value by path. Returns `none` (absent) when a step is missing. -/
|
|
195
|
+
def getpath (val : Val) (path : List String) : Val :=
|
|
196
|
+
path.foldl
|
|
197
|
+
(fun current part =>
|
|
198
|
+
match current with
|
|
199
|
+
| some (.arr entries) =>
|
|
200
|
+
match part.toNat? with
|
|
201
|
+
| some index => if index < entries.size then some entries[index]! else none
|
|
202
|
+
| none => none
|
|
203
|
+
| some (.obj _) => jget current part
|
|
204
|
+
| _ => none)
|
|
205
|
+
val
|
|
206
|
+
|
|
207
|
+
/-- Depth-first walk: children first, then the containing node. -/
|
|
208
|
+
partial def walk (val : Json) (apply : Json → List String → Json)
|
|
209
|
+
(path : List String := []) : Json :=
|
|
210
|
+
let next :=
|
|
211
|
+
match val with
|
|
212
|
+
| .arr entries =>
|
|
213
|
+
Json.arr (entries.mapIdx (fun index entry => walk entry apply (path ++ [toString index])))
|
|
214
|
+
| .obj kvs =>
|
|
215
|
+
Json.mkObj (kvs.toArray.toList.map (fun kv => (kv.1, walk kv.2 apply (path ++ [kv.1]))))
|
|
216
|
+
| other => other
|
|
217
|
+
apply next path
|
|
218
|
+
|
|
219
|
+
/-- Nulls (and absent values) become NULLMARK. Always a fresh copy. -/
|
|
220
|
+
partial def fixjson (val : Val) (donull : Bool) : Val :=
|
|
221
|
+
if isnone val then
|
|
222
|
+
-- Canonical returns the value UNCHANGED when donull is false
|
|
223
|
+
-- (typescript/src/Runner.ts): absent stays absent and null stays null.
|
|
224
|
+
-- Answering `Json.null` for both collapsed two states the corpus
|
|
225
|
+
-- distinguishes - and the old return type, `Json`, could not say
|
|
226
|
+
-- "absent" at all. Same defect the other ports carried (#17, #23, #25,
|
|
227
|
+
-- #26, #27, #28, #32).
|
|
228
|
+
(if donull then some (jstr nullmark) else val)
|
|
229
|
+
else
|
|
230
|
+
match val with
|
|
231
|
+
| some (.arr entries) =>
|
|
232
|
+
some (Json.arr (entries.map (fun entry => (fixjson (some entry) donull).getD Json.null)))
|
|
233
|
+
| some (.obj kvs) =>
|
|
234
|
+
some (Json.mkObj (kvs.toArray.toList.map
|
|
235
|
+
(fun kv => (kv.1, (fixjson (some kv.2) donull).getD Json.null))))
|
|
236
|
+
| some other => some other
|
|
237
|
+
| none => none
|
|
238
|
+
|
|
239
|
+
/-- The JSON form of an error: always at least {name,message}. -/
|
|
240
|
+
def errify (message : String) : Json :=
|
|
241
|
+
jmap [("name", jstr "Error"), ("message", jstr message)]
|
|
242
|
+
|
|
243
|
+
/- ---- regex engine --------------------------------------------------
|
|
244
|
+
|
|
245
|
+
Omni specs match error messages with `/pattern/` expectations, so every
|
|
246
|
+
port needs a regex. Lean has none in its standard library, so omni carries
|
|
247
|
+
this engine, a direct port of the Rust one (rust/src/regex.rs). -/
|
|
248
|
+
|
|
249
|
+
inductive ClassItem : Type where
|
|
250
|
+
| char (ch : Char)
|
|
251
|
+
| range (low high : Char)
|
|
252
|
+
| digit (invert : Bool)
|
|
253
|
+
| word (invert : Bool)
|
|
254
|
+
| space (invert : Bool)
|
|
255
|
+
|
|
256
|
+
inductive Node : Type where
|
|
257
|
+
| char (ch : Char)
|
|
258
|
+
| any
|
|
259
|
+
| cls (items : List ClassItem) (negated : Bool)
|
|
260
|
+
| start
|
|
261
|
+
| stop
|
|
262
|
+
| seq (nodes : List Node)
|
|
263
|
+
| alt (branches : List Node)
|
|
264
|
+
| repeat (inner : Node) (min : Nat) (max : Option Nat) (greedy : Bool)
|
|
265
|
+
|
|
266
|
+
instance : Inhabited Node := ⟨.any⟩
|
|
267
|
+
instance : Inhabited ClassItem := ⟨.char 'x'⟩
|
|
268
|
+
|
|
269
|
+
private def escapeNode (escape : Char) : Node :=
|
|
270
|
+
match escape with
|
|
271
|
+
| 'd' => Node.cls [ClassItem.digit false] false
|
|
272
|
+
| 'D' => Node.cls [ClassItem.digit false] true
|
|
273
|
+
| 'w' => Node.cls [ClassItem.word false] false
|
|
274
|
+
| 'W' => Node.cls [ClassItem.word false] true
|
|
275
|
+
| 's' => Node.cls [ClassItem.space false] false
|
|
276
|
+
| 'S' => Node.cls [ClassItem.space false] true
|
|
277
|
+
| 'n' => Node.char '\n'
|
|
278
|
+
| 'r' => Node.char '\r'
|
|
279
|
+
| 't' => Node.char '\t'
|
|
280
|
+
| other => Node.char other
|
|
281
|
+
|
|
282
|
+
private partial def parseClass (chars : List Char) : Option (Node × List Char) :=
|
|
283
|
+
let (negated, rest) :=
|
|
284
|
+
match chars with
|
|
285
|
+
| '^' :: more => (true, more)
|
|
286
|
+
| _ => (false, chars)
|
|
287
|
+
let rec collect (acc : List ClassItem) (first : Bool) (left : List Char)
|
|
288
|
+
: Option (Node × List Char) :=
|
|
289
|
+
match left with
|
|
290
|
+
| [] => none
|
|
291
|
+
| ']' :: more => if first then collect (ClassItem.char ']' :: acc) false more
|
|
292
|
+
else some (Node.cls acc.reverse negated, more)
|
|
293
|
+
| '\\' :: escape :: more =>
|
|
294
|
+
let item :=
|
|
295
|
+
match escape with
|
|
296
|
+
| 'd' => ClassItem.digit false
|
|
297
|
+
| 'w' => ClassItem.word false
|
|
298
|
+
| 's' => ClassItem.space false
|
|
299
|
+
| 'n' => ClassItem.char '\n'
|
|
300
|
+
| 'r' => ClassItem.char '\r'
|
|
301
|
+
| 't' => ClassItem.char '\t'
|
|
302
|
+
| other => ClassItem.char other
|
|
303
|
+
collect (item :: acc) false more
|
|
304
|
+
| low :: '-' :: high :: more =>
|
|
305
|
+
if high == ']' then collect (ClassItem.char low :: acc) false ('-' :: high :: more)
|
|
306
|
+
else collect (ClassItem.range low high :: acc) false more
|
|
307
|
+
| ch :: more => collect (ClassItem.char ch :: acc) false more
|
|
308
|
+
collect [] true rest
|
|
309
|
+
|
|
310
|
+
private partial def parseCount (chars : List Char) : Option (Nat × Option Nat × List Char) :=
|
|
311
|
+
let digits := chars.takeWhile Char.isDigit
|
|
312
|
+
let rest := chars.dropWhile Char.isDigit
|
|
313
|
+
if digits.isEmpty then none
|
|
314
|
+
else
|
|
315
|
+
let low := digits.asString.toNat!
|
|
316
|
+
match rest with
|
|
317
|
+
| '}' :: more => some (low, some low, more)
|
|
318
|
+
| ',' :: more =>
|
|
319
|
+
let highdigits := more.takeWhile Char.isDigit
|
|
320
|
+
let afterhigh := more.dropWhile Char.isDigit
|
|
321
|
+
match afterhigh with
|
|
322
|
+
| '}' :: final =>
|
|
323
|
+
some (low, (if highdigits.isEmpty then none else some highdigits.asString.toNat!), final)
|
|
324
|
+
| _ => none
|
|
325
|
+
| _ => none
|
|
326
|
+
|
|
327
|
+
mutual
|
|
328
|
+
|
|
329
|
+
private partial def parseAlt (chars : List Char) : Option (Node × List Char) := do
|
|
330
|
+
let (first, rest) ← parseSeq chars
|
|
331
|
+
let rec branches (acc : List Node) (left : List Char) : Option (Node × List Char) :=
|
|
332
|
+
match left with
|
|
333
|
+
| '|' :: more => do
|
|
334
|
+
let (branch, rest) ← parseSeq more
|
|
335
|
+
branches (branch :: acc) rest
|
|
336
|
+
| _ => some (if acc.length == 1 then acc.head! else .alt acc.reverse, left)
|
|
337
|
+
branches [first] rest
|
|
338
|
+
|
|
339
|
+
private partial def parseSeq (chars : List Char) : Option (Node × List Char) :=
|
|
340
|
+
let rec collect (acc : List Node) (left : List Char) : Option (Node × List Char) :=
|
|
341
|
+
match left with
|
|
342
|
+
| [] => some (.seq acc.reverse, [])
|
|
343
|
+
| '|' :: _ => some (.seq acc.reverse, left)
|
|
344
|
+
| ')' :: _ => some (.seq acc.reverse, left)
|
|
345
|
+
| _ => do
|
|
346
|
+
let (atom, rest) ← parseAtom left
|
|
347
|
+
let (node, more) ← parseRepeat atom rest
|
|
348
|
+
collect (node :: acc) more
|
|
349
|
+
collect [] chars
|
|
350
|
+
|
|
351
|
+
private partial def parseRepeat (atom : Node) (chars : List Char) : Option (Node × List Char) :=
|
|
352
|
+
let build (min : Nat) (max : Option Nat) (rest : List Char) : Option (Node × List Char) :=
|
|
353
|
+
match rest with
|
|
354
|
+
| '?' :: more => some (.repeat atom min max false, more)
|
|
355
|
+
| _ => some (.repeat atom min max true, rest)
|
|
356
|
+
match chars with
|
|
357
|
+
| '*' :: rest => build 0 none rest
|
|
358
|
+
| '+' :: rest => build 1 none rest
|
|
359
|
+
| '?' :: rest => build 0 (some 1) rest
|
|
360
|
+
| '{' :: rest =>
|
|
361
|
+
match parseCount rest with
|
|
362
|
+
| some (min, max, more) => build min max more
|
|
363
|
+
| none => some (atom, chars)
|
|
364
|
+
| _ => some (atom, chars)
|
|
365
|
+
|
|
366
|
+
private partial def parseAtom (chars : List Char) : Option (Node × List Char) :=
|
|
367
|
+
match chars with
|
|
368
|
+
| [] => none
|
|
369
|
+
| '(' :: rest => do
|
|
370
|
+
let inner := match rest with
|
|
371
|
+
| '?' :: ':' :: more => more
|
|
372
|
+
| _ => rest
|
|
373
|
+
let (node, more) ← parseAlt inner
|
|
374
|
+
match more with
|
|
375
|
+
| ')' :: final => some (node, final)
|
|
376
|
+
| _ => none
|
|
377
|
+
| '[' :: rest => parseClass rest
|
|
378
|
+
| '.' :: rest => some (.any, rest)
|
|
379
|
+
| '^' :: rest => some (.start, rest)
|
|
380
|
+
| '$' :: rest => some (.stop, rest)
|
|
381
|
+
| '\\' :: escape :: rest => some (escapeNode escape, rest)
|
|
382
|
+
| ch :: rest => some (.char ch, rest)
|
|
383
|
+
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
private def isWordChar (ch : Char) : Bool := ch.isAlphanum || ch == '_'
|
|
387
|
+
|
|
388
|
+
private def classMatch (items : List ClassItem) (negated : Bool) (ch : Char) : Bool :=
|
|
389
|
+
let hit := items.any fun item =>
|
|
390
|
+
match item with
|
|
391
|
+
| .char want => ch == want
|
|
392
|
+
| .range low high => low ≤ ch && ch ≤ high
|
|
393
|
+
| .digit invert => ch.isDigit != invert
|
|
394
|
+
| .word invert => isWordChar ch != invert
|
|
395
|
+
| .space invert => ch.isWhitespace != invert
|
|
396
|
+
hit != negated
|
|
397
|
+
|
|
398
|
+
mutual
|
|
399
|
+
|
|
400
|
+
private partial def matchNode (node : Node) (text : Array Char) (pos : Nat)
|
|
401
|
+
(cont : Nat → Bool) : Bool :=
|
|
402
|
+
match node with
|
|
403
|
+
| .char want => pos < text.size && text[pos]! == want && cont (pos + 1)
|
|
404
|
+
| .any => pos < text.size && cont (pos + 1)
|
|
405
|
+
| .cls items negated =>
|
|
406
|
+
pos < text.size && classMatch items negated text[pos]! && cont (pos + 1)
|
|
407
|
+
| .start => pos == 0 && cont pos
|
|
408
|
+
| .stop => pos == text.size && cont pos
|
|
409
|
+
| .seq nodes => matchSeq nodes text pos cont
|
|
410
|
+
| .alt branches => branches.any (fun branch => matchNode branch text pos cont)
|
|
411
|
+
| .repeat inner min max greedy => matchRepeat inner min max greedy 0 text pos cont
|
|
412
|
+
|
|
413
|
+
private partial def matchSeq (nodes : List Node) (text : Array Char) (pos : Nat)
|
|
414
|
+
(cont : Nat → Bool) : Bool :=
|
|
415
|
+
match nodes with
|
|
416
|
+
| [] => cont pos
|
|
417
|
+
| head :: rest => matchNode head text pos (fun next => matchSeq rest text next cont)
|
|
418
|
+
|
|
419
|
+
private partial def matchRepeat (inner : Node) (min : Nat) (max : Option Nat) (greedy : Bool)
|
|
420
|
+
(count : Nat) (text : Array Char) (pos : Nat) (cont : Nat → Bool) : Bool :=
|
|
421
|
+
let canmore := match max with
|
|
422
|
+
| some limit => count < limit
|
|
423
|
+
| none => true
|
|
424
|
+
let takemore := fun _ =>
|
|
425
|
+
canmore && matchNode inner text pos (fun next =>
|
|
426
|
+
-- A zero-width repeat would loop forever.
|
|
427
|
+
next > pos && matchRepeat inner min max greedy (count + 1) text next cont)
|
|
428
|
+
let stopnow := fun _ => count >= min && cont pos
|
|
429
|
+
if greedy then takemore () || stopnow () else stopnow () || takemore ()
|
|
430
|
+
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
/-- Is the pattern found anywhere in the text? -/
|
|
434
|
+
def regexFind (pattern text : String) : Bool :=
|
|
435
|
+
match parseAlt pattern.toList with
|
|
436
|
+
| some (root, []) =>
|
|
437
|
+
let chars := text.toList.toArray
|
|
438
|
+
(List.range (chars.size + 1)).any (fun start => matchNode root chars start (fun _ => true))
|
|
439
|
+
| _ => false
|
|
440
|
+
|
|
441
|
+
/-- Match one leaf: /regex/ or case-insensitive substring for strings. -/
|
|
442
|
+
def matchval (check base : Val) : Bool :=
|
|
443
|
+
if deepequal check base then true
|
|
444
|
+
else
|
|
445
|
+
let want :=
|
|
446
|
+
match asstr check with
|
|
447
|
+
| some text => if text == undefmark || text == nullmark then none else check
|
|
448
|
+
| none => check
|
|
449
|
+
if isnone want then
|
|
450
|
+
isnone base || asstr base == some nullmark
|
|
451
|
+
else
|
|
452
|
+
match asstr want with
|
|
453
|
+
-- An empty want is not a wildcard: the empty string is a substring of
|
|
454
|
+
-- everything, so `match:{out:""}` (or `err:""`) would accept any value.
|
|
455
|
+
| some "" => asstr base == some ""
|
|
456
|
+
| some text =>
|
|
457
|
+
let basestr := stringify base
|
|
458
|
+
if text.length > 2 && text.startsWith "/" && text.endsWith "/" then
|
|
459
|
+
-- Through the char list rather than `dropRight`: that one is
|
|
460
|
+
-- deprecated by v4.32 and now answers a `String.Slice`, which is a
|
|
461
|
+
-- different type, so a consumer on a newer toolchain cannot build
|
|
462
|
+
-- this. `String.mk` and `List.dropLast` are stable across both.
|
|
463
|
+
regexFind (String.mk (text.toList.drop 1 |>.dropLast)) basestr
|
|
464
|
+
else
|
|
465
|
+
(basestr.toLower.splitOn text.toLower).length > 1
|
|
466
|
+
| none => deepequal want base
|
|
467
|
+
|
|
468
|
+
/-- Convert NULLMARK sentinels back into real nulls. -/
|
|
469
|
+
def nullmodifier (val : Val) : Val :=
|
|
470
|
+
match asstr val with
|
|
471
|
+
| some text =>
|
|
472
|
+
if text == nullmark then some Json.null
|
|
473
|
+
else some (jstr (String.intercalate "null" (text.splitOn nullmark)))
|
|
474
|
+
| none => val
|
|
475
|
+
|
|
476
|
+
/- ---- runner --------------------------------------------------------- -/
|
|
477
|
+
|
|
478
|
+
/-- The newest spec format version this runner understands. A spec with no
|
|
479
|
+
`OMNI` block is version 0: the original, lenient format, frozen forever.
|
|
480
|
+
Version 1 turns on strict entry validation (see `checkentry`). -/
|
|
481
|
+
def SPECVERSION : Nat := 1
|
|
482
|
+
|
|
483
|
+
/-- Capability strings this runner supports beyond the version baseline. A
|
|
484
|
+
spec's `OMNI.requires` list is checked against this: an unknown capability
|
|
485
|
+
refuses the spec loudly at load time, instead of a lagging port silently
|
|
486
|
+
mis-running it. (Empty today; future format features mint a string here.) -/
|
|
487
|
+
def CAPABILITIES : List String := []
|
|
488
|
+
|
|
489
|
+
/-- The complete set of fields an entry may carry. Under version 1 anything
|
|
490
|
+
else is an error: an unrecognised key is almost always a typo'd assertion,
|
|
491
|
+
and a typo'd assertion is a test that silently stopped testing. -/
|
|
492
|
+
private def ENTRYFIELDS : List String :=
|
|
493
|
+
["in", "args", "ctx", "out", "err", "match", "client", "id", "doc"]
|
|
494
|
+
|
|
495
|
+
-- A capability that is not a string, or not in CAPABILITIES, is one this
|
|
496
|
+
-- runner does not recognise.
|
|
497
|
+
private def unsupportedcap (cap : Json) : Bool :=
|
|
498
|
+
match asstr (some cap) with
|
|
499
|
+
| some capstr => !(CAPABILITIES.contains capstr)
|
|
500
|
+
| none => true
|
|
501
|
+
|
|
502
|
+
/-- The `requires` half of `resolveversion`: a present-but-non-list is
|
|
503
|
+
malformed, and any listed capability this runner does not recognise
|
|
504
|
+
refuses the whole spec. An absent `requires` is fine - it only narrows,
|
|
505
|
+
never widens, what a version already permits. -/
|
|
506
|
+
private def checkrequires (requires : Val) (version : Nat) : Except String Nat :=
|
|
507
|
+
if isabsent requires then
|
|
508
|
+
pure version
|
|
509
|
+
else
|
|
510
|
+
match aslist requires with
|
|
511
|
+
| none => throw "omni: malformed OMNI requires list"
|
|
512
|
+
| some caps =>
|
|
513
|
+
match caps.toList.find? unsupportedcap with
|
|
514
|
+
| some badcap =>
|
|
515
|
+
throw s!"omni: spec requires unsupported capability: {stringify (some badcap)}"
|
|
516
|
+
| none => pure version
|
|
517
|
+
|
|
518
|
+
/-- Read the spec's format version from its optional top-level `OMNI`
|
|
519
|
+
block, and refuse a spec this runner cannot faithfully run: a version
|
|
520
|
+
newer than `SPECVERSION`, or a required capability not in `CAPABILITIES`.
|
|
521
|
+
These are runner refusals, never candidates for an `err` expectation.
|
|
522
|
+
|
|
523
|
+
Only a genuinely absent `OMNI` key is legacy; a present null (or anything
|
|
524
|
+
else that is not a map) is malformed - the presence/definedness
|
|
525
|
+
distinction the sentinel system exists to preserve applies to the
|
|
526
|
+
runner's own validation too. -/
|
|
527
|
+
def resolveversion (alltests : Json) : Except String Nat :=
|
|
528
|
+
-- Not `meta`: that became a reserved token by Lean v4.32, so the binding
|
|
529
|
+
-- fails to parse on a newer toolchain than this file pins.
|
|
530
|
+
let omniblock := jget (some alltests) "OMNI"
|
|
531
|
+
if isabsent omniblock then
|
|
532
|
+
pure 0
|
|
533
|
+
else
|
|
534
|
+
match omniblock with
|
|
535
|
+
| some (.obj _) =>
|
|
536
|
+
match asnum (jget omniblock "version") with
|
|
537
|
+
| some num =>
|
|
538
|
+
if num != num.floor then
|
|
539
|
+
throw "omni: malformed OMNI version block"
|
|
540
|
+
else if num < 0.0 || SPECVERSION.toFloat < num then
|
|
541
|
+
throw s!"omni: unsupported spec version: {numstr num}"
|
|
542
|
+
else
|
|
543
|
+
checkrequires (jget omniblock "requires") num.toUInt64.toNat
|
|
544
|
+
| none => throw "omni: malformed OMNI version block"
|
|
545
|
+
| _ => throw "omni: malformed OMNI version block"
|
|
546
|
+
|
|
547
|
+
/-- The function under test. Arguments arrive as `Val` - JSON values, or
|
|
548
|
+
absence - and failure is reported as `Except.error message`.
|
|
549
|
+
|
|
550
|
+
`Val`, not `Json`, on both sides. An entry that supplies no `in` means the
|
|
551
|
+
subject is called with NO argument, which the corpus distinguishes from one
|
|
552
|
+
called with null: struct's `minor/typify` has both `{in: null}` and `{}`
|
|
553
|
+
(register 4.12). And a subject that legitimately returns nothing needs to be
|
|
554
|
+
able to say so under `{null: false}`. -/
|
|
555
|
+
abbrev Subject : Type := List Val → Except String Val
|
|
556
|
+
|
|
557
|
+
/-- A subject that may REPLACE its arguments, which `match.args` can then
|
|
558
|
+
assert on. `minor/setpath` is the case in point: eight of its nine entries
|
|
559
|
+
assert that the store was rewritten in place, and `merge/integrity` all six.
|
|
560
|
+
|
|
561
|
+
Lean is pure, so a consumer's nodes are its own - struct/lean keeps them in a
|
|
562
|
+
heap threaded through `SIO` - and nothing it does is visible through this
|
|
563
|
+
runner's argument list. Returning the arguments alongside the result is the
|
|
564
|
+
only channel there is.
|
|
565
|
+
|
|
566
|
+
Separate from `Subject` rather than replacing it: a signature change would
|
|
567
|
+
break every consumer for a capability most subjects do not need. omni-rust,
|
|
568
|
+
-cpp, -ocaml, -elixir, -haskell, -clojure, -scala and -swift carry the same
|
|
569
|
+
pair, for the same reason. -/
|
|
570
|
+
abbrev SubjectArgs : Type := List Val → Except String (List Val × Val)
|
|
571
|
+
|
|
572
|
+
/-- Run-time options for a set of test entries. -/
|
|
573
|
+
structure Flags where
|
|
574
|
+
null : Bool := true
|
|
575
|
+
name : Option String := none
|
|
576
|
+
|
|
577
|
+
def Flags.nonull : Flags := { null := false }
|
|
578
|
+
|
|
579
|
+
/-- The host of the system under test. Every hook is optional.
|
|
580
|
+
|
|
581
|
+
A provider is recursive (a client hook yields another provider), so it is
|
|
582
|
+
an inductive rather than a structure. -/
|
|
583
|
+
inductive Provider : Type where
|
|
584
|
+
| mk (subject : Option (String → Option Subject))
|
|
585
|
+
(client : Option (Json → Provider))
|
|
586
|
+
(contextify : Option (Json → Json))
|
|
587
|
+
-- Build the `match.err` base from the failure, REPLACING `errify`.
|
|
588
|
+
--
|
|
589
|
+
-- Lean reaches this point with a message and nothing else, so a
|
|
590
|
+
-- library whose errors carry a code has no other way to put one in
|
|
591
|
+
-- the base. The hook receives that message and returns the base,
|
|
592
|
+
-- letting a spec assert `match: {err: {code: "x"}}` instead of
|
|
593
|
+
-- pattern-matching prose.
|
|
594
|
+
(errify : Option (String → Json))
|
|
595
|
+
|
|
596
|
+
def Provider.subject : Provider → Option (String → Option Subject)
|
|
597
|
+
| .mk found _ _ _ => found
|
|
598
|
+
|
|
599
|
+
def Provider.client : Provider → Option (Json → Provider)
|
|
600
|
+
| .mk _ found _ _ => found
|
|
601
|
+
|
|
602
|
+
def Provider.contextify : Provider → Option (Json → Json)
|
|
603
|
+
| .mk _ _ found _ => found
|
|
604
|
+
|
|
605
|
+
def Provider.errify : Provider → Option (String → Json)
|
|
606
|
+
| .mk _ _ _ found => found
|
|
607
|
+
|
|
608
|
+
/-- Build a provider from the hooks a host supplies. -/
|
|
609
|
+
def provider (subject : Option (String → Option Subject) := none)
|
|
610
|
+
(client : Option (Json → Provider) := none)
|
|
611
|
+
(contextify : Option (Json → Json) := none)
|
|
612
|
+
(errify : Option (String → Json) := none) : Provider :=
|
|
613
|
+
.mk subject client contextify errify
|
|
614
|
+
|
|
615
|
+
/-- A provider with no hooks. -/
|
|
616
|
+
def emptyProvider : Provider := provider
|
|
617
|
+
|
|
618
|
+
instance : Inhabited Provider := ⟨emptyProvider⟩
|
|
619
|
+
|
|
620
|
+
/-- Find `primary.<name>`, then `<name>`, then the whole spec. -/
|
|
621
|
+
def resolvespec (name : String) (alltests : Json) : Json :=
|
|
622
|
+
if name.isEmpty then alltests
|
|
623
|
+
else
|
|
624
|
+
match jget (jget (some alltests) "primary") name with
|
|
625
|
+
| some found => found
|
|
626
|
+
| none =>
|
|
627
|
+
match jget (some alltests) name with
|
|
628
|
+
| some found => found
|
|
629
|
+
| none => alltests
|
|
630
|
+
|
|
631
|
+
-- The spec-defined part of an entry (drop runner bookkeeping).
|
|
632
|
+
private def entrysummary (entry : Json) : Json :=
|
|
633
|
+
match entry with
|
|
634
|
+
| .obj kvs =>
|
|
635
|
+
Json.mkObj ((kvs.toArray.toList.map (fun kv => (kv.1, kv.2))).filter
|
|
636
|
+
(fun kv => kv.1 != "res" && kv.1 != "thrown" && kv.1 != "ctx"))
|
|
637
|
+
| other => other
|
|
638
|
+
|
|
639
|
+
-- The label of one entry, for failure messages.
|
|
640
|
+
private def entryref (label : String) (index : Nat) (entry : Json) : String :=
|
|
641
|
+
let id := jget (some entry) "id"
|
|
642
|
+
let idpart := if isabsent id then "" else s! " ({stringify id})"
|
|
643
|
+
s!"{label}[{index}]{idpart}"
|
|
644
|
+
|
|
645
|
+
private def failure (label : String) (index : Nat) (entry : Json) (reason : String)
|
|
646
|
+
(expected actual : Option String) : String :=
|
|
647
|
+
let head := s!"omni: {entryref label index entry}: {reason}"
|
|
648
|
+
let head := match expected with
|
|
649
|
+
| some text => head ++ s!"\n expected: {text}"
|
|
650
|
+
| none => head
|
|
651
|
+
let head := match actual with
|
|
652
|
+
| some text => head ++ s!"\n actual: {text}"
|
|
653
|
+
| none => head
|
|
654
|
+
head ++ s!"\n entry: {stringify (some (entrysummary entry))}"
|
|
655
|
+
|
|
656
|
+
-- Strict entry validation, applied when the spec declares version 1 or
|
|
657
|
+
-- later. The lenient format converts each of these mistakes into a silent
|
|
658
|
+
-- pass or a dead field; here they fail with the entry named.
|
|
659
|
+
private def checkentry (label : String) (index : Nat) (entry : Json) : Except String Unit := do
|
|
660
|
+
if !ismap (some entry) then
|
|
661
|
+
throw (failure label index entry "entry is not a map" none none)
|
|
662
|
+
|
|
663
|
+
let entrykeys := (asmap (some entry)).getD []
|
|
664
|
+
match entrykeys.find? (fun kv => !(ENTRYFIELDS.contains kv.1)) with
|
|
665
|
+
| some badkv => throw (failure label index entry s!"unknown entry field: {badkv.1}" none none)
|
|
666
|
+
| none => pure ()
|
|
667
|
+
|
|
668
|
+
let argsources := (["in", "args", "ctx"].filter (fun key => jhas (some entry) key)).length
|
|
669
|
+
if 1 < argsources then
|
|
670
|
+
throw (failure label index entry "entry has more than one of in, args, ctx" none none)
|
|
671
|
+
|
|
672
|
+
-- `err` must be present AND non-null; `out` only needs to be present -
|
|
673
|
+
-- an authored `out: null` still collides with an `err` expectation.
|
|
674
|
+
let errpresent := !isnone (jget (some entry) "err")
|
|
675
|
+
let outpresent := jhas (some entry) "out"
|
|
676
|
+
if errpresent && outpresent then
|
|
677
|
+
throw (failure label index entry "entry has both err and out" none none)
|
|
678
|
+
|
|
679
|
+
let idval := jget (some entry) "id"
|
|
680
|
+
if !isabsent idval && (asstr idval).isNone then
|
|
681
|
+
throw (failure label index entry "entry id is not a string" none none)
|
|
682
|
+
else
|
|
683
|
+
pure ()
|
|
684
|
+
|
|
685
|
+
-- Validate a version-1 group up front, against the AUTHORED entries -
|
|
686
|
+
-- null-normalisation would otherwise rewrite an authored null (e.g.
|
|
687
|
+
-- `id: null`) into a sentinel string and hide it from validation. A
|
|
688
|
+
-- malformed spec is a spec error, not a test result, so it fails before
|
|
689
|
+
-- any subject runs.
|
|
690
|
+
private def checkset (label : String) (testspec : Json) (normalset : Array Json)
|
|
691
|
+
: Except String Unit := do
|
|
692
|
+
let origset :=
|
|
693
|
+
match aslist (jget (some testspec) "set") with
|
|
694
|
+
| some entries => entries
|
|
695
|
+
| none => normalset
|
|
696
|
+
|
|
697
|
+
let emptyflag := match jget (some testspec) "empty" with
|
|
698
|
+
| some (.bool true) => true
|
|
699
|
+
| _ => false
|
|
700
|
+
|
|
701
|
+
if origset.size == 0 && !emptyflag then
|
|
702
|
+
throw s!"omni: empty test set: {label}"
|
|
703
|
+
|
|
704
|
+
for index in List.range origset.size do
|
|
705
|
+
checkentry label index origset[index]!
|
|
706
|
+
|
|
707
|
+
-- Check that every leaf of `check` is present, and matches, in `base`.
|
|
708
|
+
private partial def matchcheck (label : String) (index : Nat) (entry check base : Json)
|
|
709
|
+
(path : List String) : Except String Unit := do
|
|
710
|
+
let place := if path.isEmpty then "<root>" else pathify path
|
|
711
|
+
match check with
|
|
712
|
+
| .arr entries =>
|
|
713
|
+
for idx in List.range entries.size do
|
|
714
|
+
matchcheck label index entry entries[idx]! base (path ++ [toString idx])
|
|
715
|
+
| .obj kvs =>
|
|
716
|
+
for kv in kvs.toArray.toList do
|
|
717
|
+
matchcheck label index entry kv.2 base (path ++ [kv.1])
|
|
718
|
+
| leaf =>
|
|
719
|
+
let baseval := getpath (some base) path
|
|
720
|
+
let leafval := some leaf
|
|
721
|
+
-- The sentinels are tested BEFORE the identity check below. Otherwise
|
|
722
|
+
-- a subject returning the literal string "__UNDEF__" satisfies an
|
|
723
|
+
-- assertion that the key is absent - two mutually exclusive states
|
|
724
|
+
-- passing one check. A sentinel that accepts its own literal is not a
|
|
725
|
+
-- sentinel. (NULLMARK still accepts NULLMARK: under the default null
|
|
726
|
+
-- flag a real null has already been normalised to it, so the two are
|
|
727
|
+
-- genuinely indistinguishable here - that one needs a raw-value
|
|
728
|
+
-- escape, not an ordering change.)
|
|
729
|
+
-- Explicitly absent: satisfied only by a genuinely missing key, never
|
|
730
|
+
-- by a present null (the distinction the sentinels exist to keep).
|
|
731
|
+
if asstr leafval == some undefmark then
|
|
732
|
+
if isabsent baseval then pure ()
|
|
733
|
+
else throw (failure label index entry s!"expected absent at {place}"
|
|
734
|
+
(some "absent") (some (stringify baseval)))
|
|
735
|
+
-- Explicitly null: satisfied only by a present null.
|
|
736
|
+
else if asstr leafval == some nullmark then
|
|
737
|
+
if isnull baseval || asstr baseval == some nullmark then pure ()
|
|
738
|
+
else throw (failure label index entry s!"expected null at {place}"
|
|
739
|
+
(some "null") (some (stringify baseval)))
|
|
740
|
+
-- Explicitly present: any present value, including null.
|
|
741
|
+
else if asstr leafval == some existsmark then
|
|
742
|
+
if !isabsent baseval then pure ()
|
|
743
|
+
else throw (failure label index entry s!"expected present at {place}"
|
|
744
|
+
(some "present") (some "absent"))
|
|
745
|
+
-- Identical values match. This sits below the sentinel branches on
|
|
746
|
+
-- purpose - see the note above.
|
|
747
|
+
else if deepequal leafval baseval then
|
|
748
|
+
pure ()
|
|
749
|
+
-- A concrete expectation never matches a missing key - a match leaf
|
|
750
|
+
-- against an absent value must fail, not substring-match "undefined".
|
|
751
|
+
else if isabsent baseval then
|
|
752
|
+
throw (failure label index entry s!"match failed at {place}"
|
|
753
|
+
(some (stringify leafval)) (some "absent"))
|
|
754
|
+
else if matchval leafval baseval then
|
|
755
|
+
pure ()
|
|
756
|
+
else
|
|
757
|
+
throw (failure label index entry s!"match failed at {place}"
|
|
758
|
+
(some (stringify leafval)) (some (stringify baseval)))
|
|
759
|
+
|
|
760
|
+
private def checkresult (label : String) (index : Nat) (entry : Json) (args : List Val)
|
|
761
|
+
(res : Val) : Except String Unit := do
|
|
762
|
+
let entryerr := jget (some entry) "err"
|
|
763
|
+
|
|
764
|
+
if !isnone entryerr then
|
|
765
|
+
throw (failure label index entry "expected error did not occur"
|
|
766
|
+
(some (stringify entryerr)) (some (stringify res)))
|
|
767
|
+
|
|
768
|
+
let check := jget (some entry) "match"
|
|
769
|
+
let matched := !isnone check
|
|
770
|
+
|
|
771
|
+
if let some checkval := check then
|
|
772
|
+
let base := jmap [
|
|
773
|
+
("in", (jget (some entry) "in").getD Json.null),
|
|
774
|
+
-- A match block reads arguments as JSON, and an absent one renders as
|
|
775
|
+
-- null there the way canonical's JSON round-trip does.
|
|
776
|
+
("args", jlist (args.map (fun arg => arg.getD Json.null))),
|
|
777
|
+
("out", (jget (some entry) "res").getD Json.null),
|
|
778
|
+
("ctx", (jget (some entry) "ctx").getD Json.null)]
|
|
779
|
+
matchcheck label index entry checkval base []
|
|
780
|
+
|
|
781
|
+
let out := jget (some entry) "out"
|
|
782
|
+
|
|
783
|
+
if deepequal res out then
|
|
784
|
+
pure ()
|
|
785
|
+
-- NOTE: a match with no explicit out is a complete check on its own.
|
|
786
|
+
else if matched && (isnone out || asstr out == some nullmark) then
|
|
787
|
+
pure ()
|
|
788
|
+
else
|
|
789
|
+
throw (failure label index entry "result mismatch"
|
|
790
|
+
(some (stringify out)) (some (stringify res)))
|
|
791
|
+
|
|
792
|
+
/-- The error base a `match.err` sees: the provider's own, when it has one. -/
|
|
793
|
+
def errbase (message : String) (prov : Provider) : Json :=
|
|
794
|
+
match prov.errify with
|
|
795
|
+
| some hook => hook message
|
|
796
|
+
| none => errify message
|
|
797
|
+
|
|
798
|
+
private def handleerror (label : String) (index : Nat) (entry : Json) (message : String)
|
|
799
|
+
(prov : Provider) : Except String Unit := do
|
|
800
|
+
let entryerr := jget (some entry) "err"
|
|
801
|
+
|
|
802
|
+
if isnone entryerr then
|
|
803
|
+
throw (failure label index entry "unexpected error" none (some message))
|
|
804
|
+
|
|
805
|
+
let istrue := match entryerr with
|
|
806
|
+
| some (.bool true) => true
|
|
807
|
+
| _ => false
|
|
808
|
+
|
|
809
|
+
if istrue || matchval entryerr (some (jstr message)) then
|
|
810
|
+
if let some checkval := jget (some entry) "match" then
|
|
811
|
+
let base := jmap [
|
|
812
|
+
("in", (jget (some entry) "in").getD Json.null),
|
|
813
|
+
("out", (jget (some entry) "res").getD Json.null),
|
|
814
|
+
("ctx", (jget (some entry) "ctx").getD Json.null),
|
|
815
|
+
("err", errbase message prov)]
|
|
816
|
+
matchcheck label index entry checkval base []
|
|
817
|
+
pure ()
|
|
818
|
+
else
|
|
819
|
+
throw (failure label index entry "error mismatch"
|
|
820
|
+
(some (stringify entryerr)) (some message))
|
|
821
|
+
|
|
822
|
+
/-- What a runner returns for one named spec section. -/
|
|
823
|
+
structure RunPack where
|
|
824
|
+
spec : Json
|
|
825
|
+
subject : Option Subject
|
|
826
|
+
client : Provider
|
|
827
|
+
clients : List (String × Provider)
|
|
828
|
+
name : String
|
|
829
|
+
-- The spec's resolved format version (see `resolveversion`). 0 for a
|
|
830
|
+
-- legacy spec with no `OMNI` block, in which case strict validation
|
|
831
|
+
-- (`checkset`/`checkentry`) never runs.
|
|
832
|
+
specversion : Nat
|
|
833
|
+
|
|
834
|
+
/-- A named group of the resolved spec. -/
|
|
835
|
+
def RunPack.set (pack : RunPack) (setname : String) : Json :=
|
|
836
|
+
(jget (some pack.spec) setname).getD Json.null
|
|
837
|
+
|
|
838
|
+
/-- Run one set of test entries with flags, for a subject that may replace
|
|
839
|
+
its arguments. See `SubjectArgs`. -/
|
|
840
|
+
partial def RunPack.drive (pack : RunPack) (testspec : Json) (flags : Flags)
|
|
841
|
+
(testsubject : Option Subject) (argsubject : Option SubjectArgs)
|
|
842
|
+
: Except String Unit := do
|
|
843
|
+
let label := flags.name.getD (if pack.name.isEmpty then "set" else pack.name)
|
|
844
|
+
|
|
845
|
+
let usesubject ← match argsubject with
|
|
846
|
+
| some _ => pure none
|
|
847
|
+
| none =>
|
|
848
|
+
match testsubject.orElse (fun _ => pack.subject) with
|
|
849
|
+
| some found => pure (some found)
|
|
850
|
+
| none => throw s!"omni: no test subject for: {label}"
|
|
851
|
+
|
|
852
|
+
let testspecmap := fixjson (some testspec) flags.null
|
|
853
|
+
|
|
854
|
+
let testset ← match aslist (jget testspecmap "set") with
|
|
855
|
+
| some entries => pure entries
|
|
856
|
+
| none => throw s!"omni: test spec has no set: {label}"
|
|
857
|
+
|
|
858
|
+
-- Validate the AUTHORED group (testspec, not testspecmap) once, before
|
|
859
|
+
-- any subject runs - see checkset for why this must read the
|
|
860
|
+
-- pre-normalisation entries.
|
|
861
|
+
if 1 <= pack.specversion then
|
|
862
|
+
checkset label testspec testset
|
|
863
|
+
|
|
864
|
+
for index in List.range testset.size do
|
|
865
|
+
let rawentry := testset[index]!
|
|
866
|
+
|
|
867
|
+
if !ismap (some rawentry) then
|
|
868
|
+
throw s!"omni: {label}[{index}]: entry is not a map"
|
|
869
|
+
|
|
870
|
+
-- An entry with no `out` expects a null (or absent) result.
|
|
871
|
+
let entry :=
|
|
872
|
+
if flags.null && isnone (jget (some rawentry) "out") then
|
|
873
|
+
jset rawentry "out" (jstr nullmark)
|
|
874
|
+
else rawentry
|
|
875
|
+
|
|
876
|
+
let entrysubject ←
|
|
877
|
+
match usesubject, asstr (jget (some entry) "client") with
|
|
878
|
+
| none, _ => pure none
|
|
879
|
+
| some found, some clientname =>
|
|
880
|
+
match pack.clients.lookup clientname with
|
|
881
|
+
| none => throw s!"omni: unknown client: {clientname}"
|
|
882
|
+
| some client =>
|
|
883
|
+
match client.subject with
|
|
884
|
+
| some resolve => pure (some ((resolve pack.name).getD found))
|
|
885
|
+
| none => pure (some found)
|
|
886
|
+
| some found, none => pure (some found)
|
|
887
|
+
|
|
888
|
+
-- Build the argument list: `ctx`, `args`, or `in`.
|
|
889
|
+
let hasctx := jhas (some entry) "ctx"
|
|
890
|
+
let hasargs := jhas (some entry) "args"
|
|
891
|
+
|
|
892
|
+
-- `Val`, so an entry with no `in` reaches the subject as ABSENT rather
|
|
893
|
+
-- than as null - the distinction `minor/typify` asserts (register 4.12).
|
|
894
|
+
let args0 : List Val :=
|
|
895
|
+
if hasctx then [jget (some entry) "ctx"]
|
|
896
|
+
else if hasargs then
|
|
897
|
+
match aslist (jget (some entry) "args") with
|
|
898
|
+
| some list => list.toList.map some
|
|
899
|
+
| none => [jget (some entry) "args"]
|
|
900
|
+
else [jget (some entry) "in"]
|
|
901
|
+
|
|
902
|
+
let (args, entry) :=
|
|
903
|
+
if (hasctx || hasargs) && !args0.isEmpty && ismap args0.head! then
|
|
904
|
+
let head := args0.head!.getD Json.null
|
|
905
|
+
let contextified := match pack.client.contextify with
|
|
906
|
+
| some contextify => contextify head
|
|
907
|
+
| none => head
|
|
908
|
+
-- Canonical attaches the resolved client onto ctx/args[0]
|
|
909
|
+
-- (testpack.client) so a context subject can tell it was invoked
|
|
910
|
+
-- through one. Json has no case for a live Provider reference,
|
|
911
|
+
-- and the resolved client is never absent (it defaults to the
|
|
912
|
+
-- root provider), so a boolean marker carries the same
|
|
913
|
+
-- observable meaning the corpus checks for - presence, never
|
|
914
|
+
-- identity.
|
|
915
|
+
let first := jset contextified "client" (jbool true)
|
|
916
|
+
(some first :: args0.tail!, jset entry "ctx" first)
|
|
917
|
+
else (args0, entry)
|
|
918
|
+
|
|
919
|
+
-- An args-subject returns its arguments alongside the result, so
|
|
920
|
+
-- `match.args` sees what the subject actually did with them.
|
|
921
|
+
let outcome : Except String (List Val × Val) :=
|
|
922
|
+
match argsubject, entrysubject with
|
|
923
|
+
| some call, _ => call args
|
|
924
|
+
| none, some call => (call args).map (fun res => (args, res))
|
|
925
|
+
| none, none => throw s!"omni: no test subject for: {label}"
|
|
926
|
+
|
|
927
|
+
match outcome with
|
|
928
|
+
| .ok (callargs, rawres) =>
|
|
929
|
+
let res := fixjson rawres flags.null
|
|
930
|
+
let done := match res with
|
|
931
|
+
| some value => jset entry "res" value
|
|
932
|
+
| none => entry
|
|
933
|
+
checkresult label index done callargs res
|
|
934
|
+
| .error message => handleerror label index entry message pack.client
|
|
935
|
+
|
|
936
|
+
/-- Run one set of test entries with flags. Returns the failure message. -/
|
|
937
|
+
def RunPack.runsetflags (pack : RunPack) (testspec : Json) (flags : Flags)
|
|
938
|
+
(testsubject : Option Subject) : Except String Unit :=
|
|
939
|
+
pack.drive testspec flags testsubject none
|
|
940
|
+
|
|
941
|
+
/-- Everything `runsetflags` does, for a subject that may replace its
|
|
942
|
+
arguments. See `SubjectArgs`. -/
|
|
943
|
+
def RunPack.runsetflagsargs (pack : RunPack) (testspec : Json) (flags : Flags)
|
|
944
|
+
(argsubject : SubjectArgs) : Except String Unit :=
|
|
945
|
+
pack.drive testspec flags none (some argsubject)
|
|
946
|
+
|
|
947
|
+
/-- Run one set of test entries. Returns the failure message. -/
|
|
948
|
+
def RunPack.runset (pack : RunPack) (testspec : Json) (testsubject : Option Subject)
|
|
949
|
+
: Except String Unit :=
|
|
950
|
+
pack.runsetflags testspec {} testsubject
|
|
951
|
+
|
|
952
|
+
/-- Make a runner for a spec value and a provider. Resolves the spec's
|
|
953
|
+
format version first (see `resolveversion`) and refuses to build a runner
|
|
954
|
+
at all for a version this port cannot faithfully run - fail fast, before
|
|
955
|
+
any group or client is resolved. -/
|
|
956
|
+
def makeRunnerSpec (alltests : Json) (provider : Provider) (name : String)
|
|
957
|
+
: Except String RunPack := do
|
|
958
|
+
let specversion ← resolveversion alltests
|
|
959
|
+
|
|
960
|
+
let spec := resolvespec name alltests
|
|
961
|
+
|
|
962
|
+
-- A spec may define clients that a given test run never references.
|
|
963
|
+
let clients :=
|
|
964
|
+
match asmap (jget (jget (some spec) "DEF") "client"), provider.client with
|
|
965
|
+
| some entries, some clientmaker =>
|
|
966
|
+
entries.map (fun kv =>
|
|
967
|
+
let copts := (jget (jget (some kv.2) "test") "options").getD (jmap [])
|
|
968
|
+
(kv.1, clientmaker copts))
|
|
969
|
+
| _, _ => []
|
|
970
|
+
|
|
971
|
+
let subject :=
|
|
972
|
+
if name.isEmpty then none
|
|
973
|
+
else match provider.subject with
|
|
974
|
+
| some resolve => resolve name
|
|
975
|
+
| none => none
|
|
976
|
+
|
|
977
|
+
pure { spec := spec, subject := subject, client := provider, clients := clients,
|
|
978
|
+
name := name, specversion := specversion }
|
|
979
|
+
|
|
980
|
+
/-- Load a spec: a path to a JSON file. -/
|
|
981
|
+
def loadspec (path : String) : IO Json := do
|
|
982
|
+
let text ← IO.FS.readFile path
|
|
983
|
+
match Json.parse text with
|
|
984
|
+
| .ok value => pure value
|
|
985
|
+
| .error message => throw (IO.userError s!"omni: cannot parse spec: {path}: {message}")
|
|
986
|
+
|
|
987
|
+
/-- Make a runner for a spec file path and a provider. A version refusal
|
|
988
|
+
from `makeRunnerSpec` surfaces the same way a spec-load failure already
|
|
989
|
+
does: a thrown `IO.userError`, so a caller can guard one `makeRunner` call
|
|
990
|
+
regardless of which stage failed. -/
|
|
991
|
+
def makeRunner (path : String) (provider : Provider) (name : String) : IO RunPack := do
|
|
992
|
+
let alltests ← loadspec path
|
|
993
|
+
match makeRunnerSpec alltests provider name with
|
|
994
|
+
| .ok pack => pure pack
|
|
995
|
+
| .error message => throw (IO.userError message)
|
|
996
|
+
|
|
997
|
+
end Omni
|