@voxgig/sdkgen 4.8.2 → 4.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/doctor.d.ts +1 -0
  3. package/dist/action/doctor.js +50 -2
  4. package/dist/action/doctor.js.map +1 -1
  5. package/dist/helpers/naming.js +4 -2
  6. package/dist/helpers/naming.js.map +1 -1
  7. package/dist/tsconfig.tsbuildinfo +1 -1
  8. package/model/sdkgen.aon +21 -0
  9. package/package.json +1 -1
  10. package/project/.sdk/model/feature/secrets.aon +120 -18
  11. package/project/.sdk/model/target/c.aon +10 -0
  12. package/project/.sdk/model/target/clojure.aon +13 -0
  13. package/project/.sdk/model/target/cpp.aon +10 -0
  14. package/project/.sdk/model/target/csharp.aon +9 -0
  15. package/project/.sdk/model/target/dart.aon +10 -0
  16. package/project/.sdk/model/target/elixir.aon +9 -0
  17. package/project/.sdk/model/target/go.aon +13 -0
  18. package/project/.sdk/model/target/java.aon +9 -0
  19. package/project/.sdk/model/target/js.aon +7 -0
  20. package/project/.sdk/model/target/kotlin.aon +9 -0
  21. package/project/.sdk/model/target/lean.aon +17 -0
  22. package/project/.sdk/model/target/lua.aon +8 -0
  23. package/project/.sdk/model/target/ocaml.aon +9 -0
  24. package/project/.sdk/model/target/perl.aon +8 -0
  25. package/project/.sdk/model/target/php.aon +8 -0
  26. package/project/.sdk/model/target/py.aon +13 -0
  27. package/project/.sdk/model/target/rb.aon +8 -0
  28. package/project/.sdk/model/target/rust.aon +10 -0
  29. package/project/.sdk/model/target/scala.aon +21 -2
  30. package/project/.sdk/model/target/swift.aon +18 -0
  31. package/project/.sdk/model/target/ts.aon +13 -0
  32. package/project/.sdk/src/cmp/clojure/Package_clojure.ts +9 -2
  33. package/project/.sdk/src/cmp/cpp/Gitignore_cpp.ts +3 -0
  34. package/project/.sdk/src/cmp/dart/Test_dart.ts +2 -0
  35. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +4 -1
  36. package/project/.sdk/src/cmp/go/Config_go.ts +59 -2
  37. package/project/.sdk/src/cmp/go/Main_go.ts +8 -2
  38. package/project/.sdk/src/cmp/java/Test_java.ts +6 -5
  39. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +6 -5
  40. package/project/.sdk/src/cmp/lean/Package_lean.ts +23 -0
  41. package/project/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +1 -0
  42. package/project/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +1 -0
  43. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  44. package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +7 -3
  45. package/project/.sdk/src/cmp/py/Config_py.ts +82 -1
  46. package/project/.sdk/src/cmp/py/Main_py.ts +44 -0
  47. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +2 -0
  48. package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
  49. package/project/.sdk/src/cmp/swift/Package_swift.ts +13 -1
  50. package/project/.sdk/src/cmp/ts/Config_ts.ts +42 -17
  51. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +13 -1
  52. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +13 -1
  53. package/project/.sdk/tm/c/Makefile +38 -3
  54. package/project/.sdk/tm/c/tests/omni_resolver.h +361 -0
  55. package/project/.sdk/tm/c/tests/omni_smoke_test.c +237 -0
  56. package/project/.sdk/tm/c/tests/primary_corpus_test.c +205 -284
  57. package/project/.sdk/tm/c/tests/struct_corpus_test.c +292 -212
  58. package/project/.sdk/tm/c/tests/vendor/omni/json.c +811 -0
  59. package/project/.sdk/tm/c/tests/vendor/omni/omni.h +240 -0
  60. package/project/.sdk/tm/c/tests/vendor/omni/runner.c +940 -0
  61. package/project/.sdk/tm/c/tests/vendor/omni/util.c +351 -0
  62. package/project/.sdk/tm/c/utility/struct/utility.c +7 -2
  63. package/project/.sdk/tm/clojure/test/sdk/test/omni.clj +401 -0
  64. package/project/.sdk/tm/clojure/test/sdk/test/omni_smoke.clj +81 -0
  65. package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +92 -58
  66. package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +122 -149
  67. package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +8 -3
  68. package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/json.clj +145 -0
  69. package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/runner.clj +445 -0
  70. package/project/.sdk/tm/clojure/test/vendor/omni/voxgig/omni/util.clj +145 -0
  71. package/project/.sdk/tm/cpp/Makefile +5 -1
  72. package/project/.sdk/tm/cpp/test/omni_resolver.hpp +473 -0
  73. package/project/.sdk/tm/cpp/test/omni_smoke_test.cpp +209 -0
  74. package/project/.sdk/tm/cpp/test/primary_utility_test.cpp +243 -184
  75. package/project/.sdk/tm/cpp/test/runner_support.hpp +68 -181
  76. package/project/.sdk/tm/cpp/test/struct_corpus_test.cpp +183 -56
  77. package/project/.sdk/tm/cpp/test/vendor/omni/json.hpp +406 -0
  78. package/project/.sdk/tm/cpp/test/vendor/omni/omni.hpp +751 -0
  79. package/project/.sdk/tm/cpp/test/vendor/omni/util.hpp +256 -0
  80. package/project/.sdk/tm/cpp/utility/pipeline.hpp +7 -1
  81. package/project/.sdk/tm/csharp/test/OmniResolver.cs +491 -0
  82. package/project/.sdk/tm/csharp/test/OmniSmokeTest.cs +106 -0
  83. package/project/.sdk/tm/csharp/test/PrimaryUtilityTest.cs +299 -316
  84. package/project/.sdk/tm/csharp/test/Runner.cs +104 -269
  85. package/project/.sdk/tm/csharp/test/StructUtilityTest.cs +535 -724
  86. package/project/.sdk/tm/csharp/test/vendor/omni/Runner.cs +864 -0
  87. package/project/.sdk/tm/csharp/test/vendor/omni/Util.cs +347 -0
  88. package/project/.sdk/tm/csharp/utility/MakeSpec.cs +3 -1
  89. package/project/.sdk/tm/csharp/utility/PrepareMethod.cs +6 -1
  90. package/project/.sdk/tm/csharp/utility/struct/Struct.cs +21 -5
  91. package/project/.sdk/tm/dart/test/omni.dart +520 -0
  92. package/project/.sdk/tm/dart/test/omni_smoke_test.dart +143 -0
  93. package/project/.sdk/tm/dart/test/primary_test.dart +86 -28
  94. package/project/.sdk/tm/dart/test/struct_test.dart +438 -11
  95. package/project/.sdk/tm/dart/test/vendor/omni/omni.dart +7 -0
  96. package/project/.sdk/tm/dart/test/vendor/omni/runner.dart +653 -0
  97. package/project/.sdk/tm/dart/test/vendor/omni/util.dart +232 -0
  98. package/project/.sdk/tm/elixir/Makefile +11 -3
  99. package/project/.sdk/tm/elixir/test/omni_smoke_test.exs +117 -0
  100. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +341 -16
  101. package/project/.sdk/tm/elixir/test/struct_corpus_test.exs +554 -9
  102. package/project/.sdk/tm/elixir/test/support/omni.ex +377 -0
  103. package/project/.sdk/tm/elixir/test/vendor/omni/json.ex +122 -0
  104. package/project/.sdk/tm/elixir/test/vendor/omni/runner.ex +623 -0
  105. package/project/.sdk/tm/elixir/test/vendor/omni/util.ex +187 -0
  106. package/project/.sdk/tm/go/core/context.go +32 -16
  107. package/project/.sdk/tm/go/feature/secrets/plugin/capability.go +261 -0
  108. package/project/.sdk/tm/go/feature/secrets/plugin/catalog.go +78 -0
  109. package/project/.sdk/tm/go/feature/secrets/plugin/config.go +652 -0
  110. package/project/.sdk/tm/go/feature/secrets/plugin/depend.go +250 -0
  111. package/project/.sdk/tm/go/feature/secrets/plugin/env.go +194 -0
  112. package/project/.sdk/tm/go/feature/secrets/plugin/export.go +80 -0
  113. package/project/.sdk/tm/go/feature/secrets/plugin/graph.go +238 -0
  114. package/project/.sdk/tm/go/feature/secrets/plugin/host.go +1575 -0
  115. package/project/.sdk/tm/go/feature/secrets/plugin/order.go +248 -0
  116. package/project/.sdk/tm/go/feature/secrets/plugin/point.go +184 -0
  117. package/project/.sdk/tm/go/feature/secrets/plugin/ref.go +123 -0
  118. package/project/.sdk/tm/go/feature/secrets/plugin/resolve.go +79 -0
  119. package/project/.sdk/tm/go/feature/secrets/plugin/types.go +296 -0
  120. package/project/.sdk/tm/go/feature/secrets/plugin/util.go +87 -0
  121. package/project/.sdk/tm/go/feature/secrets/plugin/version.go +161 -0
  122. package/project/.sdk/tm/go/feature/secrets/plugins/aws/aws.go +292 -0
  123. package/project/.sdk/tm/go/feature/secrets/plugins/aws/sigv4.go +201 -0
  124. package/project/.sdk/tm/go/feature/secrets/plugins/azuresecrets/azuresecrets.go +187 -0
  125. package/project/.sdk/tm/go/feature/secrets/plugins/boru/boru.go +184 -0
  126. package/project/.sdk/tm/go/feature/secrets/plugins/doppler/doppler.go +114 -0
  127. package/project/.sdk/tm/go/feature/secrets/plugins/gcpsecrets/gcpsecrets.go +156 -0
  128. package/project/.sdk/tm/go/feature/secrets/plugins/hashicorp/hashicorp.go +209 -0
  129. package/project/.sdk/tm/go/feature/secrets/plugins/httpjson/httpjson.go +241 -0
  130. package/project/.sdk/tm/go/feature/secrets/plugins/infisical/infisical.go +151 -0
  131. package/project/.sdk/tm/go/feature/secrets/plugins/onepassword/onepassword.go +152 -0
  132. package/project/.sdk/tm/go/feature/secrets/plugins/secretspec/secretspec.go +143 -0
  133. package/project/.sdk/tm/go/feature/secrets/sekreto/addr.go +120 -0
  134. package/project/.sdk/tm/go/feature/secrets/sekreto/providers.go +439 -0
  135. package/project/.sdk/tm/go/feature/secrets/sekreto/sekreto.go +685 -0
  136. package/project/.sdk/tm/go/feature/secrets_feature.go +675 -0
  137. package/project/.sdk/tm/go/src/feature/secrets/.gitkeep +0 -0
  138. package/project/.sdk/tm/go/test/feature/secrets/secrets_feature_test.go +799 -0
  139. package/project/.sdk/tm/go/test/omni/omni.go +897 -0
  140. package/project/.sdk/tm/go/test/omni/util.go +360 -0
  141. package/project/.sdk/tm/go/test/omniresolver_test.go +748 -0
  142. package/project/.sdk/tm/go/test/omnismoke_test.go +112 -0
  143. package/project/.sdk/tm/go/test/primary_utility_test.go +123 -191
  144. package/project/.sdk/tm/go/test/struct_utility_test.go +323 -16
  145. package/project/.sdk/tm/go/test/testsupport_test.go +294 -0
  146. package/project/.sdk/tm/go/utility/make_options.go +8 -1
  147. package/project/.sdk/tm/go/utility/prepare_method.go +5 -1
  148. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +2 -2
  149. package/project/.sdk/tm/java/test/OmniResolver.java +355 -0
  150. package/project/.sdk/tm/java/test/OmniSmokeTest.java +99 -0
  151. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +254 -280
  152. package/project/.sdk/tm/java/test/RunnerSupport.java +9 -194
  153. package/project/.sdk/tm/java/test/StructCorpusTest.java +119 -42
  154. package/project/.sdk/tm/java/test/vendor/omni/Json.java +287 -0
  155. package/project/.sdk/tm/java/test/vendor/omni/Runner.java +851 -0
  156. package/project/.sdk/tm/java/test/vendor/omni/Util.java +322 -0
  157. package/project/.sdk/tm/java/utility/MakeSpec.java +3 -1
  158. package/project/.sdk/tm/java/utility/PrepareMethod.java +5 -5
  159. package/project/.sdk/tm/java/utility/struct/Struct.java +17 -4
  160. package/project/.sdk/tm/js/src/utility/StructUtility.js +3 -3
  161. package/project/.sdk/tm/js/test/omni.js +251 -0
  162. package/project/.sdk/tm/js/test/omni.test.js +105 -0
  163. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +6 -2
  164. package/project/.sdk/tm/js/test/utility/StructUtility.test.js +27 -2
  165. package/project/.sdk/tm/js/test/vendor/omni/index.js +77 -0
  166. package/project/.sdk/tm/js/test/vendor/omni/runner.js +613 -0
  167. package/project/.sdk/tm/js/test/vendor/omni/util.js +212 -0
  168. package/project/.sdk/tm/kotlin/core/Utility.kt +1 -1
  169. package/project/.sdk/tm/kotlin/test/OmniResolver.kt +343 -0
  170. package/project/.sdk/tm/kotlin/test/OmniSmokeTest.kt +100 -0
  171. package/project/.sdk/tm/kotlin/test/PrimaryUtilityTest.kt +244 -262
  172. package/project/.sdk/tm/kotlin/test/RunnerSupport.kt +9 -178
  173. package/project/.sdk/tm/kotlin/test/StructCorpusTest.kt +121 -32
  174. package/project/.sdk/tm/kotlin/test/vendor/omni/Json.kt +264 -0
  175. package/project/.sdk/tm/kotlin/test/vendor/omni/Runner.kt +615 -0
  176. package/project/.sdk/tm/kotlin/test/vendor/omni/Util.kt +139 -0
  177. package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +6 -3
  178. package/project/.sdk/tm/kotlin/utility/Prepare.kt +6 -6
  179. package/project/.sdk/tm/kotlin/utility/struct/Struct.kt +18 -2
  180. package/project/.sdk/tm/lean/Makefile +12 -5
  181. package/project/.sdk/tm/lean/src/SdkUtility.lean +14 -1
  182. package/project/.sdk/tm/lean/test/OmniResolver.lean +358 -0
  183. package/project/.sdk/tm/lean/test/OmniSmoke.lean +174 -0
  184. package/project/.sdk/tm/lean/test/StructCorpus.lean +245 -574
  185. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +155 -291
  186. package/project/.sdk/tm/lean/test/vendor/omni/Omni.lean +997 -0
  187. package/project/.sdk/tm/lua/test/omni.lua +456 -0
  188. package/project/.sdk/tm/lua/test/omni_smoke_test.lua +103 -0
  189. package/project/.sdk/tm/lua/test/primary_utility_test.lua +250 -509
  190. package/project/.sdk/tm/lua/test/struct_utility_test.lua +161 -70
  191. package/project/.sdk/tm/lua/test/vendor/omni/json.lua +268 -0
  192. package/project/.sdk/tm/lua/test/vendor/omni/regex.lua +385 -0
  193. package/project/.sdk/tm/lua/test/vendor/omni/runner.lua +641 -0
  194. package/project/.sdk/tm/lua/test/vendor/omni/util.lua +282 -0
  195. package/project/.sdk/tm/lua/utility/prepare_method.lua +5 -1
  196. package/project/.sdk/tm/lua/utility/struct/regex.lua +663 -0
  197. package/project/.sdk/tm/lua/utility/struct/struct.lua +660 -420
  198. package/project/.sdk/tm/ocaml/Makefile +37 -12
  199. package/project/.sdk/tm/ocaml/test/omni_resolver.ml +284 -0
  200. package/project/.sdk/tm/ocaml/test/omni_smoke_test.ml +169 -0
  201. package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +84 -100
  202. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +225 -201
  203. package/project/.sdk/tm/ocaml/test/vendor/omni/omni.ml +1157 -0
  204. package/project/.sdk/tm/perl/lib/Voxgig/Struct.pm +88 -12
  205. package/project/.sdk/tm/perl/t/omni.pm +647 -0
  206. package/project/.sdk/tm/perl/t/omni_smoke.t +87 -0
  207. package/project/.sdk/tm/perl/t/primary_utility.t +189 -487
  208. package/project/.sdk/tm/perl/t/struct_utility.t +455 -389
  209. package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni/Runner.pm +649 -0
  210. package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni/Util.pm +253 -0
  211. package/project/.sdk/tm/perl/t/vendor/omni/Voxgig/Omni.pm +24 -0
  212. package/project/.sdk/tm/perl/utility/prepare_method.pm +7 -2
  213. package/project/.sdk/tm/php/test/Omni.php +691 -0
  214. package/project/.sdk/tm/php/test/OmniSmokeTest.php +93 -0
  215. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +210 -571
  216. package/project/.sdk/tm/php/test/StructUtilityTest.php +320 -342
  217. package/project/.sdk/tm/php/test/vendor/omni/Runner.php +644 -0
  218. package/project/.sdk/tm/php/test/vendor/omni/Util.php +283 -0
  219. package/project/.sdk/tm/php/utility/MakeSpec.php +10 -3
  220. package/project/.sdk/tm/php/utility/PrepareBody.php +7 -1
  221. package/project/.sdk/tm/php/utility/PrepareMethod.php +6 -2
  222. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/__init__.py +36 -0
  223. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/capability.py +141 -0
  224. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/catalog.py +50 -0
  225. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/config.py +333 -0
  226. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/depend.py +198 -0
  227. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/env.py +147 -0
  228. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/export.py +52 -0
  229. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/graph.py +139 -0
  230. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/host.py +1053 -0
  231. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/order.py +171 -0
  232. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/point.py +95 -0
  233. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/ref.py +116 -0
  234. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/resolve.py +59 -0
  235. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/types.py +87 -0
  236. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_plugin/version.py +112 -0
  237. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/__init__.py +74 -0
  238. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/addr.py +116 -0
  239. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/aws.py +192 -0
  240. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/azuresecrets.py +120 -0
  241. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/boru.py +140 -0
  242. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/doppler.py +62 -0
  243. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/gcpsecrets.py +101 -0
  244. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/hashicorp.py +152 -0
  245. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/httpjson.py +115 -0
  246. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/infisical.py +92 -0
  247. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/onepassword.py +101 -0
  248. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/secretspec.py +119 -0
  249. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/plugins/sigv4.py +125 -0
  250. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/providers.py +211 -0
  251. package/project/.sdk/tm/py/pkg/feature/secrets/voxgig_sekreto/sekreto.py +455 -0
  252. package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +467 -0
  253. package/project/.sdk/tm/py/pkg/utility/prepare_method.py +5 -1
  254. package/project/.sdk/tm/py/pkg/utility/voxgig_struct/__init__.py +0 -2
  255. package/project/.sdk/tm/py/pkg/utility/voxgig_struct/voxgig_struct.py +780 -529
  256. package/project/.sdk/tm/py/src/feature/secrets/.gitkeep +0 -0
  257. package/project/.sdk/tm/py/test/feature/__init__.py +0 -0
  258. package/project/.sdk/tm/py/test/feature/secrets/__init__.py +0 -0
  259. package/project/.sdk/tm/py/test/feature/secrets/test_secrets.py +337 -0
  260. package/project/.sdk/tm/py/test/omni.py +415 -0
  261. package/project/.sdk/tm/py/test/test_omni_smoke.py +74 -0
  262. package/project/.sdk/tm/py/test/test_primary_utility.py +144 -668
  263. package/project/.sdk/tm/py/test/test_struct_utility.py +74 -27
  264. package/project/.sdk/tm/py/test/voxgig_omni/__init__.py +62 -0
  265. package/project/.sdk/tm/py/test/voxgig_omni/runner.py +566 -0
  266. package/project/.sdk/tm/py/test/voxgig_omni/util.py +237 -0
  267. package/project/.sdk/tm/rb/test/omni.rb +505 -0
  268. package/project/.sdk/tm/rb/test/omni_smoke_test.rb +72 -0
  269. package/project/.sdk/tm/rb/test/primary_utility_test.rb +174 -560
  270. package/project/.sdk/tm/rb/test/struct_utility_test.rb +13 -7
  271. package/project/.sdk/tm/rb/test/vendor/omni/runner.rb +493 -0
  272. package/project/.sdk/tm/rb/test/vendor/omni/util.rb +199 -0
  273. package/project/.sdk/tm/rb/test/vendor/omni/voxgig_omni.rb +15 -0
  274. package/project/.sdk/tm/rb/utility/prepare_method.rb +5 -1
  275. package/project/.sdk/tm/rust/tests/common/mod.rs +19 -242
  276. package/project/.sdk/tm/rust/tests/omni_resolver/mod.rs +888 -0
  277. package/project/.sdk/tm/rust/tests/omni_smoke_test.rs +286 -0
  278. package/project/.sdk/tm/rust/tests/primary_utility_test.rs +318 -312
  279. package/project/.sdk/tm/rust/tests/struct_utility_test.rs +66 -28
  280. package/project/.sdk/tm/rust/tests/vendor/omni/json.rs +412 -0
  281. package/project/.sdk/tm/rust/tests/vendor/omni/mod.rs +38 -0
  282. package/project/.sdk/tm/rust/tests/vendor/omni/regex.rs +392 -0
  283. package/project/.sdk/tm/rust/tests/vendor/omni/runner.rs +1001 -0
  284. package/project/.sdk/tm/rust/tests/vendor/omni/util.rs +161 -0
  285. package/project/.sdk/tm/rust/utility/prepare_method.rs +5 -1
  286. package/project/.sdk/tm/scala/Makefile +16 -6
  287. package/project/.sdk/tm/scala/sdktest/OmniResolver.scala +497 -0
  288. package/project/.sdk/tm/scala/sdktest/OmniSmoke.scala +192 -0
  289. package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +47 -19
  290. package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
  291. package/project/.sdk/tm/scala/sdktest/vendor/omni/Json.scala +229 -0
  292. package/project/.sdk/tm/scala/sdktest/vendor/omni/Runner.scala +552 -0
  293. package/project/.sdk/tm/scala/sdktest/vendor/omni/Util.scala +104 -0
  294. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Constants.swift +10 -0
  295. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Inject.swift +28 -5
  296. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Injection.swift +5 -1
  297. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Minor.swift +4 -1
  298. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/Struct/Validate.swift +15 -5
  299. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +6 -1
  300. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/OmniResolver.swift +474 -0
  301. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/OmniSmokeTest.swift +107 -0
  302. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/PrimaryUtilityTest.swift +141 -178
  303. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/Runner.swift +17 -113
  304. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/StructUtilityTest.swift +295 -446
  305. package/project/.sdk/tm/swift/Tests/vendor/omni/Json.swift +364 -0
  306. package/project/.sdk/tm/swift/Tests/vendor/omni/Runner.swift +730 -0
  307. package/project/.sdk/tm/swift/Tests/vendor/omni/Util.swift +162 -0
  308. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +7 -0
  309. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Capability.ts +148 -0
  310. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Catalog.ts +55 -0
  311. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Config.ts +344 -0
  312. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Depend.ts +205 -0
  313. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Env.ts +138 -0
  314. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Export.ts +46 -0
  315. package/project/.sdk/tm/ts/src/feature/secrets/plugin/FeatureHost.ts +349 -0
  316. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Graph.ts +181 -0
  317. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Host.ts +1132 -0
  318. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Order.ts +177 -0
  319. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Point.ts +119 -0
  320. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Ref.ts +102 -0
  321. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Resolve.ts +61 -0
  322. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Types.ts +112 -0
  323. package/project/.sdk/tm/ts/src/feature/secrets/plugin/Version.ts +100 -0
  324. package/project/.sdk/tm/ts/src/feature/secrets/plugin/index.ts +37 -0
  325. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +163 -30
  326. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +27 -22
  327. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/aws.ts +26 -36
  328. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/azuresecrets.ts +19 -21
  329. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/boru.ts +36 -17
  330. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/doppler.ts +14 -21
  331. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/gcpsecrets.ts +27 -27
  332. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/hashicorp.ts +25 -39
  333. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/plugins/httpjson.ts +99 -0
  334. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/infisical.ts +14 -16
  335. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{provider → plugins}/onepassword.ts +15 -22
  336. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/plugins/secretspec.ts +98 -0
  337. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/{Sigv4.ts → plugins/sigv4.ts} +7 -6
  338. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +97 -23
  339. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/builtin.ts +37 -0
  340. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +4 -17
  341. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +4 -16
  342. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +11 -25
  343. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +6 -23
  344. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +106 -39
  345. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +3 -3
  346. package/project/.sdk/tm/ts/test/omni.ts +104 -32
  347. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +27 -0
  348. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +32 -5
  349. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +2 -2
  350. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +2 -2
  351. package/project/.sdk/tm/zig/test/primary_utility_test.zig +1 -1
  352. package/project/.sdk/tm/zig/test/struct_runner.zig +5 -4
  353. package/project/sdkgen-package.json +1 -1
  354. package/src/action/doctor.ts +66 -2
  355. package/src/helpers/naming.ts +4 -2
  356. package/project/.sdk/tm/c/tests/runner.h +0 -274
  357. package/project/.sdk/tm/cpp/test/struct_runner.hpp +0 -314
  358. package/project/.sdk/tm/csharp/test/StructRunner.cs +0 -276
  359. package/project/.sdk/tm/dart/test/runner.dart +0 -406
  360. package/project/.sdk/tm/dart/test/struct_corpus.dart +0 -531
  361. package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +0 -1088
  362. package/project/.sdk/tm/go/test/runner_test.go +0 -629
  363. package/project/.sdk/tm/go/test/struct_runner_test.go +0 -1094
  364. package/project/.sdk/tm/java/test/StructRunner.java +0 -281
  365. package/project/.sdk/tm/js/test/runner.js +0 -387
  366. package/project/.sdk/tm/kotlin/test/StructRunner.kt +0 -177
  367. package/project/.sdk/tm/lua/test/struct_runner.lua +0 -602
  368. package/project/.sdk/tm/ocaml/test/corpus_runner.ml +0 -185
  369. package/project/.sdk/tm/perl/t/struct_runner.pm +0 -275
  370. package/project/.sdk/tm/php/test/StructRunner.php +0 -275
  371. package/project/.sdk/tm/py/test/struct_runner.py +0 -411
  372. package/project/.sdk/tm/rb/test/struct_runner.rb +0 -309
  373. package/project/.sdk/tm/rust/tests/struct_runner/mod.rs +0 -424
  374. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +0 -90
  375. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +0 -66
  376. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +0 -188
