@voxgig/sdkgen 0.35.2 → 0.37.0

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