@@ -1,7 +1,30 @@
1
+ -- VENDORED: @voxgig/struct 0.1.1 (lua/src/struct.lua)
2
+ -- Source: https://github.com/voxgig/struct @ 2caf7f448f265144c18dd6fab6ba270a7f3bca07 [tag: sdk-20260904-1610-0]
3
+ -- License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
1
4
  -- Copyright (c) 2025-2026 Voxgig Ltd. MIT LICENSE.
2
5
 
3
6
  -- VERSION: @voxgig/struct 0.0.10
4
7
 
8
+ -- RE2-subset regex engine — pure Lua, no external deps. Lua's built-in
9
+ -- string.match uses Lua patterns (not regex), so for cross-port uniformity
10
+ -- we use this engine. See /REGEX_API.md.
11
+ -- Installed (namespaced) layout: voxgig/struct.lua + voxgig/struct/regex.lua.
12
+ local _regex_ok, _regex = pcall(require, "utility.struct.regex")
13
+ if not _regex_ok then
14
+ -- Flat dev layout / the regex-only specs, which put src/ on package.path.
15
+ _regex_ok, _regex = pcall(require, "regex")
16
+ end
17
+ if not _regex_ok then
18
+ -- When loaded from a sibling test file with package.path tweaked.
19
+ _regex_ok, _regex = pcall(dofile, (debug.getinfo(1, "S").source:sub(2):gsub("struct%.lua$", "regex.lua")))
20
+ end
21
+ local re_compile = _regex and _regex.re_compile or nil
22
+ local re_test = _regex and _regex.re_test or nil
23
+ local re_find = _regex and _regex.re_find or nil
24
+ local re_find_all = _regex and _regex.re_find_all or nil
25
+ local re_replace = _regex and _regex.re_replace or nil
26
+ local re_escape = _regex and _regex.re_escape or nil
27
+
5
28
  --[[
6
29
  Voxgig Struct
7
30
  =============
@@ -51,7 +74,8 @@
51
74
  to represent JSON null, if this ambiguity creates issues
52
75
  (thankfully in most APIs, JSON nulls are not used). For example,
53
76
  the unit tests use the string "__NULL__" where necessary.
54
- ]] ----------------------------------------------------------
77
+ ]]
78
+ ----------------------------------------------------------
55
79
  -- String constants are explicitly defined.
56
80
  ----------------------------------------------------------
57
81
 
@@ -61,61 +85,66 @@ local M_KEYPOST = 2
61
85
  local M_VAL = 4
62
86
 
63
87
  local MODENAME = {
64
- [M_VAL] = 'val',
65
- [M_KEYPRE] = 'key:pre',
66
- [M_KEYPOST] = 'key:post',
88
+ [M_VAL] = "val",
89
+ [M_KEYPRE] = "key:pre",
90
+ [M_KEYPOST] = "key:post",
67
91
  }
68
92
 
69
93
  -- Special strings.
70
- local S_BKEY = '`$KEY`'
71
- local S_BANNO = '`$ANNO`'
72
- local S_BEXACT = '`$EXACT`'
73
- local S_BVAL = '`$VAL`'
94
+ local S_BKEY = "`$KEY`"
95
+ local S_BANNO = "`$ANNO`"
96
+ local S_BEXACT = "`$EXACT`"
97
+ local S_BVAL = "`$VAL`"
74
98
 
75
- local S_DKEY = '$KEY'
76
- local S_DTOP = '$TOP'
77
- local S_DERRS = '$ERRS'
78
- local S_DSPEC = '$SPEC'
99
+ local S_DKEY = "$KEY"
100
+ local S_DTOP = "$TOP"
101
+ local S_DERRS = "$ERRS"
102
+ local S_DSPEC = "$SPEC"
79
103
 
80
104
  -- General strings.
81
- local S_list = 'list'
82
- local S_base = 'base'
83
- local S_boolean = 'boolean'
84
- local S_function = 'function'
85
- local S_symbol = 'symbol'
86
- local S_instance = 'instance'
87
- local S_key = 'key'
88
- local S_any = 'any'
89
- local S_nil = 'nil'
90
- local S_null = 'null'
91
- local S_number = 'number'
92
- local S_object = 'object'
93
- local S_string = 'string'
94
- local S_decimal = 'decimal'
95
- local S_integer = 'integer'
96
- local S_map = 'map'
97
- local S_scalar = 'scalar'
98
- local S_node = 'node'
105
+ local S_list = "list"
106
+ local S_base = "base"
107
+ local S_boolean = "boolean"
108
+ local S_function = "function"
109
+ local S_symbol = "symbol"
110
+ local S_instance = "instance"
111
+ local S_key = "key"
112
+ local S_any = "any"
113
+ local S_nil = "nil"
114
+ local S_null = "null"
115
+
116
+ -- JSON null sentinel value. Lua has no value distinct from nil for JSON
117
+ -- null, so (as the file header notes) the string "__NULL__" carries a JSON
118
+ -- null through code and the test harness. This is distinct from nil, which
119
+ -- represents an absent/undefined value (the canonical TS `undefined`/NONE).
120
+ local NULLMARK = "__NULL__"
121
+ local S_number = "number"
122
+ local S_object = "object"
123
+ local S_string = "string"
124
+ local S_decimal = "decimal"
125
+ local S_integer = "integer"
126
+ local S_map = "map"
127
+ local S_scalar = "scalar"
128
+ local S_node = "node"
99
129
 
100
130
  -- Character strings.
101
- local S_BT = '`'
102
- local S_CN = ':'
103
- local S_CS = ']'
104
- local S_DS = '$'
105
- local S_DT = '.'
106
- local S_FS = '/'
107
- local S_KEY = 'KEY'
108
- local S_MT = ''
109
- local S_OS = '['
110
- local S_SP = ' '
111
- local S_CM = ','
112
- local S_VIZ = ': '
113
-
131
+ local S_BT = "`"
132
+ local S_CN = ":"
133
+ local S_CS = "]"
134
+ local S_DS = "$"
135
+ local S_DT = "."
136
+ local S_FS = "/"
137
+ local S_KEY = "KEY"
138
+ local S_MT = ""
139
+ local S_OS = "["
140
+ local S_SP = " "
141
+ local S_CM = ","
142
+ local S_VIZ = ": "
114
143
 
115
144
  -- Types (bit flags)
116
145
  -- Using explicit bit positions to match TS implementation
117
- local T_any = (1 << 31) - 1 -- All bits set
118
- local T_noval = 1 << 30 -- Property absent, undefined
146
+ local T_any = (1 << 31) - 1 -- All bits set
147
+ local T_noval = 1 << 30 -- Property absent, undefined
119
148
  local T_boolean = 1 << 29
120
149
  local T_decimal = 1 << 28
121
150
  local T_integer = 1 << 27
@@ -123,7 +152,7 @@ local T_number = 1 << 26
123
152
  local T_string = 1 << 25
124
153
  local T_function = 1 << 24
125
154
  local T_symbol = 1 << 23
126
- local T_null = 1 << 22 -- Actual JSON null value
155
+ local T_null = 1 << 22 -- Actual JSON null value
127
156
  -- gap of 7
128
157
  local T_list = 1 << 14
129
158
  local T_map = 1 << 13
@@ -143,23 +172,57 @@ local TYPENAME = {
143
172
  S_function,
144
173
  S_symbol,
145
174
  S_null,
146
- '', '', '',
147
- '', '', '', '',
175
+ "",
176
+ "",
177
+ "",
178
+ "",
179
+ "",
180
+ "",
181
+ "",
148
182
  S_list,
149
183
  S_map,
150
184
  S_instance,
151
- '', '', '', '',
185
+ "",
186
+ "",
187
+ "",
188
+ "",
152
189
  S_scalar,
153
190
  S_node,
154
191
  }
155
192
 
156
-
157
193
  -- The standard undefined value for this language.
158
194
  local NONE = nil
159
195
 
160
196
  -- Private markers
161
- local SKIP = { ['`$SKIP`'] = true }
162
- local DELETE = { ['`$DELETE`'] = true }
197
+ local SKIP = { ["`$SKIP`"] = true }
198
+ local DELETE = { ["`$DELETE`"] = true }
199
+
200
+ -- The no-value: absent, as distinct from a JSON null.
201
+ --
202
+ -- Lua has one `nil` and the corpus needs two states, so this port models the
203
+ -- second explicitly - the same move struct/go makes with its `NOVAL`
204
+ -- sentinel. `typify()` and `typify(null)` are different results the corpus
205
+ -- pins as different (1073741824 against 4194432), and without a sentinel a
206
+ -- Lua port simply cannot express the first.
207
+ --
208
+ -- It is recognised AHEAD of the normal dispatch in `typify`, which is the
209
+ -- whole trick: a Lua sentinel is a table, so the ordinary path would class it
210
+ -- as a map. Everywhere else it collapses to `nil` via `denoval`, because
211
+ -- canonical distinguishes undefined from null in `typify` and nowhere else
212
+ -- (`null == val` is true for both in JavaScript).
213
+ local NOVAL = setmetatable({}, {
214
+ __tostring = function()
215
+ return "NOVAL"
216
+ end,
217
+ })
218
+
219
+ -- Collapse the no-value to nil for every function that must not see it.
220
+ local function denoval(val)
221
+ if NOVAL == val then
222
+ return nil
223
+ end
224
+ return val
225
+ end
163
226
 
164
227
  local MAXDEPTH = 32
165
228
 
@@ -171,6 +234,7 @@ local _injecthandler
171
234
  local _validatehandler
172
235
  local _invalidTypeMsg
173
236
  local _validation
237
+ local stringify
174
238
  local ismap
175
239
  local islist
176
240
  local getpath
@@ -179,31 +243,47 @@ local delprop
179
243
  local checkPlacement
180
244
  local injectorArgs
181
245
 
182
-
183
246
  -- Return type string for narrowest type.
184
247
  local function typename(t)
185
248
  -- Math.clz32 equivalent: count leading zeros in a 32-bit integer
186
249
  local function clz32(x)
187
- if x == 0 then return 32 end
250
+ if x == 0 then
251
+ return 32
252
+ end
188
253
  local n = 0
189
- if (x & 0xFFFF0000) == 0 then n = n + 16; x = x << 16 end
190
- if (x & 0xFF000000) == 0 then n = n + 8; x = x << 8 end
191
- if (x & 0xF0000000) == 0 then n = n + 4; x = x << 4 end
192
- if (x & 0xC0000000) == 0 then n = n + 2; x = x << 2 end
193
- if (x & 0x80000000) == 0 then n = n + 1 end
254
+ if (x & 0xFFFF0000) == 0 then
255
+ n = n + 16
256
+ x = x << 16
257
+ end
258
+ if (x & 0xFF000000) == 0 then
259
+ n = n + 8
260
+ x = x << 8
261
+ end
262
+ if (x & 0xF0000000) == 0 then
263
+ n = n + 4
264
+ x = x << 4
265
+ end
266
+ if (x & 0xC0000000) == 0 then
267
+ n = n + 2
268
+ x = x << 2
269
+ end
270
+ if (x & 0x80000000) == 0 then
271
+ n = n + 1
272
+ end
194
273
  return n
195
274
  end
196
- local idx = clz32(t) + 1 -- 1-based index
275
+ local idx = clz32(t) + 1 -- 1-based index
197
276
  if idx >= 1 and idx <= #TYPENAME then
198
277
  return TYPENAME[idx]
199
278
  end
200
- return TYPENAME[1] -- S_any
279
+ return TYPENAME[1] -- S_any
201
280
  end
202
281
 
203
282
  -- Value is a node - defined, and a map (hash) or list (array).
204
283
  -- @param val (any) The value to check
205
284
  -- @return (boolean) True if value is a node
206
285
  local function isnode(val)
286
+ val = denoval(val)
207
287
  if val == nil then
208
288
  return false
209
289
  end
@@ -211,14 +291,13 @@ local function isnode(val)
211
291
  return ismap(val) or islist(val)
212
292
  end
213
293
 
214
-
215
294
  -- Value is a defined map (hash) with string keys.
216
295
  -- @param val (any) The value to check
217
296
  -- @return (boolean) True if value is a map
218
297
  ismap = function(val)
298
+ val = denoval(val)
219
299
  -- Check if the value is a table
220
- if type(val) ~= "table" or
221
- (getmetatable(val) and getmetatable(val).__jsontype == "array") then
300
+ if type(val) ~= "table" or (getmetatable(val) and getmetatable(val).__jsontype == "array") then
222
301
  return false
223
302
  end
224
303
 
@@ -237,21 +316,24 @@ ismap = function(val)
237
316
  return true
238
317
  end
239
318
 
240
-
241
319
  -- Value is a defined list (array) with integer keys (indexes).
242
320
  -- @param val (any) The value to check
243
321
  -- @return (boolean) True if value is a list
244
322
  islist = function(val)
323
+ val = denoval(val)
245
324
  -- First check metatable indicators (preferred approach)
246
- if getmetatable(val) and ((getmetatable(val).__jsontype == "array") or
247
- (getmetatable(val).__jsontype and getmetatable(val).__jsontype.type ==
248
- "array")) then
325
+ if
326
+ getmetatable(val)
327
+ and (
328
+ (getmetatable(val).__jsontype == "array")
329
+ or (getmetatable(val).__jsontype and getmetatable(val).__jsontype.type == "array")
330
+ )
331
+ then
249
332
  return true
250
333
  end
251
334
 
252
335
  -- Check if it's a table
253
- if type(val) ~= "table" or
254
- (getmetatable(val) and getmetatable(val).__jsontype == "object") then
336
+ if type(val) ~= "table" or (getmetatable(val) and getmetatable(val).__jsontype == "object") then
255
337
  return false
256
338
  end
257
339
 
@@ -271,17 +353,15 @@ islist = function(val)
271
353
  return count > 0 and max == count
272
354
  end
273
355
 
274
-
275
356
  -- Value is a defined string (non-empty) or integer key.
276
357
  -- @param key (any) The key to check
277
358
  -- @return (boolean) True if key is valid
278
359
  local function iskey(key)
360
+ key = denoval(key)
279
361
  local keytype = type(key)
280
- return (keytype == S_string and key ~= S_MT and key ~= S_null) or keytype ==
281
- S_number
362
+ return (keytype == S_string and key ~= S_MT and key ~= NULLMARK) or keytype == S_number
282
363
  end
283
364
 
284
-
285
365
  -- Get a defined value. Returns alt if val is nil.
286
366
  local function getdef(val, alt)
287
367
  if nil == val then
@@ -290,14 +370,16 @@ local function getdef(val, alt)
290
370
  return val
291
371
  end
292
372
 
293
-
294
373
  -- The integer size of the value.
295
374
  local function size(val)
375
+ val = denoval(val)
296
376
  if islist(val) then
297
377
  return #val
298
378
  elseif ismap(val) then
299
379
  local count = 0
300
- for _ in pairs(val) do count = count + 1 end
380
+ for _ in pairs(val) do
381
+ count = count + 1
382
+ end
301
383
  return count
302
384
  end
303
385
 
@@ -314,13 +396,13 @@ local function size(val)
314
396
  end
315
397
  end
316
398
 
317
-
318
399
  -- Check for an "empty" value - nil, empty string, array, object.
319
400
  -- @param val (any) The value to check
320
401
  -- @return (boolean) True if value is empty
321
402
  local function isempty(val)
322
- -- Check if the value is nil
323
- if val == nil or val == S_null then
403
+ val = denoval(val)
404
+ -- Check if the value is absent (nil) or JSON null (NULLMARK).
405
+ if val == nil or val == NULLMARK then
324
406
  return true
325
407
  end
326
408
 
@@ -338,29 +420,37 @@ local function isempty(val)
338
420
  return false
339
421
  end
340
422
 
341
-
342
423
  -- Value is a function.
343
424
  -- @param val (any) The value to check
344
425
  -- @return (boolean) True if value is a function
345
426
  local function isfunc(val)
346
- return type(val) == 'function'
427
+ val = denoval(val)
428
+ return type(val) == "function"
347
429
  end
348
430
 
349
-
350
431
  -- Determine the type of a value as a bit code.
351
432
  -- @param value (any) The value to check
352
433
  -- @return (number) The type as a bit flag
353
434
  local function typify(value)
435
+ -- The no-value, checked BEFORE anything else: NOVAL is a table, so the
436
+ -- dispatch below would otherwise class it as a map.
437
+ if NOVAL == value then
438
+ return T_noval
439
+ end
440
+
441
+ -- Lua's nil is the representation of JSON null. Canonical TS
442
+ -- typify(null) === T_scalar | T_null; typify(undefined) === T_noval, which
443
+ -- this port spells NOVAL (above).
354
444
  if value == nil then
355
- return T_null
445
+ return T_scalar | T_null
356
446
  end
357
447
 
358
448
  local luatype = type(value)
359
449
 
360
450
  if luatype == S_number then
361
- if value ~= value then -- NaN check
451
+ if value ~= value then -- NaN check
362
452
  return T_noval
363
- elseif math.type(value) == 'integer' or (value % 1 == 0) then
453
+ elseif math.type(value) == "integer" or (value % 1 == 0) then
364
454
  return T_scalar | T_number | T_integer
365
455
  else
366
456
  return T_scalar | T_number | T_decimal
@@ -371,7 +461,7 @@ local function typify(value)
371
461
  return T_scalar | T_boolean
372
462
  elseif luatype == S_function then
373
463
  return T_scalar | T_function
374
- elseif luatype == 'table' then
464
+ elseif luatype == "table" then
375
465
  if islist(value) then
376
466
  return T_node | T_list
377
467
  elseif ismap(value) then
@@ -384,7 +474,6 @@ local function typify(value)
384
474
  return T_any
385
475
  end
386
476
 
387
-
388
477
  -- Safely get a property of a node. Nil arguments return nil.
389
478
  -- If the key is not found, return the alternative value, if any.
390
479
  -- @param val (any) The parent object/table
@@ -424,7 +513,8 @@ local function getprop(val, key, alt)
424
513
  end
425
514
  end
426
515
 
427
- -- Return alternative if out is nil
516
+ -- Return alternative if out is absent. Lua's nil is JSON null/undefined;
517
+ -- canonical TS `if (null == out) return alt` unifies null and undefined.
428
518
  if out == nil then
429
519
  return alt
430
520
  end
@@ -432,7 +522,6 @@ local function getprop(val, key, alt)
432
522
  return out
433
523
  end
434
524
 
435
-
436
525
  -- Get a list element. The key should be an integer, or a string
437
526
  -- that can parse to an integer only. Negative integers count from the end of the list.
438
527
  local function getelem(val, key, alt)
@@ -453,7 +542,11 @@ local function getelem(val, key, alt)
453
542
  end
454
543
  end
455
544
 
456
- if NONE == out then
545
+ -- A nil/JSON-null slot counts as "no value" → alt. Lua arrays cannot hold
546
+ -- a nil hole, so the test harness carries a JSON null list element as the
547
+ -- NULLMARK ("__NULL__") sentinel; treat it as no-value here too.
548
+ -- Canonical TS: `if (null == out) return alt`.
549
+ if NONE == out or NULLMARK == out then
457
550
  if NONE ~= alt and type(alt) == S_function then
458
551
  return alt()
459
552
  end
@@ -463,7 +556,6 @@ local function getelem(val, key, alt)
463
556
  return out
464
557
  end
465
558
 
466
-
467
559
  -- Convert different types of keys to string representation.
468
560
  -- String keys are returned as is.
469
561
  -- Number keys are converted to strings.
@@ -472,7 +564,8 @@ end
472
564
  -- @param key (any) The key to convert
473
565
  -- @return (string) The string representation of the key
474
566
  local function strkey(key)
475
- if key == NONE or key == S_null then
567
+ key = denoval(key)
568
+ if key == NONE or key == NULLMARK then
476
569
  return S_MT
477
570
  end
478
571
 
@@ -491,18 +584,21 @@ local function strkey(key)
491
584
  return S_MT
492
585
  end
493
586
 
494
-
495
587
  -- Sorted keys of a map, or indexes of a list.
496
588
  -- @param val (any) The object or array to get keys from
497
589
  -- @return (table) Array of keys as strings
498
590
  local function keysof(val)
591
+ val = denoval(val)
592
+ -- Marked, not plain. An UNMARKED empty table is a map by this port's own
593
+ -- rules (ismap({}) is true, islist({}) is false), so an unmarked `{}` here
594
+ -- claims keysof returned an object where the corpus wants [].
499
595
  if not isnode(val) then
500
- return {}
596
+ return setmetatable({}, { __jsontype = "array" })
501
597
  end
502
598
 
503
599
  if ismap(val) then
504
600
  -- For maps, collect all keys and sort them
505
- local keys = {}
601
+ local keys = setmetatable({}, { __jsontype = "array" })
506
602
  for k, _ in pairs(val) do
507
603
  table.insert(keys, k)
508
604
  end
@@ -510,7 +606,7 @@ local function keysof(val)
510
606
  return keys
511
607
  else
512
608
  -- For lists, create array of stringified indices (0-based to match JS/Go)
513
- local indexes = {}
609
+ local indexes = setmetatable({}, { __jsontype = "array" })
514
610
  for i = 1, #val do
515
611
  -- Subtract 1 to convert from Lua's 1-based to 0-based indexing
516
612
  table.insert(indexes, tostring(i - 1))
@@ -519,7 +615,6 @@ local function keysof(val)
519
615
  end
520
616
  end
521
617
 
522
-
523
618
  -- Value of property with name key in node val is defined.
524
619
  -- @param val (any) The object to check
525
620
  -- @param key (any) The key to check
@@ -528,16 +623,16 @@ local function haskey(val, key)
528
623
  return getprop(val, key) ~= NONE
529
624
  end
530
625
 
531
-
532
626
  -- List the sorted keys of a map or list as an array of tuples of the form {key, value}
533
627
  -- @param val (any) The object or array to convert to key-value pairs
534
628
  -- @return (table) Array of {key, value} pairs
535
629
  local function items(val)
630
+ val = denoval(val)
536
631
  if type(val) ~= "table" then
537
- return {}
632
+ return setmetatable({}, { __jsontype = "array" })
538
633
  end
539
634
 
540
- local result = {}
635
+ local result = setmetatable({}, { __jsontype = "array" })
541
636
 
542
637
  if islist(val) then
543
638
  -- Handle array-like tables (0-based string keys like JS Object.entries)
@@ -559,7 +654,6 @@ local function items(val)
559
654
  return result
560
655
  end
561
656
 
562
-
563
657
  -- Filter item values using check function.
564
658
  -- check receives {key, value} pairs (1-indexed: [1]=key, [2]=value).
565
659
  -- Returns array of values where check returns true.
@@ -576,7 +670,6 @@ local function filter(val, check)
576
670
  return out
577
671
  end
578
672
 
579
-
580
673
  -- Escape regular expression.
581
674
  -- @param s (string) The string to escape
582
675
  -- @return (string) The escaped string
@@ -586,7 +679,6 @@ local function escre(s)
586
679
  return result
587
680
  end
588
681
 
589
-
590
682
  -- Escape URLs.
591
683
  -- @param s (string) The string to escape
592
684
  -- @return (string) The URL-encoded string
@@ -599,10 +691,9 @@ local function escurl(s)
599
691
  return result
600
692
  end
601
693
 
602
-
603
694
  -- Replace a search string (all), or a pattern, in a source string.
604
695
  local function replace(s, from, to)
605
- local rs = s
696
+ local rs
606
697
  local ts = typify(s)
607
698
  if 0 == (T_string & ts) then
608
699
  rs = stringify(s)
@@ -611,7 +702,7 @@ local function replace(s, from, to)
611
702
  else
612
703
  rs = stringify(s)
613
704
  end
614
- if type(from) == 'string' then
705
+ if type(from) == "string" then
615
706
  -- Plain string replacement (all occurrences)
616
707
  return (rs:gsub(escre(from), to))
617
708
  else
@@ -620,7 +711,6 @@ local function replace(s, from, to)
620
711
  end
621
712
  end
622
713
 
623
-
624
714
  -- Return a sub-array. Start and end are 0-based, end is exclusive.
625
715
  -- For numbers: clamp between start and end-1.
626
716
  -- For strings: substring from start to end.
@@ -643,12 +733,16 @@ local function slice(val, start, endidx, mutate)
643
733
  if start ~= nil then
644
734
  if start < 0 then
645
735
  endidx = vlen + start
646
- if endidx < 0 then endidx = 0 end
736
+ if endidx < 0 then
737
+ endidx = 0
738
+ end
647
739
  start = 0
648
740
  elseif endidx ~= nil then
649
741
  if endidx < 0 then
650
742
  endidx = vlen + endidx
651
- if endidx < 0 then endidx = 0 end
743
+ if endidx < 0 then
744
+ endidx = 0
745
+ end
652
746
  elseif vlen < endidx then
653
747
  endidx = vlen
654
748
  end
@@ -686,7 +780,9 @@ local function slice(val, start, endidx, mutate)
686
780
  else
687
781
  if islist(val) then
688
782
  if mutate then
689
- for i = 1, #val do val[i] = nil end
783
+ for i = 1, #val do
784
+ val[i] = nil
785
+ end
690
786
  return val
691
787
  end
692
788
  return setmetatable({}, { __jsontype = "array" })
@@ -699,7 +795,6 @@ local function slice(val, start, endidx, mutate)
699
795
  return val
700
796
  end
701
797
 
702
-
703
798
  -- Flatten nested lists to a given depth.
704
799
  local function flatten(val, depth)
705
800
  if not islist(val) then
@@ -710,7 +805,7 @@ local function flatten(val, depth)
710
805
  setmetatable(result, { __jsontype = "array" })
711
806
 
712
807
  for _, item in ipairs(val) do
713
- if (islist(item) or (type(item) == 'table' and next(item) == nil)) and depth > 0 then
808
+ if (islist(item) or (type(item) == "table" and next(item) == nil)) and depth > 0 then
714
809
  local sub = flatten(item, depth - 1)
715
810
  for _, v in ipairs(sub) do
716
811
  table.insert(result, v)
@@ -722,14 +817,22 @@ local function flatten(val, depth)
722
817
  return result
723
818
  end
724
819
 
725
-
726
820
  -- Pad a string or number.
727
821
  -- Positive padlen = right-pad (padEnd), negative padlen = left-pad (padStart).
728
822
  local function pad(val, padlen, padchar)
729
- val = S_string == type(val) and val or stringify(val)
823
+ -- Mirror TS canonical: stringify(null) is "null". Lua's nil corresponds
824
+ -- to JSON null when input doesn't carry the runner's __NULL__ marker;
825
+ -- explicitly coerce here so padding works the same as in TS.
826
+ if val == nil then
827
+ val = "null"
828
+ else
829
+ val = S_string == type(val) and val or stringify(val)
830
+ end
730
831
  padlen = padlen or 44
731
832
  padchar = padchar or S_SP
732
- if #padchar > 1 then padchar = padchar:sub(1, 1) end
833
+ if #padchar > 1 then
834
+ padchar = padchar:sub(1, 1)
835
+ end
733
836
 
734
837
  if padlen >= 0 then
735
838
  -- Right-pad (padEnd)
@@ -746,7 +849,6 @@ local function pad(val, padlen, padchar)
746
849
  return val
747
850
  end
748
851
 
749
-
750
852
  -- Delete a property from a node.
751
853
  delprop = function(parent, key)
752
854
  if not iskey(key) then
@@ -771,7 +873,6 @@ delprop = function(parent, key)
771
873
  return parent
772
874
  end
773
875
 
774
-
775
876
  -- Build a JSON map from alternating key, value arguments.
776
877
  local function jm(...)
777
878
  local kv = { ... }
@@ -779,7 +880,7 @@ local function jm(...)
779
880
  local o = {}
780
881
  local i = 0
781
882
  while i < kvsize do
782
- local k = getprop(kv, i, '$KEY' .. i)
883
+ local k = getprop(kv, i, "$KEY" .. i)
783
884
  k = S_string == type(k) and k or stringify(k)
784
885
  o[k] = getprop(kv, i + 1, nil)
785
886
  i = i + 2
@@ -787,7 +888,6 @@ local function jm(...)
787
888
  return o
788
889
  end
789
890
 
790
-
791
891
  -- Define a JSON Array using function arguments.
792
892
  local function jt(...)
793
893
  local v = { ... }
@@ -800,7 +900,6 @@ local function jt(...)
800
900
  return a
801
901
  end
802
902
 
803
-
804
903
  -- Concatenate strings, merging separator char as needed.
805
904
  -- Default separator is comma. When url=true, preserve protocol slashes.
806
905
  -- @param arr (table) Array of parts to join
@@ -826,15 +925,19 @@ local function join(arr, sep, url)
826
925
  for i = 1, #arr do
827
926
  local v = arr[i]
828
927
  local ts = typify(v)
829
- if (0 < (T_string & ts)) and v ~= S_MT and v ~= S_null then
830
- table.insert(string_items, { i - 1, v }) -- 0-based index, value
928
+ -- Canonical: keep only non-empty string parts and drop JSON nulls. Lua
929
+ -- arrays cannot hold a nil hole, so a JSON null list element is carried as
930
+ -- the placeholder string S_null ("null", null=false) or NULLMARK
931
+ -- ("__NULL__", null=true); both are dropped here.
932
+ if (0 < (T_string & ts)) and v ~= S_MT and v ~= S_null and v ~= NULLMARK then
933
+ table.insert(string_items, { i - 1, v }) -- 0-based index, value
831
934
  end
832
935
  end
833
936
 
834
937
  -- Step 2: Process each element to clean separators
835
938
  local processed = {}
836
939
  for _, item in ipairs(string_items) do
837
- local idx = item[1] -- 0-based original index
940
+ local idx = item[1] -- 0-based original index
838
941
  local s = item[2]
839
942
 
840
943
  if seppat ~= nil and seppat ~= S_MT then
@@ -853,8 +956,10 @@ local function join(arr, sep, url)
853
956
  end
854
957
 
855
958
  -- Collapse multiple seps between non-sep chars
856
- s = s:gsub("([^" .. seppat .. "])" .. seppat .. "+([^" .. seppat .. "])",
857
- "%1" .. sepdef .. "%2")
959
+ s = s:gsub(
960
+ "([^" .. seppat .. "])" .. seppat .. "+([^" .. seppat .. "])",
961
+ "%1" .. sepdef .. "%2"
962
+ )
858
963
  end
859
964
  end
860
965
 
@@ -866,19 +971,24 @@ local function join(arr, sep, url)
866
971
  return table.concat(processed, sepdef)
867
972
  end
868
973
 
869
-
870
974
  -- Safely stringify a value for humans (NOT JSON!)
871
975
  -- Strings are returned as-is (not quoted).
872
976
  -- @param val (any) The value to stringify
873
977
  -- @param maxlen (number) Optional maximum length for result
874
978
  -- @param pretty (boolean) Optional pretty mode with ANSI colors
875
979
  -- @return (string) String representation of the value
876
- local function stringify(val, maxlen, pretty)
980
+ stringify = function(val, maxlen, pretty)
877
981
  local valstr = S_MT
878
982
  pretty = pretty and true or false
879
983
 
984
+ -- Absent/undefined (canonical TS NONE): empty string (or "<>" pretty).
880
985
  if val == nil then
881
- return pretty and '<>' or valstr
986
+ return pretty and "<>" or valstr
987
+ end
988
+
989
+ -- JSON null (canonical TS null): JSON.stringify(null) === "null".
990
+ if val == NULLMARK then
991
+ return "null"
882
992
  end
883
993
 
884
994
  if type(val) == S_string then
@@ -896,26 +1006,28 @@ local function stringify(val, maxlen, pretty)
896
1006
  local function serialize(obj, seen)
897
1007
  seen = seen or {}
898
1008
 
899
- if type(obj) == 'table' and seen[obj] then
900
- return '...'
1009
+ if type(obj) == "table" and seen[obj] then
1010
+ return "..."
901
1011
  end
902
1012
 
903
1013
  local obj_type = type(obj)
904
1014
 
905
- if obj == nil then
906
- return 'null'
1015
+ if obj == nil or obj == NULLMARK then
1016
+ return "null"
907
1017
  elseif obj_type == S_number then
908
- if obj ~= obj then return 'null' end -- NaN
1018
+ if obj ~= obj then
1019
+ return "null"
1020
+ end -- NaN
909
1021
  -- Use integer representation for whole numbers
910
1022
  if obj % 1 == 0 then
911
- return string.format('%d', obj)
1023
+ return string.format("%d", obj)
912
1024
  end
913
1025
  return tostring(obj)
914
1026
  elseif obj_type == S_boolean then
915
1027
  return tostring(obj)
916
1028
  elseif obj_type == S_function then
917
- return 'null'
918
- elseif obj_type ~= 'table' then
1029
+ return "null"
1030
+ elseif obj_type ~= "table" then
919
1031
  return tostring(obj)
920
1032
  end
921
1033
 
@@ -938,9 +1050,9 @@ local function stringify(val, maxlen, pretty)
938
1050
  seen[obj] = nil
939
1051
 
940
1052
  if is_arr then
941
- return S_OS .. table.concat(parts, ',') .. S_CS
1053
+ return S_OS .. table.concat(parts, ",") .. S_CS
942
1054
  else
943
- return '{' .. table.concat(parts, ',') .. '}'
1055
+ return "{" .. table.concat(parts, ",") .. "}"
944
1056
  end
945
1057
  end
946
1058
 
@@ -951,31 +1063,33 @@ local function stringify(val, maxlen, pretty)
951
1063
  if success then
952
1064
  valstr = result
953
1065
  else
954
- valstr = '__STRINGIFY_FAILED__'
1066
+ valstr = "__STRINGIFY_FAILED__"
955
1067
  end
956
1068
  end
957
1069
 
958
1070
  -- Handle maxlen
959
1071
  if maxlen ~= nil and maxlen > -1 then
960
1072
  if maxlen < #valstr then
961
- valstr = string.sub(valstr, 1, maxlen - 3) .. '...'
1073
+ valstr = string.sub(valstr, 1, maxlen - 3) .. "..."
962
1074
  end
963
1075
  end
964
1076
 
965
1077
  if pretty then
966
1078
  local c = { 81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69 }
967
- local r = '\x1b[0m'
1079
+ local r = "\x1b[0m"
968
1080
  local d = 0
969
- local function cc(n) return '\x1b[38;5;' .. n .. 'm' end
1081
+ local function cc(n)
1082
+ return "\x1b[38;5;" .. n .. "m"
1083
+ end
970
1084
  local o = cc(c[1])
971
1085
  local t = o
972
1086
  for i = 1, #valstr do
973
1087
  local ch = valstr:sub(i, i)
974
- if ch == '{' or ch == S_OS then
1088
+ if ch == "{" or ch == S_OS then
975
1089
  d = d + 1
976
1090
  o = cc(c[(d % #c) + 1])
977
1091
  t = t .. o .. ch
978
- elseif ch == '}' or ch == S_CS then
1092
+ elseif ch == "}" or ch == S_CS then
979
1093
  t = t .. o .. ch
980
1094
  d = d - 1
981
1095
  o = cc(c[(d % #c) + 1])
@@ -989,15 +1103,14 @@ local function stringify(val, maxlen, pretty)
989
1103
  return valstr
990
1104
  end
991
1105
 
992
-
993
1106
  -- Convert a value to JSON string representation (matching JSON.stringify behavior).
994
1107
  local function jsonify(val, flags)
995
1108
  local str = S_null
996
1109
 
997
1110
  if val ~= nil then
998
1111
  local ok, result = pcall(function()
999
- local indent_size = getprop(flags, 'indent', 2)
1000
- local offset = getprop(flags, 'offset', 0)
1112
+ local indent_size = getprop(flags, "indent", 2)
1113
+ local offset = getprop(flags, "offset", 0)
1001
1114
 
1002
1115
  -- Recursive JSON serializer matching JSON.stringify(val, null, indent)
1003
1116
  local function ser(v, depth)
@@ -1006,22 +1119,27 @@ local function jsonify(val, flags)
1006
1119
  elseif type(v) == S_boolean then
1007
1120
  return tostring(v)
1008
1121
  elseif type(v) == S_number then
1009
- if v ~= v then return S_null end -- NaN
1122
+ if v ~= v then
1123
+ return S_null
1124
+ end -- NaN
1010
1125
  if v % 1 == 0 then
1011
- return string.format('%d', v)
1126
+ return string.format("%d", v)
1012
1127
  end
1013
1128
  return tostring(v)
1014
1129
  elseif type(v) == S_string then
1015
1130
  -- Escape string for JSON
1016
- local escaped = v:gsub('\\', '\\\\'):gsub('"', '\\"')
1017
- :gsub('\n', '\\n'):gsub('\r', '\\r'):gsub('\t', '\\t')
1131
+ local escaped = v:gsub("\\", "\\\\")
1132
+ :gsub('"', '\\"')
1133
+ :gsub("\n", "\\n")
1134
+ :gsub("\r", "\\r")
1135
+ :gsub("\t", "\\t")
1018
1136
  return '"' .. escaped .. '"'
1019
1137
  elseif type(v) == S_function then
1020
- return nil -- Functions are omitted in JSON
1021
- elseif type(v) == 'table' then
1138
+ return nil -- Functions are omitted in JSON
1139
+ elseif type(v) == "table" then
1022
1140
  if islist(v) then
1023
1141
  if #v == 0 then
1024
- return '[]'
1142
+ return "[]"
1025
1143
  end
1026
1144
  local parts = {}
1027
1145
  for i = 1, #v do
@@ -1029,35 +1147,44 @@ local function jsonify(val, flags)
1029
1147
  table.insert(parts, sv or S_null)
1030
1148
  end
1031
1149
  if indent_size == 0 then
1032
- return '[' .. table.concat(parts, ',') .. ']'
1150
+ return "[" .. table.concat(parts, ",") .. "]"
1033
1151
  end
1034
- local pad_str = string.rep(' ', indent_size * (depth + 1) + offset)
1035
- local close_pad = string.rep(' ', indent_size * depth + offset)
1036
- return '[\n' .. pad_str .. table.concat(parts, ',\n' .. pad_str) ..
1037
- '\n' .. close_pad .. ']'
1152
+ local pad_str = string.rep(" ", indent_size * (depth + 1) + offset)
1153
+ local close_pad = string.rep(" ", indent_size * depth + offset)
1154
+ return "[\n"
1155
+ .. pad_str
1156
+ .. table.concat(parts, ",\n" .. pad_str)
1157
+ .. "\n"
1158
+ .. close_pad
1159
+ .. "]"
1038
1160
  else
1039
1161
  -- Map/object
1040
1162
  local keys_list = keysof(v)
1041
1163
  if #keys_list == 0 then
1042
- return '{}'
1164
+ return "{}"
1043
1165
  end
1044
1166
  local parts = {}
1167
+ local kv_sep = indent_size == 0 and '":' or '": '
1045
1168
  for _, k in ipairs(keys_list) do
1046
1169
  local sv = ser(v[k], depth + 1)
1047
- if sv ~= nil then -- Skip undefined values
1048
- table.insert(parts, '"' .. k .. '": ' .. sv)
1170
+ if sv ~= nil then -- Skip undefined values
1171
+ table.insert(parts, '"' .. k .. kv_sep .. sv)
1049
1172
  end
1050
1173
  end
1051
1174
  if #parts == 0 then
1052
- return '{}'
1175
+ return "{}"
1053
1176
  end
1054
1177
  if indent_size == 0 then
1055
- return '{' .. table.concat(parts, ',') .. '}'
1178
+ return "{" .. table.concat(parts, ",") .. "}"
1056
1179
  end
1057
- local pad_str = string.rep(' ', indent_size * (depth + 1) + offset)
1058
- local close_pad = string.rep(' ', indent_size * depth + offset)
1059
- return '{\n' .. pad_str .. table.concat(parts, ',\n' .. pad_str) ..
1060
- '\n' .. close_pad .. '}'
1180
+ local pad_str = string.rep(" ", indent_size * (depth + 1) + offset)
1181
+ local close_pad = string.rep(" ", indent_size * depth + offset)
1182
+ return "{\n"
1183
+ .. pad_str
1184
+ .. table.concat(parts, ",\n" .. pad_str)
1185
+ .. "\n"
1186
+ .. close_pad
1187
+ .. "}"
1061
1188
  end
1062
1189
  end
1063
1190
  return S_null
@@ -1073,14 +1200,13 @@ local function jsonify(val, flags)
1073
1200
  if ok and result ~= nil then
1074
1201
  str = result
1075
1202
  else
1076
- str = '__JSONIFY_FAILED__'
1203
+ str = "__JSONIFY_FAILED__"
1077
1204
  end
1078
1205
  end
1079
1206
 
1080
1207
  return str
1081
1208
  end
1082
1209
 
1083
-
1084
1210
  -- Build a human friendly path string.
1085
1211
  -- @param val (any) The path as array or string
1086
1212
  -- @param startin (number) Optional start index
@@ -1090,18 +1216,22 @@ local function pathify(val, startin, endin)
1090
1216
  local pathstr = NONE
1091
1217
  local path = NONE
1092
1218
 
1093
- -- Convert input to path array
1219
+ -- Convert input to path array. A JSON null (NULLMARK) is a scalar value,
1220
+ -- not a string path, so it falls through to the <unknown-path:null> branch
1221
+ -- (canonical TS: pathify(null) === '<unknown-path:null>').
1094
1222
  if islist(val) then
1095
1223
  path = val
1224
+ elseif val == NULLMARK then
1225
+ path = NONE
1096
1226
  elseif type(val) == S_string then
1097
1227
  path = { val }
1098
1228
  setmetatable(path, {
1099
- __jsontype = "array"
1229
+ __jsontype = "array",
1100
1230
  })
1101
1231
  elseif type(val) == S_number then
1102
1232
  path = { val }
1103
1233
  setmetatable(path, {
1104
- __jsontype = "array"
1234
+ __jsontype = "array",
1105
1235
  })
1106
1236
  end
1107
1237
 
@@ -1118,7 +1248,7 @@ local function pathify(val, startin, endin)
1118
1248
  path = sliced
1119
1249
 
1120
1250
  if #path == 0 then
1121
- pathstr = '<root>'
1251
+ pathstr = "<root>"
1122
1252
  else
1123
1253
  -- Filter valid path elements using iskey
1124
1254
  local filtered = {}
@@ -1148,19 +1278,18 @@ local function pathify(val, startin, endin)
1148
1278
 
1149
1279
  -- Handle unknown paths
1150
1280
  if pathstr == NONE then
1151
- pathstr = '<unknown-path'
1281
+ pathstr = "<unknown-path"
1152
1282
  if val == NONE then
1153
1283
  pathstr = pathstr .. S_MT
1154
1284
  else
1155
1285
  pathstr = pathstr .. (S_CN .. stringify(val, 47))
1156
1286
  end
1157
- pathstr = pathstr .. '>'
1287
+ pathstr = pathstr .. ">"
1158
1288
  end
1159
1289
 
1160
1290
  return pathstr
1161
1291
  end
1162
1292
 
1163
-
1164
1293
  -- Set a value deep inside a node at a key path.
1165
1294
  local function setpath(store, path, val, injdef)
1166
1295
  local pathType = typify(path)
@@ -1202,7 +1331,7 @@ local function setpath(store, path, val, injdef)
1202
1331
 
1203
1332
  local lastKey = getelem(parts, -1)
1204
1333
 
1205
- if type(val) == 'table' and val['`$DELETE`'] then
1334
+ if type(val) == "table" and val["`$DELETE`"] then
1206
1335
  delprop(parent, lastKey)
1207
1336
  else
1208
1337
  setprop(parent, lastKey, val)
@@ -1211,13 +1340,13 @@ local function setpath(store, path, val, injdef)
1211
1340
  return parent
1212
1341
  end
1213
1342
 
1214
-
1215
1343
  -- Clone a JSON-like data structure.
1216
1344
  -- NOTE: function value references are copied, *not* cloned.
1217
1345
  -- @param val (any) The value to clone
1218
1346
  -- @param flags (table) Optional flags to control cloning behavior
1219
1347
  -- @return (any) Deep copy of the value
1220
1348
  local function clone(val, flags)
1349
+ val = denoval(val)
1221
1350
  -- Handle nil value
1222
1351
  if val == nil then
1223
1352
  return nil
@@ -1283,7 +1412,6 @@ local function clone(val, flags)
1283
1412
  return val
1284
1413
  end
1285
1414
 
1286
-
1287
1415
  -- Safely set a property. Undefined arguments and invalid keys are ignored.
1288
1416
  -- Returns the (possibly modified) parent.
1289
1417
  -- If the parent is a list, and the key is negative, prepend the value.
@@ -1329,8 +1457,12 @@ setprop = function(parent, key, val)
1329
1457
  return parent
1330
1458
  end
1331
1459
 
1332
-
1333
1460
  -- Walk a data structure depth first, applying a function to each value.
1461
+ -- The `path` argument passed to the before/after callbacks is a single
1462
+ -- mutable array per depth, shared across all callback invocations for the
1463
+ -- lifetime of this top-level walk call. Callbacks that need to store the
1464
+ -- path MUST clone it (e.g. via a shallow copy); the contents will otherwise
1465
+ -- be overwritten by subsequent visits.
1334
1466
  -- @param val (any) The value to walk
1335
1467
  -- @param before (function) Applied before descending into a node
1336
1468
  -- @param after (function) Applied after descending into a node
@@ -1338,14 +1470,22 @@ end
1338
1470
  -- @param key (any) Current key (for recursive calls)
1339
1471
  -- @param parent (table) Current parent (for recursive calls)
1340
1472
  -- @param path (table) Current path (for recursive calls)
1473
+ -- @param pool (table) Per-depth reusable path arrays (for recursive calls)
1341
1474
  -- @return (any) The transformed value
1342
- local function walk(val, before, after, maxdepth,
1343
- key, parent, path)
1344
- if NONE == path then
1345
- path = {}
1346
- setmetatable(path, { __jsontype = "array" })
1475
+ local function walk(val, before, after, maxdepth, key, parent, path, pool)
1476
+ val = denoval(val)
1477
+ if nil == pool then
1478
+ pool = {}
1479
+ local rootPath = {}
1480
+ setmetatable(rootPath, { __jsontype = "array" })
1481
+ pool[0] = rootPath
1482
+ end
1483
+ if nil == path then
1484
+ path = pool[0]
1347
1485
  end
1348
1486
 
1487
+ local depth = #path
1488
+
1349
1489
  local out
1350
1490
  if nil == before then
1351
1491
  out = val
@@ -1354,18 +1494,31 @@ local function walk(val, before, after, maxdepth,
1354
1494
  end
1355
1495
 
1356
1496
  maxdepth = (maxdepth ~= nil and maxdepth >= 0) and maxdepth or MAXDEPTH
1357
- if 0 == maxdepth or (path ~= nil and 0 < maxdepth and maxdepth <= #path) then
1497
+ if 0 == maxdepth or (0 < maxdepth and maxdepth <= depth) then
1358
1498
  return out
1359
1499
  end
1360
1500
 
1361
1501
  if isnode(out) then
1502
+ local childDepth = depth + 1
1503
+ local childPath = pool[childDepth]
1504
+ if nil == childPath then
1505
+ childPath = {}
1506
+ setmetatable(childPath, { __jsontype = "array" })
1507
+ pool[childDepth] = childPath
1508
+ end
1509
+ -- Sync prefix [1..depth] from the current path. Only needed once per
1510
+ -- parent: siblings share the same prefix and will each overwrite slot
1511
+ -- [childDepth] below.
1512
+ for i = 1, depth do
1513
+ childPath[i] = path[i]
1514
+ end
1515
+
1362
1516
  for _, item in ipairs(items(out)) do
1363
1517
  local ckey, child = item[1], item[2]
1364
1518
 
1365
- local childPath = flatten({ getdef(path, {}), S_MT .. tostring(ckey) })
1366
- setmetatable(childPath, { __jsontype = "array" })
1519
+ childPath[childDepth] = S_MT .. tostring(ckey)
1367
1520
 
1368
- setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath))
1521
+ setprop(out, ckey, walk(child, before, after, maxdepth, ckey, out, childPath, pool))
1369
1522
  end
1370
1523
  end
1371
1524
 
@@ -1376,7 +1529,6 @@ local function walk(val, before, after, maxdepth,
1376
1529
  return out
1377
1530
  end
1378
1531
 
1379
-
1380
1532
  -- Merge a list of values into each other. Later values have
1381
1533
  -- precedence. Nodes override scalars. Node kinds (list or map)
1382
1534
  -- override each other, and do *not* merge. The first element is
@@ -1385,8 +1537,9 @@ end
1385
1537
  -- @param maxdepth (number) Optional maximum depth for merge
1386
1538
  -- @return (any) The merged result
1387
1539
  local function merge(val, maxdepth)
1540
+ val = denoval(val)
1388
1541
  local md = slice(getdef(maxdepth, MAXDEPTH), 0)
1389
- local out = NONE
1542
+ local out
1390
1543
 
1391
1544
  -- Handle edge cases
1392
1545
  if not islist(val) then
@@ -1443,9 +1596,8 @@ local function merge(val, maxdepth)
1443
1596
  -- NOT `a and b or c`: that idiom silently yields `c` whenever `b`
1444
1597
  -- is falsy, and `getprop` returns exactly that for a key the
1445
1598
  -- destination does not have. `dst[pI + 1]` then kept the PREVIOUS
1446
- -- sibling's destination node, so the next key descended into it:
1447
- -- merging {auth={prefix}} with {auth={...}, feature={...}} left
1448
- -- `auth` and `feature` as ONE table holding the union of both.
1599
+ -- sibling's destination node, so the next key descended into it and
1600
+ -- the two ended up sharing ONE table holding the union of both.
1449
1601
  if 0 < pI then
1450
1602
  dst[pI + 1] = getprop(dst[pI], key)
1451
1603
  end
@@ -1453,8 +1605,7 @@ local function merge(val, maxdepth)
1453
1605
 
1454
1606
  -- Destination empty, so create node (unless override is class instance).
1455
1607
  if NONE == tval and 0 == (T_instance & typify(bval)) then
1456
- cur[pI + 1] = islist(bval) and
1457
- setmetatable({}, { __jsontype = "array" }) or {}
1608
+ cur[pI + 1] = islist(bval) and setmetatable({}, { __jsontype = "array" }) or {}
1458
1609
 
1459
1610
  -- Matching override and destination so continue with their values.
1460
1611
  elseif typify(bval) == typify(tval) then
@@ -1487,14 +1638,12 @@ local function merge(val, maxdepth)
1487
1638
 
1488
1639
  if 0 == md then
1489
1640
  out = getelem(list, -1)
1490
- out = islist(out) and setmetatable({}, { __jsontype = "array" })
1491
- or ismap(out) and {} or out
1641
+ out = islist(out) and setmetatable({}, { __jsontype = "array" }) or ismap(out) and {} or out
1492
1642
  end
1493
1643
 
1494
1644
  return out
1495
1645
  end
1496
1646
 
1497
-
1498
1647
  -- Get a value deep inside a node using a key path.
1499
1648
  -- @param store (table) The data store to search in
1500
1649
  -- @param path (string|table|number) The path to the value
@@ -1511,7 +1660,7 @@ getpath = function(store, path, injdef)
1511
1660
  local pos = 1
1512
1661
  local len = #path
1513
1662
  while pos <= len do
1514
- local dotpos = path:find('.', pos, true)
1663
+ local dotpos = path:find(".", pos, true)
1515
1664
  if dotpos then
1516
1665
  table.insert(parts, path:sub(pos, dotpos - 1))
1517
1666
  pos = dotpos + 1
@@ -1523,14 +1672,12 @@ getpath = function(store, path, injdef)
1523
1672
  if pos == 1 then
1524
1673
  -- Empty string
1525
1674
  parts = { S_MT }
1526
- elseif pos == len + 1 then
1527
- -- Normal end
1528
- else
1675
+ elseif pos ~= len + 1 then
1529
1676
  -- Path ends with a dot
1530
1677
  table.insert(parts, S_MT)
1531
1678
  end
1532
1679
  -- Handle trailing dot: "a." -> ["a", ""]
1533
- if len > 0 and path:sub(len, len) == '.' then
1680
+ if len > 0 and path:sub(len, len) == "." then
1534
1681
  table.insert(parts, S_MT)
1535
1682
  end
1536
1683
  elseif type(path) == S_number then
@@ -1543,13 +1690,12 @@ getpath = function(store, path, injdef)
1543
1690
  local base = getprop(injdef, S_base)
1544
1691
  local src = getprop(store, base, store)
1545
1692
  local numparts = #parts
1546
- local dparent = getprop(injdef, 'dparent')
1693
+ local dparent = getprop(injdef, "dparent")
1547
1694
 
1548
1695
  -- An empty path (incl empty string) just finds the store.
1549
1696
  if path == nil or store == nil or (1 == numparts and S_MT == parts[1]) then
1550
1697
  val = src
1551
1698
  elseif 0 < numparts then
1552
-
1553
1699
  -- Check for $ACTIONs
1554
1700
  if 1 == numparts then
1555
1701
  val = getprop(store, parts[1])
@@ -1559,34 +1705,34 @@ getpath = function(store, path, injdef)
1559
1705
  val = src
1560
1706
 
1561
1707
  -- Check for meta path syntax: field$=value or field$~value
1562
- local m1, m2, m3 = parts[1]:match("^([^$]+)%$([=~])(.+)$")
1708
+ local m1, _, m3 = parts[1]:match("^([^$]+)%$([=~])(.+)$")
1563
1709
  if m1 and injdef and injdef.meta then
1564
1710
  val = getprop(injdef.meta, m1)
1565
1711
  parts[1] = m3
1566
1712
  end
1567
1713
 
1568
- local dpath = getprop(injdef, 'dpath')
1714
+ local dpath = getprop(injdef, "dpath")
1569
1715
 
1570
1716
  local pI = 0
1571
1717
  while NONE ~= val and pI < numparts do
1572
- local part = parts[pI + 1] -- Lua 1-based
1718
+ local part = parts[pI + 1] -- Lua 1-based
1573
1719
 
1574
1720
  if injdef and S_DKEY == part then
1575
1721
  part = getprop(injdef, S_key)
1576
- elseif injdef and part and #part > 5 and part:sub(1, 5) == '$GET:' then
1722
+ elseif injdef and part and #part > 5 and part:sub(1, 5) == "$GET:" then
1577
1723
  -- $GET:path$ -> get store value, use as path part (strip trailing $)
1578
1724
  part = stringify(getpath(src, slice(part, 5, -1)))
1579
- elseif injdef and part and #part > 5 and part:sub(1, 5) == '$REF:' then
1725
+ elseif injdef and part and #part > 5 and part:sub(1, 5) == "$REF:" then
1580
1726
  -- $REF:refpath$ -> get spec value, use as path part (strip trailing $)
1581
1727
  part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1)))
1582
- elseif injdef and part and #part > 6 and part:sub(1, 6) == '$META:' then
1728
+ elseif injdef and part and #part > 6 and part:sub(1, 6) == "$META:" then
1583
1729
  -- $META:metapath$ -> get meta value, use as path part (strip trailing $)
1584
- part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
1730
+ part = stringify(getpath(getprop(injdef, "meta"), slice(part, 6, -1)))
1585
1731
  end
1586
1732
 
1587
- -- $$ escapes $
1588
- if part and type(part) == S_string then
1589
- part = part:gsub('%$%$', '$')
1733
+ -- $$ escapes $ (skip the pattern gsub for the common no-"$$" segment)
1734
+ if part and type(part) == S_string and part:find("$$", 1, true) then
1735
+ part = part:gsub("%$%$", "$")
1590
1736
  end
1591
1737
 
1592
1738
  if S_MT == part then
@@ -1631,7 +1777,7 @@ getpath = function(store, path, injdef)
1631
1777
  end
1632
1778
 
1633
1779
  -- Injdef may provide a custom handler to modify found value.
1634
- local handler = getprop(injdef, 'handler')
1780
+ local handler = getprop(injdef, "handler")
1635
1781
  if nil ~= injdef and isfunc(handler) then
1636
1782
  local ref = pathify(path)
1637
1783
  val = handler(injdef, val, ref, store)
@@ -1640,7 +1786,6 @@ getpath = function(store, path, injdef)
1640
1786
  return val
1641
1787
  end
1642
1788
 
1643
-
1644
1789
  -- Injection "class" for managing injection state.
1645
1790
  -- Methods: descend, child, setval
1646
1791
 
@@ -1672,21 +1817,37 @@ function Injection:new(val, parent)
1672
1817
  return o
1673
1818
  end
1674
1819
 
1675
-
1676
1820
  function Injection:__tostring()
1677
- return 'INJ' .. S_CN ..
1678
- pad(pathify(self.path, 1)) ..
1679
- (MODENAME[self.mode] or '') .. (self.full and '/full' or '') .. S_CN ..
1680
- 'key=' .. self.keyI .. S_FS .. tostring(self.key) .. S_FS .. S_OS .. table.concat(self.keys, ',') .. S_CS ..
1681
- ' p=' .. stringify(self.parent, -1, 1) ..
1682
- ' m=' .. stringify(self.meta, -1, 1) ..
1683
- ' d/' .. pathify(self.dpath, 1) .. '=' .. stringify(self.dparent, -1, 1) ..
1684
- ' r=' .. stringify(getprop(getprop(self.nodes, 0), S_DTOP), -1, 1)
1821
+ return "INJ"
1822
+ .. S_CN
1823
+ .. pad(pathify(self.path, 1))
1824
+ .. (MODENAME[self.mode] or "")
1825
+ .. (self.full and "/full" or "")
1826
+ .. S_CN
1827
+ .. "key="
1828
+ .. self.keyI
1829
+ .. S_FS
1830
+ .. tostring(self.key)
1831
+ .. S_FS
1832
+ .. S_OS
1833
+ .. table.concat(self.keys, ",")
1834
+ .. S_CS
1835
+ .. " p="
1836
+ .. stringify(self.parent, -1, 1)
1837
+ .. " m="
1838
+ .. stringify(self.meta, -1, 1)
1839
+ .. " d/"
1840
+ .. pathify(self.dpath, 1)
1841
+ .. "="
1842
+ .. stringify(self.dparent, -1, 1)
1843
+ .. " r="
1844
+ .. stringify(getprop(getprop(self.nodes, 0), S_DTOP), -1, 1)
1685
1845
  end
1686
1846
 
1687
-
1688
1847
  function Injection:descend()
1689
- if self.meta.__d == nil then self.meta.__d = 0 end
1848
+ if self.meta.__d == nil then
1849
+ self.meta.__d = 0
1850
+ end
1690
1851
  self.meta.__d = self.meta.__d + 1
1691
1852
 
1692
1853
  local parentkey = getelem(self.path, -2)
@@ -1700,7 +1861,7 @@ function Injection:descend()
1700
1861
  self.dparent = getprop(self.dparent, parentkey)
1701
1862
 
1702
1863
  local lastpart = getelem(self.dpath, -1)
1703
- if lastpart == '$:' .. tostring(parentkey) then
1864
+ if lastpart == "$:" .. tostring(parentkey) then
1704
1865
  self.dpath = slice(self.dpath, -1)
1705
1866
  else
1706
1867
  self.dpath = flatten({ self.dpath, parentkey })
@@ -1711,9 +1872,8 @@ function Injection:descend()
1711
1872
  return self.dparent
1712
1873
  end
1713
1874
 
1714
-
1715
1875
  function Injection:child(keyI, keys)
1716
- local key = strkey(keys[keyI + 1]) -- Lua 1-based
1876
+ local key = strkey(keys[keyI + 1]) -- Lua 1-based
1717
1877
  local val = self.val
1718
1878
 
1719
1879
  local cinj = Injection:new(getprop(val, key), val)
@@ -1738,9 +1898,8 @@ function Injection:child(keyI, keys)
1738
1898
  return cinj
1739
1899
  end
1740
1900
 
1741
-
1742
1901
  function Injection:setval(val, ancestor)
1743
- local parent = NONE
1902
+ local parent
1744
1903
  if ancestor == nil or ancestor < 2 then
1745
1904
  if NONE == val then
1746
1905
  self.parent = delprop(self.parent, self.key)
@@ -1760,7 +1919,6 @@ function Injection:setval(val, ancestor)
1760
1919
  return parent
1761
1920
  end
1762
1921
 
1763
-
1764
1922
  -- Inject values from a data store into a node recursively.
1765
1923
  -- @param val (any) The value to inject into
1766
1924
  -- @param store (table) The data store
@@ -1808,7 +1966,7 @@ local function inject(val, store, injdef)
1808
1966
  else
1809
1967
  nodekeys = {}
1810
1968
  for i = 1, #val do
1811
- table.insert(nodekeys, i - 1) -- 0-based indices
1969
+ table.insert(nodekeys, i - 1) -- 0-based indices
1812
1970
  end
1813
1971
  end
1814
1972
 
@@ -1833,10 +1991,6 @@ local function inject(val, store, injdef)
1833
1991
  -- Perform val mode injection
1834
1992
  inject(childinj.val, store, childinj)
1835
1993
 
1836
- -- The injection may modify child processing.
1837
- nkI = childinj.keyI
1838
- nodekeys = childinj.keys
1839
-
1840
1994
  -- Perform key:post mode injection
1841
1995
  childinj.mode = M_KEYPOST
1842
1996
  _injectstr(nodekey, store, childinj)
@@ -1847,7 +2001,6 @@ local function inject(val, store, injdef)
1847
2001
 
1848
2002
  nkI = nkI + 1
1849
2003
  end
1850
-
1851
2004
  elseif S_string == valtype then
1852
2005
  inj.mode = M_VAL
1853
2006
  val = _injectstr(val, store, inj)
@@ -1869,17 +2022,15 @@ local function inject(val, store, injdef)
1869
2022
  return getprop(inj.parent, S_DTOP)
1870
2023
  end
1871
2024
 
1872
-
1873
2025
  -- Delete a key from a map or list.
1874
2026
  local function transform_DELETE(inj)
1875
2027
  inj:setval(NONE)
1876
2028
  return NONE
1877
2029
  end
1878
2030
 
1879
-
1880
2031
  -- Copy value from source data.
1881
2032
  local function transform_COPY(inj, _val)
1882
- local ijname = 'COPY'
2033
+ local ijname = "COPY"
1883
2034
 
1884
2035
  if not checkPlacement(M_VAL, ijname, T_any, inj) then
1885
2036
  return NONE
@@ -1890,7 +2041,6 @@ local function transform_COPY(inj, _val)
1890
2041
  return out
1891
2042
  end
1892
2043
 
1893
-
1894
2044
  -- As a value, inject the key of the parent node.
1895
2045
  local function transform_KEY(inj)
1896
2046
  local mode, path, parent = inj.mode, inj.path, inj.parent
@@ -1909,14 +2059,12 @@ local function transform_KEY(inj)
1909
2059
  return getprop(getprop(parent, S_BANNO), S_KEY, getelem(path, -2))
1910
2060
  end
1911
2061
 
1912
-
1913
2062
  -- Store annotation data about a node.
1914
2063
  local function transform_ANNO(inj)
1915
2064
  delprop(inj.parent, S_BANNO)
1916
2065
  return NONE
1917
2066
  end
1918
2067
 
1919
-
1920
2068
  -- Merge a list of objects into the current object.
1921
2069
  local function transform_MERGE(inj)
1922
2070
  local mode, key, parent = inj.mode, inj.key, inj.parent
@@ -1925,7 +2073,6 @@ local function transform_MERGE(inj)
1925
2073
 
1926
2074
  if M_KEYPRE == mode then
1927
2075
  out = key
1928
-
1929
2076
  elseif M_KEYPOST == mode then
1930
2077
  out = key
1931
2078
 
@@ -1946,7 +2093,6 @@ local function transform_MERGE(inj)
1946
2093
  return out
1947
2094
  end
1948
2095
 
1949
-
1950
2096
  -- Helper: injectChild
1951
2097
  local function injectChild(child, store, inj)
1952
2098
  local cinj = inj
@@ -1967,11 +2113,10 @@ local function injectChild(child, store, inj)
1967
2113
  return cinj
1968
2114
  end
1969
2115
 
1970
-
1971
2116
  -- Convert a node to a list.
1972
2117
  -- Format: ['`$EACH`', '`source-path-of-node`', child-template]
1973
2118
  local function transform_EACH(inj, _val, _ref, store)
1974
- local ijname = 'EACH'
2119
+ local ijname = "EACH"
1975
2120
 
1976
2121
  if not checkPlacement(M_VAL, ijname, T_list, inj) then
1977
2122
  return NONE
@@ -1980,14 +2125,18 @@ local function transform_EACH(inj, _val, _ref, store)
1980
2125
  -- Remove remaining keys to avoid spurious processing.
1981
2126
  local trimmed = slice(inj.keys, 0, 1)
1982
2127
  -- Replace keys in-place
1983
- for i = #inj.keys, 1, -1 do inj.keys[i] = nil end
1984
- for i, v in ipairs(trimmed) do inj.keys[i] = v end
2128
+ for i = #inj.keys, 1, -1 do
2129
+ inj.keys[i] = nil
2130
+ end
2131
+ for i, v in ipairs(trimmed) do
2132
+ inj.keys[i] = v
2133
+ end
1985
2134
 
1986
2135
  -- Get arguments: ['`$EACH`', 'source-path', child-template]
1987
2136
  local each_args = injectorArgs({ T_string, T_any }, slice(inj.parent, 1))
1988
2137
  local err = each_args[1]
1989
2138
  if NONE ~= err then
1990
- table.insert(inj.errs, '$' .. ijname .. ': ' .. err)
2139
+ table.insert(inj.errs, "$" .. ijname .. ": " .. err)
1991
2140
  return NONE
1992
2141
  end
1993
2142
  local srcpath = each_args[2]
@@ -1998,12 +2147,14 @@ local function transform_EACH(inj, _val, _ref, store)
1998
2147
  local src = getpath(srcstore, srcpath, inj)
1999
2148
  local srctype = typify(src)
2000
2149
 
2001
- local tcur = {}
2150
+ local tcur
2002
2151
  local tval = {}
2003
2152
  setmetatable(tval, { __jsontype = "array" })
2004
2153
 
2005
2154
  local tkey = getelem(inj.path, -2)
2006
- local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end)
2155
+ local target = getelem(inj.nodes, -2, function()
2156
+ return getelem(inj.nodes, -1)
2157
+ end)
2007
2158
 
2008
2159
  -- Create clones of the child template for each value of the source.
2009
2160
  if 0 < (T_list & srctype) then
@@ -2025,7 +2176,9 @@ local function transform_EACH(inj, _val, _ref, store)
2025
2176
  local srcvals = {}
2026
2177
  setmetatable(srcvals, { __jsontype = "array" })
2027
2178
  if islist(src) then
2028
- for i = 1, #src do table.insert(srcvals, src[i]) end
2179
+ for i = 1, #src do
2180
+ table.insert(srcvals, src[i])
2181
+ end
2029
2182
  elseif ismap(src) then
2030
2183
  for _, item in ipairs(items(src)) do
2031
2184
  table.insert(srcvals, item[2])
@@ -2042,14 +2195,14 @@ local function transform_EACH(inj, _val, _ref, store)
2042
2195
  table.insert(srcparts, p)
2043
2196
  end
2044
2197
  end
2045
- local dpath = flatten({ S_DTOP, srcparts, '$:' .. tostring(ckey) })
2198
+ local dpath = flatten({ S_DTOP, srcparts, "$:" .. tostring(ckey) })
2046
2199
 
2047
2200
  tcur = { [ckey] = srcvals }
2048
2201
 
2049
2202
  if 1 < size(tpath) then
2050
2203
  local pkey = getelem(inj.path, -3, S_DTOP)
2051
2204
  tcur = { [pkey] = tcur }
2052
- table.insert(dpath, '$:' .. tostring(pkey))
2205
+ table.insert(dpath, "$:" .. tostring(pkey))
2053
2206
  end
2054
2207
 
2055
2208
  local tinj = inj:child(0, { ckey })
@@ -2071,14 +2224,12 @@ local function transform_EACH(inj, _val, _ref, store)
2071
2224
  return getelem(rval, 0)
2072
2225
  end
2073
2226
 
2074
-
2075
2227
  -- Convert a node to a map.
2076
2228
  -- Format: { '`$PACK`':['`source-path`', child-template]}
2077
2229
  local function transform_PACK(inj, _val, _ref, store)
2078
- local mode, key, path, parent, nodes = inj.mode, inj.key, inj.path,
2079
- inj.parent, inj.nodes
2230
+ local key, path, parent, nodes = inj.key, inj.path, inj.parent, inj.nodes
2080
2231
 
2081
- local ijname = 'EACH'
2232
+ local ijname = "EACH"
2082
2233
 
2083
2234
  if not checkPlacement(M_KEYPRE, ijname, T_map, inj) then
2084
2235
  return NONE
@@ -2089,7 +2240,7 @@ local function transform_PACK(inj, _val, _ref, store)
2089
2240
  local pack_args = injectorArgs({ T_string, T_any }, args)
2090
2241
  local err = pack_args[1]
2091
2242
  if NONE ~= err then
2092
- table.insert(inj.errs, '$' .. ijname .. ': ' .. err)
2243
+ table.insert(inj.errs, "$" .. ijname .. ": " .. err)
2093
2244
  return NONE
2094
2245
  end
2095
2246
  local srcpath = pack_args[2]
@@ -2167,7 +2318,7 @@ local function transform_PACK(inj, _val, _ref, store)
2167
2318
  local srcnode = item[2]
2168
2319
  local kn
2169
2320
  if keypath == nil then
2170
- kn = srcI - 1 -- 0-based
2321
+ kn = srcI - 1 -- 0-based
2171
2322
  elseif type(keypath) == S_string and keypath:sub(1, 1) == S_BT then
2172
2323
  kn = inject(keypath, merge({ {}, store, { [S_DTOP] = srcnode } }, 1))
2173
2324
  else
@@ -2185,14 +2336,14 @@ local function transform_PACK(inj, _val, _ref, store)
2185
2336
  table.insert(srcparts, p)
2186
2337
  end
2187
2338
  end
2188
- local dpath = flatten({ S_DTOP, srcparts, '$:' .. tostring(ckey) })
2339
+ local dpath = flatten({ S_DTOP, srcparts, "$:" .. tostring(ckey) })
2189
2340
 
2190
2341
  local tcur = { [ckey] = tsrc }
2191
2342
 
2192
2343
  if 1 < size(tpath) then
2193
2344
  local pkey = getelem(inj.path, -3, S_DTOP)
2194
2345
  tcur = { [pkey] = tcur }
2195
- table.insert(dpath, '$:' .. tostring(pkey))
2346
+ table.insert(dpath, "$:" .. tostring(pkey))
2196
2347
  end
2197
2348
 
2198
2349
  local tinj = inj:child(0, { ckey })
@@ -2213,15 +2364,13 @@ local function transform_PACK(inj, _val, _ref, store)
2213
2364
  return NONE
2214
2365
  end
2215
2366
 
2216
-
2217
2367
  -- Placement labels for error messages.
2218
2368
  local PLACEMENT = {
2219
- [M_VAL] = 'value',
2369
+ [M_VAL] = "value",
2220
2370
  [M_KEYPRE] = S_key,
2221
2371
  [M_KEYPOST] = S_key,
2222
2372
  }
2223
2373
 
2224
-
2225
2374
  -- Check that a transform is used in the correct mode and parent type.
2226
2375
  checkPlacement = function(modes, ijname, parentTypes, inj)
2227
2376
  if 0 == (modes & inj.mode) then
@@ -2232,35 +2381,55 @@ checkPlacement = function(modes, ijname, parentTypes, inj)
2232
2381
  table.insert(expected, PLACEMENT[m])
2233
2382
  end
2234
2383
  end
2235
- table.insert(inj.errs, '$' .. ijname .. ': invalid placement as ' ..
2236
- PLACEMENT[inj.mode] .. ', expected: ' ..
2237
- table.concat(expected, ',') .. '.')
2384
+ table.insert(
2385
+ inj.errs,
2386
+ "$"
2387
+ .. ijname
2388
+ .. ": invalid placement as "
2389
+ .. PLACEMENT[inj.mode]
2390
+ .. ", expected: "
2391
+ .. table.concat(expected, ",")
2392
+ .. "."
2393
+ )
2238
2394
  return false
2239
2395
  end
2240
2396
  if not isempty(parentTypes) then
2241
2397
  local ptype = typify(inj.parent)
2242
2398
  if 0 == (parentTypes & ptype) then
2243
- table.insert(inj.errs, '$' .. ijname .. ': invalid placement in parent ' ..
2244
- typename(ptype) .. ', expected: ' .. typename(parentTypes) .. '.')
2399
+ table.insert(
2400
+ inj.errs,
2401
+ "$"
2402
+ .. ijname
2403
+ .. ": invalid placement in parent "
2404
+ .. typename(ptype)
2405
+ .. ", expected: "
2406
+ .. typename(parentTypes)
2407
+ .. "."
2408
+ )
2245
2409
  return false
2246
2410
  end
2247
2411
  end
2248
2412
  return true
2249
2413
  end
2250
2414
 
2251
-
2252
2415
  -- Validate and extract typed arguments from a list.
2253
2416
  injectorArgs = function(argTypes, args)
2254
2417
  local numargs = size(argTypes)
2255
2418
  local found = {}
2256
- found[1] = NONE -- err slot (1-based)
2419
+ found[1] = NONE -- err slot (1-based)
2257
2420
  for argI = 1, numargs do
2258
- local arg = getprop(args, argI - 1) -- 0-based access
2421
+ local arg = getprop(args, argI - 1) -- 0-based access
2259
2422
  local argType = typify(arg)
2260
2423
  if 0 == (argTypes[argI] & argType) then
2261
- found[1] = 'invalid argument: ' .. stringify(arg, 22) ..
2262
- ' (' .. typename(argType) .. ' at position ' .. argI ..
2263
- ') is not of type: ' .. typename(argTypes[argI]) .. '.'
2424
+ found[1] = "invalid argument: "
2425
+ .. stringify(arg, 22)
2426
+ .. " ("
2427
+ .. typename(argType)
2428
+ .. " at position "
2429
+ .. argI
2430
+ .. ") is not of type: "
2431
+ .. typename(argTypes[argI])
2432
+ .. "."
2264
2433
  break
2265
2434
  end
2266
2435
  found[1 + argI] = arg
@@ -2268,7 +2437,6 @@ injectorArgs = function(argTypes, args)
2268
2437
  return found
2269
2438
  end
2270
2439
 
2271
-
2272
2440
  -- Transform: resolve a reference to another part of the spec.
2273
2441
  -- Format: ['`$REF`', 'ref-path']
2274
2442
  local function transform_REF(inj, val, _ref, store)
@@ -2295,7 +2463,7 @@ local function transform_REF(inj, val, _ref, store)
2295
2463
  local hasSubRef = false
2296
2464
  if isnode(ref) then
2297
2465
  walk(ref, function(_k, v)
2298
- if '`$REF`' == v then
2466
+ if "`$REF`" == v then
2299
2467
  hasSubRef = true
2300
2468
  end
2301
2469
  return v
@@ -2308,7 +2476,7 @@ local function transform_REF(inj, val, _ref, store)
2308
2476
  local tpath = slice(inj.path, -1)
2309
2477
  local tcur = getpath(store, cpath)
2310
2478
  local tval = getpath(store, tpath)
2311
- local rval = NONE
2479
+ local rval
2312
2480
 
2313
2481
  if not hasSubRef or NONE ~= tval then
2314
2482
  local tinj = inj:child(0, { getelem(tpath, -1) })
@@ -2337,10 +2505,11 @@ local function transform_REF(inj, val, _ref, store)
2337
2505
  return val
2338
2506
  end
2339
2507
 
2340
-
2341
2508
  -- Named formatters for transform_FORMAT.
2342
2509
  local FORMATTER = {
2343
- identity = function(_k, v) return v end,
2510
+ identity = function(_k, v)
2511
+ return v
2512
+ end,
2344
2513
  upper = function(_k, v)
2345
2514
  return isnode(v) and v or string.upper(tostring(v))
2346
2515
  end,
@@ -2351,14 +2520,20 @@ local FORMATTER = {
2351
2520
  return isnode(v) and v or tostring(v)
2352
2521
  end,
2353
2522
  number = function(_k, v)
2354
- if isnode(v) then return v end
2523
+ if isnode(v) then
2524
+ return v
2525
+ end
2355
2526
  local n = tonumber(v)
2356
2527
  return (n == nil or n ~= n) and 0 or n
2357
2528
  end,
2358
2529
  integer = function(_k, v)
2359
- if isnode(v) then return v end
2530
+ if isnode(v) then
2531
+ return v
2532
+ end
2360
2533
  local n = tonumber(v)
2361
- if n == nil or n ~= n then n = 0 end
2534
+ if n == nil or n ~= n then
2535
+ n = 0
2536
+ end
2362
2537
  return math.floor(n)
2363
2538
  end,
2364
2539
  concat = function(k, v)
@@ -2366,7 +2541,7 @@ local FORMATTER = {
2366
2541
  local parts = {}
2367
2542
  for _, item in ipairs(items(v)) do
2368
2543
  local val = item[2]
2369
- table.insert(parts, isnode(val) and '' or tostring(val))
2544
+ table.insert(parts, isnode(val) and "" or tostring(val))
2370
2545
  end
2371
2546
  return table.concat(parts)
2372
2547
  end
@@ -2374,7 +2549,6 @@ local FORMATTER = {
2374
2549
  end,
2375
2550
  }
2376
2551
 
2377
-
2378
2552
  -- Transform: format values using named formatters.
2379
2553
  -- Format: ['`$FORMAT`', 'name', child]
2380
2554
  local function transform_FORMAT(inj, _val, _ref, store)
@@ -2391,7 +2565,9 @@ local function transform_FORMAT(inj, _val, _ref, store)
2391
2565
 
2392
2566
  -- Source data.
2393
2567
  local tkey = getelem(inj.path, -2)
2394
- local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end)
2568
+ local target = getelem(inj.nodes, -2, function()
2569
+ return getelem(inj.nodes, -1)
2570
+ end)
2395
2571
 
2396
2572
  local cinj = injectChild(child, store, inj)
2397
2573
  local resolved = cinj.val
@@ -2399,7 +2575,7 @@ local function transform_FORMAT(inj, _val, _ref, store)
2399
2575
  local formatter = (0 < (T_function & typify(name))) and name or getprop(FORMATTER, name)
2400
2576
 
2401
2577
  if NONE == formatter then
2402
- table.insert(inj.errs, '$FORMAT: unknown format: ' .. tostring(name) .. '.')
2578
+ table.insert(inj.errs, "$FORMAT: unknown format: " .. tostring(name) .. ".")
2403
2579
  return NONE
2404
2580
  end
2405
2581
 
@@ -2410,11 +2586,10 @@ local function transform_FORMAT(inj, _val, _ref, store)
2410
2586
  return out
2411
2587
  end
2412
2588
 
2413
-
2414
2589
  -- Apply a function to a value.
2415
2590
  -- Format: ['`$APPLY`', function, child]
2416
2591
  local function transform_APPLY(inj, _val, _ref, store)
2417
- local ijname = 'APPLY'
2592
+ local ijname = "APPLY"
2418
2593
 
2419
2594
  if not checkPlacement(M_VAL, ijname, T_list, inj) then
2420
2595
  return NONE
@@ -2423,12 +2598,14 @@ local function transform_APPLY(inj, _val, _ref, store)
2423
2598
  local found = injectorArgs({ T_function, T_any }, slice(inj.parent, 1))
2424
2599
  local err, apply, child = found[1], found[2], found[3]
2425
2600
  if NONE ~= err then
2426
- table.insert(inj.errs, '$' .. ijname .. ': ' .. err)
2601
+ table.insert(inj.errs, "$" .. ijname .. ": " .. err)
2427
2602
  return NONE
2428
2603
  end
2429
2604
 
2430
2605
  local tkey = getelem(inj.path, -2)
2431
- local target = getelem(inj.nodes, -2, function() return getelem(inj.nodes, -1) end)
2606
+ local target = getelem(inj.nodes, -2, function()
2607
+ return getelem(inj.nodes, -1)
2608
+ end)
2432
2609
 
2433
2610
  local cinj = injectChild(child, store, inj)
2434
2611
  local resolved = cinj.val
@@ -2439,7 +2616,6 @@ local function transform_APPLY(inj, _val, _ref, store)
2439
2616
  return out
2440
2617
  end
2441
2618
 
2442
-
2443
2619
  -- Transform data using spec.
2444
2620
  -- @param data (any) Source data to transform
2445
2621
  -- @param spec (any) Transform specification
@@ -2480,51 +2656,58 @@ local function transform(data, spec, injdef)
2480
2656
  {
2481
2657
  [S_DTOP] = dataClone,
2482
2658
 
2483
- [S_DSPEC] = function() return origspec end,
2484
-
2485
- ['$BT'] = function() return S_BT end,
2486
- ['$DS'] = function() return S_DS end,
2487
- ['$WHEN'] = function() return os.date('!%Y-%m-%dT%H:%M:%S.000Z') end,
2488
-
2489
- ['$DELETE'] = transform_DELETE,
2490
- ['$COPY'] = transform_COPY,
2491
- ['$KEY'] = transform_KEY,
2492
- ['$ANNO'] = transform_ANNO,
2493
- ['$MERGE'] = transform_MERGE,
2494
- ['$EACH'] = transform_EACH,
2495
- ['$PACK'] = transform_PACK,
2496
- ['$REF'] = transform_REF,
2497
- ['$FORMAT'] = transform_FORMAT,
2498
- ['$APPLY'] = transform_APPLY,
2659
+ [S_DSPEC] = function()
2660
+ return origspec
2661
+ end,
2662
+
2663
+ ["$BT"] = function()
2664
+ return S_BT
2665
+ end,
2666
+ ["$DS"] = function()
2667
+ return S_DS
2668
+ end,
2669
+ ["$WHEN"] = function()
2670
+ return os.date("!%Y-%m-%dT%H:%M:%S.000Z")
2671
+ end,
2672
+
2673
+ ["$DELETE"] = transform_DELETE,
2674
+ ["$COPY"] = transform_COPY,
2675
+ ["$KEY"] = transform_KEY,
2676
+ ["$ANNO"] = transform_ANNO,
2677
+ ["$MERGE"] = transform_MERGE,
2678
+ ["$EACH"] = transform_EACH,
2679
+ ["$PACK"] = transform_PACK,
2680
+ ["$REF"] = transform_REF,
2681
+ ["$FORMAT"] = transform_FORMAT,
2682
+ ["$APPLY"] = transform_APPLY,
2499
2683
  },
2500
2684
  extraTransforms,
2501
- { ['$ERRS'] = errs },
2685
+ { ["$ERRS"] = errs },
2502
2686
  }, 1)
2503
2687
 
2504
2688
  local out = inject(spec, store, injdef)
2505
2689
 
2506
2690
  local generr = 0 < size(errs) and not collect
2507
2691
  if generr then
2508
- error(table.concat(errs, ' | '))
2692
+ error(table.concat(errs, " | "))
2509
2693
  end
2510
2694
 
2511
2695
  return out
2512
2696
  end
2513
2697
 
2514
-
2515
2698
  -- A required string value. NOTE: Rejects empty strings.
2516
2699
  local function validate_STRING(inj)
2517
2700
  local out = getprop(inj.dparent, inj.key)
2518
2701
 
2519
2702
  local t = typify(out)
2520
2703
  if 0 == (T_string & t) then
2521
- local msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
2704
+ local msg = _invalidTypeMsg(inj.path, S_string, t, out, "V1010")
2522
2705
  table.insert(inj.errs, msg)
2523
2706
  return NONE
2524
2707
  end
2525
2708
 
2526
2709
  if S_MT == out then
2527
- local msg = 'Empty string at ' .. pathify(inj.path, 1)
2710
+ local msg = "Empty string at " .. pathify(inj.path, 1)
2528
2711
  table.insert(inj.errs, msg)
2529
2712
  return NONE
2530
2713
  end
@@ -2532,7 +2715,6 @@ local function validate_STRING(inj)
2532
2715
  return out
2533
2716
  end
2534
2717
 
2535
-
2536
2718
  -- A generic type validator. Ref is used to determine which type to check.
2537
2719
  local function validate_TYPE(inj, _val, ref)
2538
2720
  local tname = slice(ref, 1):lower()
@@ -2555,27 +2737,24 @@ local function validate_TYPE(inj, _val, ref)
2555
2737
 
2556
2738
  local t = typify(out)
2557
2739
  if 0 == (t & typev) then
2558
- table.insert(inj.errs, _invalidTypeMsg(inj.path, tname, t, out, 'V1001'))
2740
+ table.insert(inj.errs, _invalidTypeMsg(inj.path, tname, t, out, "V1001"))
2559
2741
  return NONE
2560
2742
  end
2561
2743
 
2562
2744
  return out
2563
2745
  end
2564
2746
 
2565
-
2566
2747
  -- Allow any value.
2567
2748
  local function validate_ANY(inj)
2568
2749
  local out = getprop(inj.dparent, inj.key)
2569
2750
  return out
2570
2751
  end
2571
2752
 
2572
-
2573
2753
  -- Specify child values for map or list.
2574
2754
  -- Map syntax: {'`$CHILD`': child-template }
2575
2755
  -- List syntax: ['`$CHILD`', child-template ]
2576
2756
  local function validate_CHILD(inj)
2577
- local mode, key, parent, keys, path = inj.mode, inj.key, inj.parent,
2578
- inj.keys, inj.path
2757
+ local mode, key, parent, keys, path = inj.mode, inj.key, inj.parent, inj.keys, inj.path
2579
2758
 
2580
2759
  -- Map syntax.
2581
2760
  if M_KEYPRE == mode then
@@ -2588,8 +2767,10 @@ local function validate_CHILD(inj)
2588
2767
  if NONE == tval then
2589
2768
  tval = {}
2590
2769
  elseif not ismap(tval) then
2591
- table.insert(inj.errs, _invalidTypeMsg(
2592
- slice(inj.path, 0, -1), S_object, typify(tval), tval, 'V0220'))
2770
+ table.insert(
2771
+ inj.errs,
2772
+ _invalidTypeMsg(slice(inj.path, 0, -1), S_object, typify(tval), tval, "V0220")
2773
+ )
2593
2774
  return NONE
2594
2775
  end
2595
2776
 
@@ -2610,7 +2791,7 @@ local function validate_CHILD(inj)
2610
2791
  if M_VAL == mode then
2611
2792
  if not islist(parent) then
2612
2793
  -- $CHILD was not inside a list.
2613
- table.insert(inj.errs, 'Invalid $CHILD as value')
2794
+ table.insert(inj.errs, "Invalid $CHILD as value")
2614
2795
  return NONE
2615
2796
  end
2616
2797
 
@@ -2623,34 +2804,45 @@ local function validate_CHILD(inj)
2623
2804
  end
2624
2805
 
2625
2806
  if not islist(inj.dparent) then
2626
- local msg = _invalidTypeMsg(
2627
- slice(inj.path, 0, -1), S_list, typify(inj.dparent), inj.dparent, 'V0230')
2807
+ local msg =
2808
+ _invalidTypeMsg(slice(inj.path, 0, -1), S_list, typify(inj.dparent), inj.dparent, "V0230")
2628
2809
  table.insert(inj.errs, msg)
2629
2810
  inj.keyI = size(parent)
2630
2811
  return inj.dparent
2631
2812
  end
2632
2813
 
2633
2814
  -- Clone children and reset inj key index.
2815
+ -- The inject child loop will now iterate over the cloned children,
2816
+ -- validating them against the current list values.
2634
2817
  for i = 1, #inj.dparent do
2635
2818
  parent[i] = clone(childtm)
2636
2819
  end
2637
2820
  slice(parent, 0, #inj.dparent, true)
2638
- inj.keyI = 0
2639
2821
 
2640
- local out = getprop(inj.dparent, 0)
2641
- return out
2822
+ -- NOTE: modifying inj! This extends the child value loop in inject
2823
+ -- to cover every cloned child.
2824
+ for ckeyI = size(keys), size(parent) - 1 do
2825
+ table.insert(keys, ckeyI)
2826
+ end
2827
+
2828
+ -- Restart the child value loop at the first element (the loop
2829
+ -- increments keyI on resume) so that the first element is also
2830
+ -- validated against the child template.
2831
+ inj.keyI = -1
2832
+
2833
+ -- SKIP leaves the cloned child template in place at the first
2834
+ -- element so the resumed loop can validate it.
2835
+ return SKIP
2642
2836
  end
2643
2837
 
2644
2838
  return NONE
2645
2839
  end
2646
2840
 
2647
-
2648
2841
  ----------------------------------------------------------
2649
2842
  -- Forward declaration for validate to resolve lack of function hoisting
2650
2843
  ----------------------------------------------------------
2651
2844
  local validate
2652
2845
 
2653
-
2654
2846
  -- Match at least one of the specified shapes.
2655
2847
  -- Syntax: ['`$ONE`', alt0, alt1, ...]
2656
2848
  local function validate_ONE(inj, _val, _ref, store)
@@ -2659,9 +2851,12 @@ local function validate_ONE(inj, _val, _ref, store)
2659
2851
  -- Only operate in val mode, since parent is a list.
2660
2852
  if M_VAL == mode then
2661
2853
  if not islist(parent) or 0 ~= keyI then
2662
- table.insert(inj.errs,
2663
- 'The $ONE validator at field ' .. pathify(inj.path, 1, 1) ..
2664
- ' must be the first element of an array.')
2854
+ table.insert(
2855
+ inj.errs,
2856
+ "The $ONE validator at field "
2857
+ .. pathify(inj.path, 1, 1)
2858
+ .. " must be the first element of an array."
2859
+ )
2665
2860
  return
2666
2861
  end
2667
2862
 
@@ -2675,9 +2870,12 @@ local function validate_ONE(inj, _val, _ref, store)
2675
2870
 
2676
2871
  local tvals = slice(parent, 1)
2677
2872
  if 0 == size(tvals) then
2678
- table.insert(inj.errs,
2679
- 'The $ONE validator at field ' .. pathify(inj.path, 1, 1) ..
2680
- ' must have at least one argument.')
2873
+ table.insert(
2874
+ inj.errs,
2875
+ "The $ONE validator at field "
2876
+ .. pathify(inj.path, 1, 1)
2877
+ .. " must have at least one argument."
2878
+ )
2681
2879
  return
2682
2880
  end
2683
2881
 
@@ -2708,19 +2906,24 @@ local function validate_ONE(inj, _val, _ref, store)
2708
2906
  for _, v in ipairs(tvals) do
2709
2907
  table.insert(valdesc, stringify(v))
2710
2908
  end
2711
- local valdesc_str = table.concat(valdesc, ', ')
2712
- valdesc_str = valdesc_str:gsub('`%$([A-Z]+)`', function(p1)
2909
+ local valdesc_str = table.concat(valdesc, ", ")
2910
+ valdesc_str = valdesc_str:gsub("`%$([A-Z]+)`", function(p1)
2713
2911
  return string.lower(p1)
2714
2912
  end)
2715
2913
 
2716
- table.insert(inj.errs,
2717
- _invalidTypeMsg(inj.path,
2718
- (1 < size(tvals) and 'one of ' or '') .. valdesc_str, typify(inj.dparent),
2719
- inj.dparent, 'V0210'))
2914
+ table.insert(
2915
+ inj.errs,
2916
+ _invalidTypeMsg(
2917
+ inj.path,
2918
+ (1 < size(tvals) and "one of " or "") .. valdesc_str,
2919
+ typify(inj.dparent),
2920
+ inj.dparent,
2921
+ "V0210"
2922
+ )
2923
+ )
2720
2924
  end
2721
2925
  end
2722
2926
 
2723
-
2724
2927
  -- Match exactly one of the specified values.
2725
2928
  -- Syntax: ['`$EXACT`', val1, val2, ...]
2726
2929
  local function validate_EXACT(inj)
@@ -2729,9 +2932,12 @@ local function validate_EXACT(inj)
2729
2932
  -- Only operate in val mode, since parent is a list.
2730
2933
  if M_VAL == mode then
2731
2934
  if not islist(parent) or 0 ~= keyI then
2732
- table.insert(inj.errs, 'The $EXACT validator at field ' ..
2733
- pathify(inj.path, 1, 1) ..
2734
- ' must be the first element of an array.')
2935
+ table.insert(
2936
+ inj.errs,
2937
+ "The $EXACT validator at field "
2938
+ .. pathify(inj.path, 1, 1)
2939
+ .. " must be the first element of an array."
2940
+ )
2735
2941
  return
2736
2942
  end
2737
2943
 
@@ -2745,9 +2951,12 @@ local function validate_EXACT(inj)
2745
2951
 
2746
2952
  local tvals = slice(parent, 1)
2747
2953
  if 0 == size(tvals) then
2748
- table.insert(inj.errs, 'The $EXACT validator at field ' ..
2749
- pathify(inj.path, 1, 1) ..
2750
- ' must have at least one argument.')
2954
+ table.insert(
2955
+ inj.errs,
2956
+ "The $EXACT validator at field "
2957
+ .. pathify(inj.path, 1, 1)
2958
+ .. " must have at least one argument."
2959
+ )
2751
2960
  return
2752
2961
  end
2753
2962
 
@@ -2773,19 +2982,26 @@ local function validate_EXACT(inj)
2773
2982
  for _, v in ipairs(tvals) do
2774
2983
  table.insert(valdesc, stringify(v))
2775
2984
  end
2776
- local valdesc_str = table.concat(valdesc, ', ')
2777
-
2778
- table.insert(inj.errs, _invalidTypeMsg(
2779
- inj.path,
2780
- (1 < size(inj.path) and '' or 'value ') ..
2781
- 'exactly equal to ' .. (1 == size(tvals) and '' or 'one of ') .. valdesc_str,
2782
- typify(inj.dparent), inj.dparent, 'V0110'))
2985
+ local valdesc_str = table.concat(valdesc, ", ")
2986
+
2987
+ table.insert(
2988
+ inj.errs,
2989
+ _invalidTypeMsg(
2990
+ inj.path,
2991
+ (1 < size(inj.path) and "" or "value ")
2992
+ .. "exactly equal to "
2993
+ .. (1 == size(tvals) and "" or "one of ")
2994
+ .. valdesc_str,
2995
+ typify(inj.dparent),
2996
+ inj.dparent,
2997
+ "V0110"
2998
+ )
2999
+ )
2783
3000
  else
2784
3001
  delprop(parent, key)
2785
3002
  end
2786
3003
  end
2787
3004
 
2788
-
2789
3005
  -- This is the "modify" argument to inject. Use this to perform
2790
3006
  -- generic validation. Runs *after* any special commands.
2791
3007
  _validation = function(pval, key, parent, inj)
@@ -2818,13 +3034,13 @@ _validation = function(pval, key, parent, inj)
2818
3034
 
2819
3035
  -- Type mismatch.
2820
3036
  if ptype ~= ctype and NONE ~= pval then
2821
- table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0010'))
3037
+ table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0010"))
2822
3038
  return
2823
3039
  end
2824
3040
 
2825
3041
  if ismap(cval) then
2826
3042
  if not ismap(pval) then
2827
- table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0020'))
3043
+ table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0020"))
2828
3044
  return
2829
3045
  end
2830
3046
 
@@ -2832,7 +3048,7 @@ _validation = function(pval, key, parent, inj)
2832
3048
  local pkeys = keysof(pval)
2833
3049
 
2834
3050
  -- Empty spec object {} means object can be open (any keys).
2835
- if 0 < size(pkeys) and true ~= getprop(pval, '`$OPEN`') then
3051
+ if 0 < size(pkeys) and true ~= getprop(pval, "`$OPEN`") then
2836
3052
  local badkeys = {}
2837
3053
 
2838
3054
  for _, ckey in ipairs(ckeys) do
@@ -2843,27 +3059,30 @@ _validation = function(pval, key, parent, inj)
2843
3059
 
2844
3060
  -- Closed object, so reject extra keys not in shape.
2845
3061
  if 0 < size(badkeys) then
2846
- local msg =
2847
- 'Unexpected keys at field ' .. pathify(inj.path, 1) .. S_VIZ .. table.concat(badkeys, ', ')
3062
+ local msg = "Unexpected keys at field "
3063
+ .. pathify(inj.path, 1)
3064
+ .. S_VIZ
3065
+ .. table.concat(badkeys, ", ")
2848
3066
  table.insert(inj.errs, msg)
2849
3067
  end
2850
3068
  else
2851
3069
  -- Object is open, so merge in extra keys.
2852
3070
  merge({ pval, cval })
2853
3071
  if isnode(pval) then
2854
- delprop(pval, '`$OPEN`')
3072
+ delprop(pval, "`$OPEN`")
2855
3073
  end
2856
3074
  end
2857
3075
  elseif islist(cval) then
2858
3076
  if not islist(pval) then
2859
- table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
3077
+ table.insert(inj.errs, _invalidTypeMsg(inj.path, typename(ptype), ctype, cval, "V0030"))
2860
3078
  end
2861
3079
  elseif exact then
2862
3080
  if cval ~= pval then
2863
- local pathmsg = 1 < size(inj.path)
2864
- and ('at field ' .. pathify(inj.path, 1) .. S_VIZ) or S_MT
2865
- table.insert(inj.errs, 'Value ' .. pathmsg .. tostring(cval) ..
2866
- ' should equal ' .. tostring(pval) .. '.')
3081
+ local pathmsg = 1 < size(inj.path) and ("at field " .. pathify(inj.path, 1) .. S_VIZ) or S_MT
3082
+ table.insert(
3083
+ inj.errs,
3084
+ "Value " .. pathmsg .. tostring(cval) .. " should equal " .. tostring(pval) .. "."
3085
+ )
2867
3086
  end
2868
3087
  else
2869
3088
  -- Spec value was a default, copy over data
@@ -2871,7 +3090,6 @@ _validation = function(pval, key, parent, inj)
2871
3090
  end
2872
3091
  end
2873
3092
 
2874
-
2875
3093
  -- Validate a data structure against a shape specification. The shape
2876
3094
  -- specification follows the "by example" principle. Plain data in
2877
3095
  -- the shape is treated as default values that also specify the
@@ -2928,7 +3146,7 @@ validate = function(data, spec, injdef)
2928
3146
  -- A special top level value to collect errors.
2929
3147
  {
2930
3148
  ["$ERRS"] = errs,
2931
- }
3149
+ },
2932
3150
  }, 1)
2933
3151
 
2934
3152
  local meta = (injdef and injdef.meta) or {}
@@ -2945,17 +3163,15 @@ validate = function(data, spec, injdef)
2945
3163
  local generr = (0 < size(errs) and not collect)
2946
3164
 
2947
3165
  if generr then
2948
- error(table.concat(errs, ' | '))
3166
+ error(table.concat(errs, " | "))
2949
3167
  end
2950
3168
 
2951
3169
  return out
2952
3170
  end
2953
3171
 
2954
-
2955
3172
  -- Select query operators
2956
3173
  -- ======================
2957
3174
 
2958
-
2959
3175
  local function select_AND(inj, _val, _ref, store)
2960
3176
  if M_KEYPRE == inj.mode then
2961
3177
  local terms = getprop(inj.parent, inj.key)
@@ -2976,8 +3192,10 @@ local function select_AND(inj, _val, _ref, store)
2976
3192
  })
2977
3193
 
2978
3194
  if 0 ~= size(terrs) then
2979
- table.insert(inj.errs,
2980
- 'AND:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(terms))
3195
+ table.insert(
3196
+ inj.errs,
3197
+ "AND:" .. pathify(ppath) .. S_VIZ .. stringify(point) .. " fail:" .. stringify(terms)
3198
+ )
2981
3199
  end
2982
3200
  end
2983
3201
 
@@ -2987,7 +3205,6 @@ local function select_AND(inj, _val, _ref, store)
2987
3205
  end
2988
3206
  end
2989
3207
 
2990
-
2991
3208
  local function select_OR(inj, _val, _ref, store)
2992
3209
  if M_KEYPRE == inj.mode then
2993
3210
  local terms = getprop(inj.parent, inj.key)
@@ -3016,12 +3233,13 @@ local function select_OR(inj, _val, _ref, store)
3016
3233
  end
3017
3234
  end
3018
3235
 
3019
- table.insert(inj.errs,
3020
- 'OR:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(terms))
3236
+ table.insert(
3237
+ inj.errs,
3238
+ "OR:" .. pathify(ppath) .. S_VIZ .. stringify(point) .. " fail:" .. stringify(terms)
3239
+ )
3021
3240
  end
3022
3241
  end
3023
3242
 
3024
-
3025
3243
  local function select_NOT(inj, _val, _ref, store)
3026
3244
  if M_KEYPRE == inj.mode then
3027
3245
  local term = getprop(inj.parent, inj.key)
@@ -3041,8 +3259,10 @@ local function select_NOT(inj, _val, _ref, store)
3041
3259
  })
3042
3260
 
3043
3261
  if 0 == size(terrs) then
3044
- table.insert(inj.errs,
3045
- 'NOT:' .. pathify(ppath) .. S_VIZ .. stringify(point) .. ' fail:' .. stringify(term))
3262
+ table.insert(
3263
+ inj.errs,
3264
+ "NOT:" .. pathify(ppath) .. S_VIZ .. stringify(point) .. " fail:" .. stringify(term)
3265
+ )
3046
3266
  end
3047
3267
 
3048
3268
  local gkey = getelem(inj.path, -2)
@@ -3051,7 +3271,6 @@ local function select_NOT(inj, _val, _ref, store)
3051
3271
  end
3052
3272
  end
3053
3273
 
3054
-
3055
3274
  local function select_CMP(inj, _val, ref, store)
3056
3275
  if M_KEYPRE == inj.mode then
3057
3276
  local term = getprop(inj.parent, inj.key)
@@ -3062,35 +3281,47 @@ local function select_CMP(inj, _val, ref, store)
3062
3281
 
3063
3282
  local pass = false
3064
3283
 
3065
- if '$GT' == ref and point > term then
3066
- pass = true
3067
- elseif '$LT' == ref and point < term then
3284
+ if "$GT" == ref and point > term then
3068
3285
  pass = true
3069
- elseif '$GTE' == ref and point >= term then
3286
+ elseif "$LT" == ref and point < term then
3070
3287
  pass = true
3071
- elseif '$LTE' == ref and point <= term then
3288
+ elseif "$GTE" == ref and point >= term then
3072
3289
  pass = true
3073
- elseif '$LIKE' == ref and stringify(point):match(term) then
3290
+ elseif "$LTE" == ref and point <= term then
3074
3291
  pass = true
3292
+ elseif "$LIKE" == ref then
3293
+ if re_test then
3294
+ if re_test(tostring(term), stringify(point)) then pass = true end
3295
+ else
3296
+ if stringify(point):match(term) then pass = true end
3297
+ end
3075
3298
  end
3076
3299
 
3077
3300
  if pass then
3078
3301
  local gp = getelem(inj.nodes, -2)
3079
3302
  setprop(gp, gkey, point)
3080
3303
  else
3081
- table.insert(inj.errs, 'CMP: ' .. pathify(ppath) .. S_VIZ .. stringify(point) ..
3082
- ' fail:' .. ref .. ' ' .. stringify(term))
3304
+ table.insert(
3305
+ inj.errs,
3306
+ "CMP: "
3307
+ .. pathify(ppath)
3308
+ .. S_VIZ
3309
+ .. stringify(point)
3310
+ .. " fail:"
3311
+ .. ref
3312
+ .. " "
3313
+ .. stringify(term)
3314
+ )
3083
3315
  end
3084
3316
  end
3085
3317
 
3086
3318
  return NONE
3087
3319
  end
3088
3320
 
3089
-
3090
3321
  -- Select children matching a query.
3091
3322
  local function select_fn(children, query)
3092
3323
  if not isnode(children) then
3093
- return {}
3324
+ return setmetatable({}, { __jsontype = "array" })
3094
3325
  end
3095
3326
 
3096
3327
  if ismap(children) then
@@ -3106,7 +3337,7 @@ local function select_fn(children, query)
3106
3337
  end
3107
3338
  end
3108
3339
 
3109
- local results = {}
3340
+ local results = setmetatable({}, { __jsontype = "array" })
3110
3341
  local injdef = {
3111
3342
  errs = {},
3112
3343
  meta = { [S_BEXACT] = true },
@@ -3119,14 +3350,14 @@ local function select_fn(children, query)
3119
3350
  ["$GTE"] = select_CMP,
3120
3351
  ["$LTE"] = select_CMP,
3121
3352
  ["$LIKE"] = select_CMP,
3122
- }
3353
+ },
3123
3354
  }
3124
3355
 
3125
3356
  local q = clone(query)
3126
3357
 
3127
3358
  walk(q, function(_k, v)
3128
3359
  if ismap(v) then
3129
- setprop(v, '`$OPEN`', getprop(v, '`$OPEN`', true))
3360
+ setprop(v, "`$OPEN`", getprop(v, "`$OPEN`", true))
3130
3361
  end
3131
3362
  return v
3132
3363
  end)
@@ -3144,25 +3375,25 @@ local function select_fn(children, query)
3144
3375
  return results
3145
3376
  end
3146
3377
 
3147
-
3148
3378
  -- Internal utilities
3149
3379
  -- ==================
3150
3380
 
3151
-
3152
3381
  -- Build a type validation error message.
3153
3382
  _invalidTypeMsg = function(path, needtype, vt, v, _whence)
3154
- local vs = (v == nil or v == S_null) and 'no value' or stringify(v)
3383
+ local vs = (v == nil or v == S_null) and "no value" or stringify(v)
3155
3384
  local vtname = type(vt) == S_number and typename(vt) or tostring(vt)
3156
3385
 
3157
- local msg = 'Expected ' .. (1 < #path and ('field ' .. pathify(path, 1)
3158
- .. ' to be ') or '') .. needtype .. ', but found ' .. ((v ~= nil and v ~= S_null)
3159
- and (vtname .. S_VIZ) or '') .. vs
3386
+ local msg = "Expected "
3387
+ .. (1 < #path and ("field " .. pathify(path, 1) .. " to be ") or "")
3388
+ .. needtype
3389
+ .. ", but found "
3390
+ .. ((v ~= nil and v ~= S_null) and (vtname .. S_VIZ) or "")
3391
+ .. vs
3160
3392
 
3161
- msg = msg .. '.'
3393
+ msg = msg .. "."
3162
3394
  return msg
3163
3395
  end
3164
3396
 
3165
-
3166
3397
  -- Default inject handler for transforms.
3167
3398
  _injecthandler = function(inj, val, ref, store)
3168
3399
  local out = val
@@ -3180,10 +3411,9 @@ _injecthandler = function(inj, val, ref, store)
3180
3411
  return out
3181
3412
  end
3182
3413
 
3183
-
3184
3414
  -- Validate handler - intercepts meta paths for validation.
3185
3415
  _validatehandler = function(inj, val, ref, store)
3186
- local out = val
3416
+ local out
3187
3417
 
3188
3418
  -- Check for meta path syntax: field$=value or field$~value
3189
3419
  local m = ref:match("^([^$]+)%$([=~])(.+)$")
@@ -3191,7 +3421,7 @@ _validatehandler = function(inj, val, ref, store)
3191
3421
 
3192
3422
  if ismetapath then
3193
3423
  local eq = ref:match("^[^$]+%$(.)") -- '=' or '~'
3194
- if '=' == eq then
3424
+ if "=" == eq then
3195
3425
  inj:setval({ S_BEXACT, val })
3196
3426
  else
3197
3427
  inj:setval(val)
@@ -3206,7 +3436,6 @@ _validatehandler = function(inj, val, ref, store)
3206
3436
  return out
3207
3437
  end
3208
3438
 
3209
-
3210
3439
  -- Inject store values into a string.
3211
3440
  _injectstr = function(val, store, inj)
3212
3441
  -- Can't inject into non-strings
@@ -3214,7 +3443,7 @@ _injectstr = function(val, store, inj)
3214
3443
  return S_MT
3215
3444
  end
3216
3445
 
3217
- local out = val
3446
+ local out
3218
3447
 
3219
3448
  -- Full value wrapped in backticks
3220
3449
  -- R_INJECTION_FULL: /^`(\$[A-Z]+|[^`]*)[0-9]*`$/
@@ -3257,11 +3486,14 @@ _injectstr = function(val, store, inj)
3257
3486
  return S_MT
3258
3487
  elseif type(found) == S_string then
3259
3488
  return found
3260
- elseif type(found) == 'table' then
3261
- local dkjson = require("dkjson")
3262
- local ok, result = pcall(dkjson.encode, found)
3263
- if ok and result then return result end
3264
- return islist(found) and '[...]' or '{...}'
3489
+ elseif type(found) == "table" then
3490
+ -- Use the in-tree jsonify (compact form) so the library proper has
3491
+ -- no third-party JSON dependency.
3492
+ local ok, result = pcall(jsonify, found, { indent = 0 })
3493
+ if ok and result then
3494
+ return result
3495
+ end
3496
+ return islist(found) and "[...]" or "{...}"
3265
3497
  else
3266
3498
  return tostring(found)
3267
3499
  end
@@ -3277,9 +3509,9 @@ _injectstr = function(val, store, inj)
3277
3509
  return out
3278
3510
  end
3279
3511
 
3280
-
3281
3512
  -- Define the StructUtility "class"
3282
3513
  local StructUtility = {
3514
+ NOVAL = NOVAL,
3283
3515
  clone = clone,
3284
3516
  delprop = delprop,
3285
3517
  escre = escre,
@@ -3319,6 +3551,13 @@ local StructUtility = {
3319
3551
  validate = validate,
3320
3552
  walk = walk,
3321
3553
 
3554
+ re_compile = _regex and _regex.compile or nil,
3555
+ re_find = re_find,
3556
+ re_find_all = re_find_all,
3557
+ re_replace = re_replace,
3558
+ re_test = re_test,
3559
+ re_escape = re_escape,
3560
+
3322
3561
  SKIP = SKIP,
3323
3562
  DELETE = DELETE,
3324
3563
 
@@ -3362,6 +3601,7 @@ end
3362
3601
 
3363
3602
  return {
3364
3603
  StructUtility = StructUtility,
3604
+ NOVAL = NOVAL,
3365
3605
  clone = clone,
3366
3606
  delprop = delprop,
3367
3607
  escre = escre,