tigerbeetle 0.0.34 → 0.0.37

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/ext/tb_client/extconf.rb +13 -13
  4. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  5. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  6. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  7. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  12. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  53. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  54. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  55. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  56. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  57. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  58. data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  69. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  72. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  73. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  74. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  75. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  76. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  77. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  78. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  79. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  80. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  81. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  82. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  121. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
  122. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  124. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  125. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  126. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  127. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  130. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  131. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  139. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  140. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  141. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  146. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  147. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  164. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  165. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  166. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  196. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  197. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  205. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  206. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  208. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  209. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  210. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  211. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  212. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  242. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  243. data/lib/tb_client/shared_lib.rb +12 -5
  244. data/lib/tigerbeetle/client.rb +1 -1
  245. data/lib/tigerbeetle/platforms.rb +9 -0
  246. data/lib/tigerbeetle/version.rb +2 -2
  247. data/tigerbeetle.gemspec +22 -5
  248. metadata +242 -3
  249. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,1034 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+
4
+ const vsr = @import("vsr.zig");
5
+ const stdx = vsr.stdx;
6
+ const constants = vsr.constants;
7
+ const IO = vsr.io.IO;
8
+ const Time = vsr.time.Time;
9
+ const StaticAllocator = @import("static_allocator.zig");
10
+ const MessagePool = vsr.message_pool.MessagePool;
11
+ const RingBufferType = stdx.RingBufferType;
12
+ const tb = vsr.tigerbeetle;
13
+
14
+ const Terminal = @import("repl/terminal.zig").Terminal;
15
+ const Completion = @import("repl/completion.zig").Completion;
16
+ const Parser = @import("repl/parser.zig").Parser;
17
+
18
+ const repl_history_entries = 256;
19
+ const repl_history_entry_bytes_with_nul = 512;
20
+ const repl_history_entry_bytes_without_nul = 511;
21
+
22
+ const ReplBufferBoundedArray = stdx.BoundedArrayType(u8, repl_history_entry_bytes_without_nul);
23
+
24
+ pub fn ReplType(comptime MessageBus: type) type {
25
+ const Client = vsr.ClientType(tb.Operation, MessageBus);
26
+
27
+ // Requires 512 * 256 == 128KiB of stack space.
28
+ const HistoryBuffer = RingBufferType(
29
+ [repl_history_entry_bytes_with_nul]u8,
30
+ .{ .array = repl_history_entries },
31
+ );
32
+
33
+ return struct {
34
+ event_loop_done: bool,
35
+ request_done: bool,
36
+
37
+ interactive: bool,
38
+ debug_logs: bool,
39
+
40
+ client: Client,
41
+ terminal: Terminal,
42
+ completion: Completion,
43
+ history: HistoryBuffer,
44
+
45
+ /// Fixed-capacity buffer for reading input strings.
46
+ buffer: ReplBufferBoundedArray,
47
+ /// Saved input string while navigating through history.
48
+ buffer_outside_history: ReplBufferBoundedArray,
49
+
50
+ arguments: std.ArrayListUnmanaged(u8),
51
+ message_pool: *MessagePool,
52
+ io: *IO,
53
+
54
+ static_allocator: StaticAllocator,
55
+
56
+ const Repl = @This();
57
+
58
+ fn fail(repl: *const Repl, comptime format: []const u8, arguments: anytype) !void {
59
+ if (!repl.interactive) {
60
+ try repl.terminal.print_error(format, arguments);
61
+ std.process.exit(1);
62
+ }
63
+
64
+ try repl.terminal.print(format, arguments);
65
+ }
66
+
67
+ fn debug(repl: *const Repl, comptime format: []const u8, arguments: anytype) !void {
68
+ if (repl.debug_logs) {
69
+ try repl.terminal.print("[Debug] " ++ format, arguments);
70
+ }
71
+ }
72
+
73
+ fn do_statement(
74
+ repl: *Repl,
75
+ statement: Parser.Statement,
76
+ ) !void {
77
+ try repl.debug("Running command: {}.\n", .{statement.operation});
78
+ switch (statement.operation) {
79
+ .none => {
80
+ // No input was parsed.
81
+ try repl.debug("No command was parsed, continuing.\n", .{});
82
+ },
83
+ .help => {
84
+ try repl.display_help();
85
+ },
86
+
87
+ .create_accounts,
88
+ .create_transfers,
89
+ .lookup_accounts,
90
+ .lookup_transfers,
91
+ .get_account_transfers,
92
+ .get_account_balances,
93
+ .query_accounts,
94
+ .query_transfers,
95
+ => |operation| {
96
+ const state_machine_operation = operation.state_machine_op();
97
+ try repl.send(
98
+ state_machine_operation,
99
+ statement.arguments,
100
+ );
101
+ },
102
+ }
103
+ }
104
+
105
+ const prompt = "> ";
106
+
107
+ fn read_until_newline_or_eof(
108
+ repl: *Repl,
109
+ ) !?[]const u8 {
110
+ repl.buffer.clear();
111
+ repl.buffer_outside_history.clear();
112
+
113
+ try repl.terminal.prompt_mode_set();
114
+ defer repl.terminal.prompt_mode_unset() catch {};
115
+
116
+ var terminal_screen = try repl.terminal.get_screen();
117
+
118
+ var buffer_index: usize = 0;
119
+ var history_index = repl.history.count;
120
+
121
+ var current_completion: []const u8 = "";
122
+
123
+ while (true) {
124
+ const user_input = try repl.terminal.read_user_input() orelse return null;
125
+
126
+ // Clear the completion menu when a match is selected or another key is pressed.
127
+ if (user_input != .tab and repl.completion.count() > 0) {
128
+ repl.completion.clear();
129
+ try repl.terminal.print("\x1b[{};1H\x1b[J\x1b[{};{}H", .{
130
+ terminal_screen.cursor_row + 1,
131
+ terminal_screen.cursor_row,
132
+ terminal_screen.cursor_column,
133
+ });
134
+ }
135
+
136
+ switch (user_input) {
137
+ .ctrld => {
138
+ if (repl.buffer.count() == 0 and buffer_index == 0) {
139
+ return null;
140
+ }
141
+ if (buffer_index < repl.buffer.count()) {
142
+ try repl.terminal.print("\x1b[{};{}H{s}\x20\x1b[{};{}H", .{
143
+ terminal_screen.cursor_row,
144
+ terminal_screen.cursor_column,
145
+ repl.buffer.const_slice()[buffer_index + 1 ..],
146
+ terminal_screen.cursor_row,
147
+ terminal_screen.cursor_column,
148
+ });
149
+ _ = repl.buffer.ordered_remove(buffer_index);
150
+ }
151
+ },
152
+ .ctrlc => {
153
+ // Move to end of line, print "^C" and abort the command.
154
+ const position_end_diff = @as(
155
+ isize,
156
+ @intCast(repl.buffer.count() - buffer_index),
157
+ );
158
+ terminal_screen.update_cursor_position(position_end_diff);
159
+ try repl.terminal.print("\x1b[{};{}H", .{
160
+ terminal_screen.cursor_row,
161
+ terminal_screen.cursor_column,
162
+ });
163
+ try repl.terminal.print("^C\n", .{});
164
+ repl.buffer.clear();
165
+ return &.{};
166
+ },
167
+ .newline => {
168
+ // Move to end of buffer, then return.
169
+ const position_end_diff = @as(
170
+ isize,
171
+ @intCast(repl.buffer.count() - buffer_index),
172
+ );
173
+ terminal_screen.update_cursor_position(position_end_diff);
174
+ try repl.terminal.print("\x1b[{};{}H", .{
175
+ terminal_screen.cursor_row,
176
+ terminal_screen.cursor_column,
177
+ });
178
+ try repl.terminal.print("\n", .{});
179
+ return repl.buffer.const_slice();
180
+ },
181
+ .printable => |character| {
182
+ if (repl.buffer.count() == repl_history_entry_bytes_without_nul) {
183
+ continue;
184
+ }
185
+
186
+ const is_append = buffer_index == repl.buffer.count();
187
+ if (is_append) {
188
+ terminal_screen.update_cursor_position(1);
189
+ try repl.terminal.print("{c}", .{character});
190
+ // Some terminals may not automatically move/scroll us down to the next
191
+ // row after appending a character at the last column. This can cause us
192
+ // to incorrectly report the cursor's position, so we force the terminal
193
+ // to do so by moving the cursor forward and backward one space.
194
+ if (terminal_screen.cursor_column == terminal_screen.columns) {
195
+ try repl.terminal.print("\x20\x1b[{};{}H", .{
196
+ terminal_screen.cursor_row,
197
+ terminal_screen.cursor_column,
198
+ });
199
+ }
200
+ } else {
201
+ // If we're inserting mid-buffer, we need to redraw everything that
202
+ // comes after as well. We'll track as if the cursor moved to the end of
203
+ // the buffer after redrawing, and move it back to one position after
204
+ // the newly inserted character.
205
+ const buffer_redraw_len: isize = @intCast(
206
+ repl.buffer.count() - buffer_index,
207
+ );
208
+ // It's crucial to update in two steps because the terminal may have
209
+ // scrolled down as part of the text redraw.
210
+ terminal_screen.update_cursor_position(buffer_redraw_len);
211
+ terminal_screen.update_cursor_position(1 - buffer_redraw_len);
212
+ try repl.terminal.print("{c}{s}\x1b[{};{}H", .{
213
+ character,
214
+ repl.buffer.const_slice()[buffer_index..],
215
+ terminal_screen.cursor_row,
216
+ terminal_screen.cursor_column,
217
+ });
218
+ }
219
+
220
+ repl.buffer.insert_at(buffer_index, character);
221
+ buffer_index += 1;
222
+ },
223
+ .backspace => if (buffer_index > 0) {
224
+ terminal_screen.update_cursor_position(-1);
225
+ // Move to new position, write the remaining buffer,
226
+ // write a space (\x20) to overwrite the last character,
227
+ // then move back to the new position.
228
+ try repl.terminal.print("\x1b[{};{}H{s}\x20\x1b[{};{}H", .{
229
+ terminal_screen.cursor_row,
230
+ terminal_screen.cursor_column,
231
+ // If we're deleting mid-buffer, we need to redraw everything that
232
+ // comes after as well.
233
+ if (buffer_index < repl.buffer.count())
234
+ repl.buffer.const_slice()[buffer_index..]
235
+ else
236
+ "",
237
+ terminal_screen.cursor_row,
238
+ terminal_screen.cursor_column,
239
+ });
240
+ buffer_index -= 1;
241
+ _ = repl.buffer.ordered_remove(buffer_index);
242
+ },
243
+ .delete => if (buffer_index < repl.buffer.count()) {
244
+ try repl.terminal.print("\x1b[{};{}H{s}\x20\x1b[{};{}H", .{
245
+ terminal_screen.cursor_row,
246
+ terminal_screen.cursor_column,
247
+ repl.buffer.const_slice()[buffer_index + 1 ..],
248
+ terminal_screen.cursor_row,
249
+ terminal_screen.cursor_column,
250
+ });
251
+ _ = repl.buffer.ordered_remove(buffer_index);
252
+ },
253
+ .tab => {
254
+ try repl.completion.split_and_complete(repl.buffer.slice(), buffer_index);
255
+
256
+ current_completion = try repl.completion.get_next_completion();
257
+
258
+ // Skip the current completion entry if adding it would overflow the buffer.
259
+ if ((repl.completion.prefix.count() + repl.completion.suffix.count() +
260
+ current_completion.len) >= repl_history_entry_bytes_without_nul)
261
+ {
262
+ continue;
263
+ }
264
+
265
+ // Move cursor to the start of the current row, clear the screen from start
266
+ // to end. Then print the leading buffer, currently selected completion and
267
+ // trailing buffer(if any).
268
+ terminal_screen.update_cursor_position(-@as(isize, @intCast(buffer_index)));
269
+
270
+ const buffer_row = terminal_screen.cursor_row;
271
+
272
+ terminal_screen.update_cursor_position(
273
+ @as(
274
+ isize,
275
+ @intCast(repl.completion.prefix.count() + current_completion.len),
276
+ ),
277
+ );
278
+
279
+ // \x1b[n;mH - Moves the cursor to row n, column m.
280
+ // \x1b[J - Clear the screen from current cursor position to end.
281
+ try repl.terminal.print("\x1b[{};1H\x1b[J{s}{s}{s}{s}\x20\x08", .{
282
+ buffer_row,
283
+ prompt,
284
+ repl.completion.prefix.const_slice(),
285
+ current_completion,
286
+ repl.completion.suffix.const_slice(),
287
+ });
288
+
289
+ // When cursor reaches the last row of terminal, scroll content upward
290
+ // to create space for displaying completion menu.
291
+ // \x1b[nS - Scroll whole page up by n lines; default 1 line.
292
+ // \x1b[nA - Moves cursor up n cells; default 1 cell up.
293
+ if (terminal_screen.cursor_row == terminal_screen.rows) {
294
+ try repl.terminal.print("\x1b[S\x1b[A", .{});
295
+ }
296
+
297
+ // Position cursor for completion menu. If the current buffer overflows to
298
+ // the next line after printing the prefix, selected completion and suffix,
299
+ // the completion menu should effectively be placed `row_delta` lines below
300
+ // the current buffer row.
301
+ const buffer_width =
302
+ repl.completion.prefix.count() +
303
+ repl.completion.suffix.count() +
304
+ current_completion.len + 1;
305
+ const row_delta = @divFloor(buffer_width, terminal_screen.columns) + 1;
306
+ try repl.terminal.print("\x1b[{};1H", .{buffer_row + row_delta});
307
+
308
+ // Display completion menu in the next line and highlight the currently
309
+ // selected completion.
310
+ var menu_width: usize = 0;
311
+ var match_itr = repl.completion.matches.iterator();
312
+
313
+ while (match_itr.next()) |match| {
314
+ const match_len = std.mem.indexOfScalar(u8, match[0..], '\x00').?;
315
+ menu_width += match_len + 2;
316
+
317
+ // \x1b[7m - Highlights the text by inverting the color. Inverts
318
+ // the text and background color.
319
+ // \x1b[0m - Reset the style property of text. In this case, resets
320
+ // the effect of \x1b[7m - color inversion.
321
+ if (std.mem.eql(u8, match[0..match_len], current_completion)) {
322
+ try repl.terminal.print("\x20\x1b[7m{s}\x1b[0m\x20", .{
323
+ match[0..match_len],
324
+ });
325
+ } else {
326
+ try repl.terminal.print("\x20{s}\x20", .{match[0..match_len]});
327
+ }
328
+ }
329
+
330
+ // If the completion menu overflows past the last row of terminal screen,
331
+ // move the cursor up by number of rows that would be overflown.
332
+ const menu_rows = @divFloor(menu_width, terminal_screen.columns) + 1;
333
+ const required_rows = terminal_screen.cursor_row + menu_rows;
334
+ const overflow_rows = @as(
335
+ isize,
336
+ @intCast(required_rows),
337
+ ) - @as(
338
+ isize,
339
+ @intCast(terminal_screen.rows),
340
+ );
341
+ if (overflow_rows > 0) {
342
+ terminal_screen.update_cursor_position(
343
+ -@as(isize, @intCast(terminal_screen.columns)) * overflow_rows,
344
+ );
345
+ }
346
+
347
+ try repl.terminal.print("\x1b[{};{}H", .{
348
+ terminal_screen.cursor_row,
349
+ terminal_screen.cursor_column,
350
+ });
351
+
352
+ // Re-fill the buffer with prefix, current match and suffix. Set the
353
+ // buffer index where the current selected match ends.
354
+ repl.buffer.clear();
355
+ repl.buffer.push_slice(repl.completion.prefix.slice());
356
+ repl.buffer.push_slice(current_completion);
357
+ repl.buffer.push_slice(repl.completion.suffix.slice());
358
+ buffer_index = repl.buffer.count() - repl.completion.suffix.count();
359
+ },
360
+ .left, .ctrlb => if (buffer_index > 0) {
361
+ terminal_screen.update_cursor_position(-1);
362
+ try repl.terminal.print("\x1b[{};{}H", .{
363
+ terminal_screen.cursor_row,
364
+ terminal_screen.cursor_column,
365
+ });
366
+ buffer_index -= 1;
367
+ },
368
+ .right, .ctrlf => if (buffer_index < repl.buffer.count()) {
369
+ terminal_screen.update_cursor_position(1);
370
+ try repl.terminal.print("\x1b[{};{}H", .{
371
+ terminal_screen.cursor_row,
372
+ terminal_screen.cursor_column,
373
+ });
374
+ buffer_index += 1;
375
+ },
376
+ .up, .ctrlp => if (history_index > 0) {
377
+ const history_index_next = history_index - 1;
378
+ const buffer_next_full = repl.history.get_ptr(history_index_next).?;
379
+ const buffer_next = std.mem.sliceTo(buffer_next_full, '\x00');
380
+ assert(buffer_next.len < repl_history_entry_bytes_with_nul);
381
+
382
+ // Move to the beginning of the current buffer.
383
+ terminal_screen.update_cursor_position(
384
+ -@as(isize, @intCast(buffer_index)),
385
+ );
386
+ const row_current_buffer_start = terminal_screen.cursor_row;
387
+
388
+ // Move to the end of the new buffer.
389
+ terminal_screen.update_cursor_position(
390
+ @as(isize, @intCast(buffer_next.len)),
391
+ );
392
+
393
+ try repl.terminal.print("\x1b[{};1H\x1b[J{s}{s}\x20\x1b[{};{}H", .{
394
+ row_current_buffer_start,
395
+ prompt,
396
+ buffer_next,
397
+ terminal_screen.cursor_row,
398
+ terminal_screen.cursor_column,
399
+ });
400
+
401
+ if (history_index == repl.history.count) {
402
+ repl.buffer_outside_history.clear();
403
+ repl.buffer_outside_history.push_slice(repl.buffer.const_slice());
404
+ }
405
+ history_index = history_index_next;
406
+
407
+ repl.buffer.clear();
408
+ repl.buffer.push_slice(buffer_next);
409
+ buffer_index = repl.buffer.count();
410
+ },
411
+ .down, .ctrln => if (history_index < repl.history.count) {
412
+ const history_index_next = history_index + 1;
413
+
414
+ const buffer_next = if (history_index_next == repl.history.count)
415
+ repl.buffer_outside_history.const_slice()
416
+ else brk: {
417
+ const buffer_next_full = repl.history.get_ptr(history_index_next).?;
418
+ const buffer_next = std.mem.sliceTo(buffer_next_full, '\x00');
419
+ assert(buffer_next.len < repl_history_entry_bytes_with_nul);
420
+ break :brk buffer_next;
421
+ };
422
+
423
+ history_index = history_index_next;
424
+
425
+ // Move to the beginning of the current buffer.
426
+ terminal_screen.update_cursor_position(
427
+ -@as(isize, @intCast(buffer_index)),
428
+ );
429
+ const row_current_buffer_start = terminal_screen.cursor_row;
430
+
431
+ // Move to the end of the new buffer.
432
+ terminal_screen.update_cursor_position(
433
+ @as(isize, @intCast(buffer_next.len)),
434
+ );
435
+
436
+ try repl.terminal.print("\x1b[{};1H\x1b[J{s}{s}\x20\x1b[{};{}H", .{
437
+ row_current_buffer_start,
438
+ prompt,
439
+ buffer_next,
440
+ terminal_screen.cursor_row,
441
+ terminal_screen.cursor_column,
442
+ });
443
+
444
+ repl.buffer.clear();
445
+ repl.buffer.push_slice(buffer_next);
446
+ buffer_index = repl.buffer.count();
447
+ },
448
+ .altf, .ctrlright => {
449
+ const forward = move_forward_by_word(repl.buffer.slice(), buffer_index);
450
+ terminal_screen.update_cursor_position(
451
+ @as(isize, @intCast(forward - buffer_index)),
452
+ );
453
+ try repl.terminal.print("\x1b[{};{}H", .{
454
+ terminal_screen.cursor_row,
455
+ terminal_screen.cursor_column,
456
+ });
457
+ buffer_index = forward;
458
+ },
459
+ .altb, .ctrlleft => {
460
+ const backward = move_backward_by_word(repl.buffer.slice(), buffer_index);
461
+ terminal_screen.update_cursor_position(
462
+ -@as(isize, @intCast(buffer_index - backward)),
463
+ );
464
+ try repl.terminal.print("\x1b[{};{}H", .{
465
+ terminal_screen.cursor_row,
466
+ terminal_screen.cursor_column,
467
+ });
468
+ buffer_index = backward;
469
+ },
470
+ .ctrla, .home => {
471
+ // Move to start of line.
472
+ const position_start_diff = -@as(isize, @intCast(buffer_index));
473
+ terminal_screen.update_cursor_position(position_start_diff);
474
+ try repl.terminal.print("\x1b[{};{}H", .{
475
+ terminal_screen.cursor_row,
476
+ terminal_screen.cursor_column,
477
+ });
478
+ buffer_index = 0;
479
+ },
480
+ .ctrle, .end => {
481
+ // Move to end of line.
482
+ const position_end_diff = @as(
483
+ isize,
484
+ @intCast(repl.buffer.count() - buffer_index),
485
+ );
486
+ terminal_screen.update_cursor_position(position_end_diff);
487
+ try repl.terminal.print("\x1b[{};{}H", .{
488
+ terminal_screen.cursor_row,
489
+ terminal_screen.cursor_column,
490
+ });
491
+ buffer_index = repl.buffer.count();
492
+ },
493
+ .ctrlk => {
494
+ // Clear screen from cursor.
495
+ try repl.terminal.print("\x1b[J", .{});
496
+ repl.buffer.resize(buffer_index) catch unreachable;
497
+ },
498
+ .ctrll => {
499
+ // Move to 0,0 and clear the screen from cursor, print the prompt, then ask
500
+ // the terminal for the new position.
501
+ try repl.terminal.print("\x1b[0;0H\x1b[J", .{});
502
+ try repl.terminal.print(prompt, .{});
503
+ terminal_screen = try repl.terminal.get_screen();
504
+
505
+ // Print whatever is in the buffer and move the cursor back to buffer_index.
506
+ terminal_screen.update_cursor_position(
507
+ @as(isize, @intCast(buffer_index)),
508
+ );
509
+
510
+ try repl.terminal.print("{s}\x1b[{};{}H", .{
511
+ repl.buffer.const_slice(),
512
+ terminal_screen.cursor_row,
513
+ terminal_screen.cursor_column,
514
+ });
515
+ },
516
+ .unhandled => {},
517
+ }
518
+ }
519
+ unreachable;
520
+ }
521
+
522
+ fn move_forward_by_word(buffer: []const u8, buffer_index: usize) usize {
523
+ var cur_pos = buffer_index;
524
+ while (cur_pos < buffer.len and std.ascii.isWhitespace(buffer[cur_pos])) {
525
+ cur_pos += 1;
526
+ }
527
+ while (cur_pos < buffer.len and !std.ascii.isWhitespace(buffer[cur_pos])) {
528
+ cur_pos += 1;
529
+ }
530
+ return cur_pos;
531
+ }
532
+
533
+ fn move_backward_by_word(buffer: []const u8, buffer_index: usize) usize {
534
+ var cur_pos = buffer_index;
535
+ while (cur_pos > 0 and std.ascii.isWhitespace(buffer[cur_pos - 1])) {
536
+ cur_pos -= 1;
537
+ }
538
+ while (cur_pos > 0 and !std.ascii.isWhitespace(buffer[cur_pos - 1])) {
539
+ cur_pos -= 1;
540
+ }
541
+ return cur_pos;
542
+ }
543
+
544
+ fn do_repl(
545
+ repl: *Repl,
546
+ arguments: *std.ArrayListUnmanaged(u8),
547
+ ) !void {
548
+ try repl.terminal.print(prompt, .{});
549
+ const input = repl.read_until_newline_or_eof() catch |err| {
550
+ repl.event_loop_done = true;
551
+ return err;
552
+ } orelse {
553
+ // EOF.
554
+ repl.event_loop_done = true;
555
+ try repl.fail("\nExiting.\n", .{});
556
+ return;
557
+ };
558
+
559
+ if (input.len > 0) {
560
+ const add_to_history = brk: {
561
+ const last_entry = repl.history.tail_ptr_const() orelse break :brk true;
562
+ const last_entry_str = std.mem.sliceTo(last_entry, '\x00');
563
+ break :brk !std.mem.eql(u8, last_entry_str, input);
564
+ };
565
+
566
+ if (add_to_history) {
567
+ // NB: Avoiding big stack allocations below.
568
+
569
+ assert(input.len < repl_history_entry_bytes_with_nul);
570
+
571
+ if (repl.history.full()) {
572
+ repl.history.advance_head();
573
+ }
574
+ const history_tail: *[repl_history_entry_bytes_with_nul]u8 =
575
+ repl.history.next_tail_ptr().?;
576
+ @memset(history_tail, '\x00');
577
+ stdx.copy_left(.inexact, u8, history_tail, input);
578
+ repl.history.advance_tail();
579
+ }
580
+ }
581
+
582
+ const statement = Parser.parse_statement(
583
+ input,
584
+ &repl.terminal,
585
+ arguments,
586
+ ) catch |err| {
587
+ switch (err) {
588
+ // These are parsing errors, so the REPL should
589
+ // not continue to execute this statement but can
590
+ // still accept new statements.
591
+ Parser.Error.IdentifierBad,
592
+ Parser.Error.OperationBad,
593
+ Parser.Error.ValueBad,
594
+ Parser.Error.KeyValuePairBad,
595
+ Parser.Error.KeyValuePairEqualMissing,
596
+ Parser.Error.SyntaxMatchNone,
597
+ Parser.Error.SliceOperationUnsupported,
598
+ // TODO(zig): This will be more convenient to express
599
+ // once https://github.com/ziglang/zig/issues/2473 is
600
+ // in.
601
+ => return,
602
+
603
+ // An unexpected error for which we do
604
+ // want the stacktrace.
605
+ error.AccessDenied,
606
+ error.BrokenPipe,
607
+ error.ConnectionResetByPeer,
608
+ error.DeviceBusy,
609
+ error.DiskQuota,
610
+ error.FileTooBig,
611
+ error.InputOutput,
612
+ error.InvalidArgument,
613
+ error.LockViolation,
614
+ error.NoSpaceLeft,
615
+ error.NotOpenForWriting,
616
+ error.OperationAborted,
617
+ error.OutOfMemory,
618
+ error.SystemResources,
619
+ error.Unexpected,
620
+ error.WouldBlock,
621
+ error.NoDevice,
622
+ error.ProcessNotFound,
623
+ => return err,
624
+ }
625
+ };
626
+ try repl.do_statement(statement);
627
+ }
628
+
629
+ fn display_help(repl: *Repl) !void {
630
+ try repl.terminal.print("TigerBeetle CLI Client {}\n" ++
631
+ \\ Hit enter after a semicolon to run a command.
632
+ \\ Ctrl+D to exit.
633
+ \\
634
+ \\Examples:
635
+ \\ create_accounts id=1 code=10 ledger=700 flags=linked|history, id=2 code=10 ledger=700;
636
+ \\ create_transfers id=1 debit_account_id=1 credit_account_id=2 amount=10 ledger=700 code=10;
637
+ \\ lookup_accounts id=1;
638
+ \\ lookup_accounts id=1, id=2;
639
+ \\ get_account_transfers account_id=1 flags=debits|credits;
640
+ \\ get_account_balances account_id=1 flags=debits|credits;
641
+ \\
642
+ \\
643
+ , .{constants.semver});
644
+ }
645
+
646
+ pub fn init(
647
+ parent_allocator: std.mem.Allocator,
648
+ io: *IO,
649
+ time: Time,
650
+ options: struct {
651
+ addresses: []const std.net.Address,
652
+ cluster_id: u128,
653
+ verbose: bool,
654
+ },
655
+ ) !Repl {
656
+ var static_allocator = StaticAllocator.init(parent_allocator);
657
+ const allocator = static_allocator.allocator();
658
+
659
+ var arguments = try std.ArrayListUnmanaged(u8).initCapacity(
660
+ allocator,
661
+ constants.message_body_size_max,
662
+ );
663
+ errdefer arguments.deinit(allocator);
664
+
665
+ var message_pool = try allocator.create(MessagePool);
666
+ errdefer allocator.destroy(message_pool);
667
+
668
+ message_pool.* = try MessagePool.init(allocator, .client);
669
+ errdefer message_pool.deinit(allocator);
670
+
671
+ const client_id = stdx.unique_u128();
672
+ const client = try Client.init(
673
+ allocator,
674
+ time,
675
+ message_pool,
676
+ .{
677
+ .id = client_id,
678
+ .cluster = options.cluster_id,
679
+ .replica_count = @intCast(options.addresses.len),
680
+ .aof_recovery = false,
681
+ .message_bus_options = .{
682
+ .configuration = options.addresses,
683
+ .io = io,
684
+ .trace = null,
685
+ },
686
+ },
687
+ );
688
+ errdefer client.deinit(allocator);
689
+
690
+ // Disable all dynamic allocation from this point onwards.
691
+ static_allocator.transition_from_init_to_static();
692
+
693
+ return .{
694
+ .client = client,
695
+ .debug_logs = options.verbose,
696
+ .request_done = true,
697
+ .event_loop_done = false,
698
+ .interactive = false,
699
+ .terminal = undefined, // Init on run.
700
+ .completion = undefined, // Init on run.
701
+ .history = HistoryBuffer.init(), // No corresponding deinit.
702
+ .buffer = .{},
703
+ .buffer_outside_history = .{},
704
+ .arguments = arguments,
705
+ .message_pool = message_pool,
706
+ .io = io,
707
+ .static_allocator = static_allocator,
708
+ };
709
+ }
710
+
711
+ pub fn deinit(repl: *Repl, allocator: std.mem.Allocator) void {
712
+ repl.static_allocator.transition_from_static_to_deinit();
713
+
714
+ repl.client.deinit(allocator);
715
+ repl.message_pool.deinit(allocator);
716
+ allocator.destroy(repl.message_pool);
717
+ repl.arguments.deinit(allocator);
718
+ }
719
+
720
+ pub fn run(repl: *Repl, statements: []const u8) !void {
721
+ repl.interactive = statements.len == 0;
722
+ try Terminal.init(&repl.terminal, repl.interactive); // No corresponding deinit.
723
+
724
+ try Completion.init(&repl.completion);
725
+
726
+ repl.client.register(register_callback, @intCast(@intFromPtr(repl)));
727
+ while (!repl.event_loop_done) {
728
+ repl.client.tick();
729
+ try repl.io.run_for_ns(constants.tick_ms * std.time.ns_per_ms);
730
+ }
731
+ repl.event_loop_done = false;
732
+
733
+ if (repl.interactive) {
734
+ try repl.display_help();
735
+ }
736
+
737
+ var statements_iterator = if (statements.len > 0)
738
+ std.mem.splitScalar(u8, statements, ';')
739
+ else
740
+ null;
741
+
742
+ while (!repl.event_loop_done) {
743
+ if (repl.request_done) {
744
+ repl.arguments.clearRetainingCapacity();
745
+ if (repl.interactive) {
746
+ try repl.do_repl(&repl.arguments);
747
+ } else blk: {
748
+ const statement_string = statements_iterator.?.next() orelse break :blk;
749
+
750
+ const statement = Parser.parse_statement(
751
+ statement_string,
752
+ &repl.terminal,
753
+ &repl.arguments,
754
+ ) catch |err| {
755
+ switch (err) {
756
+ // These are parsing errors and since this
757
+ // is not an interactive command, we should
758
+ // exit immediately. Parsing error info
759
+ // has already been emitted to stderr.
760
+ Parser.Error.IdentifierBad,
761
+ Parser.Error.OperationBad,
762
+ Parser.Error.ValueBad,
763
+ Parser.Error.KeyValuePairBad,
764
+ Parser.Error.KeyValuePairEqualMissing,
765
+ Parser.Error.SyntaxMatchNone,
766
+ Parser.Error.SliceOperationUnsupported,
767
+ // TODO: This will be more convenient to express
768
+ // once https://github.com/ziglang/zig/issues/2473 is
769
+ // in.
770
+ => std.posix.exit(1),
771
+
772
+ // An unexpected error for which we do
773
+ // want the stacktrace.
774
+ error.AccessDenied,
775
+ error.BrokenPipe,
776
+ error.ConnectionResetByPeer,
777
+ error.DeviceBusy,
778
+ error.DiskQuota,
779
+ error.FileTooBig,
780
+ error.InputOutput,
781
+ error.InvalidArgument,
782
+ error.LockViolation,
783
+ error.NoSpaceLeft,
784
+ error.NotOpenForWriting,
785
+ error.OperationAborted,
786
+ error.OutOfMemory,
787
+ error.SystemResources,
788
+ error.Unexpected,
789
+ error.WouldBlock,
790
+ error.NoDevice,
791
+ error.ProcessNotFound,
792
+ => return err,
793
+ }
794
+ };
795
+
796
+ try repl.do_statement(statement);
797
+ }
798
+ }
799
+
800
+ repl.client.tick();
801
+ try repl.io.run_for_ns(constants.tick_ms * std.time.ns_per_ms);
802
+ }
803
+ }
804
+
805
+ fn register_callback(
806
+ user_data: u128,
807
+ result: *const vsr.RegisterResult,
808
+ ) void {
809
+ _ = result;
810
+
811
+ const repl: *Repl = @ptrFromInt(@as(usize, @intCast(user_data)));
812
+ assert(!repl.event_loop_done);
813
+
814
+ repl.event_loop_done = true;
815
+ }
816
+
817
+ fn send(
818
+ repl: *Repl,
819
+ operation: tb.Operation,
820
+ arguments: *std.ArrayListUnmanaged(u8),
821
+ ) !void {
822
+ const operation_type = switch (operation) {
823
+ .create_accounts, .create_transfers => "create",
824
+ .get_account_transfers, .get_account_balances => "get",
825
+ .lookup_accounts, .lookup_transfers => "lookup",
826
+ .query_accounts, .query_transfers => "query",
827
+ else => unreachable,
828
+ };
829
+ const object_type = switch (operation) {
830
+ .create_accounts, .lookup_accounts, .query_accounts => "accounts",
831
+ .create_transfers, .lookup_transfers, .query_transfers => "transfers",
832
+ .get_account_transfers => "account transfers",
833
+ .get_account_balances => "account balances",
834
+ else => unreachable,
835
+ };
836
+
837
+ if (arguments.items.len == 0) {
838
+ try repl.fail(
839
+ "No {s} to {s}.\n",
840
+ .{ object_type, operation_type },
841
+ );
842
+ return;
843
+ }
844
+
845
+ repl.request_done = false;
846
+ try repl.debug("Sending command: {}.\n", .{operation});
847
+
848
+ const payload_size: u32 = @intCast(arguments.items.len);
849
+ const buffer: []u8 = buffer: {
850
+ arguments.expandToCapacity();
851
+ assert(arguments.items.len == constants.message_body_size_max);
852
+ break :buffer arguments.items;
853
+ };
854
+ var body_encoder = vsr.multi_batch.MultiBatchEncoder.init(buffer, .{
855
+ .element_size = operation.event_size(),
856
+ });
857
+ body_encoder.add(payload_size);
858
+ const bytes_written = body_encoder.finish();
859
+ assert(bytes_written > 0);
860
+
861
+ repl.client.request(
862
+ client_request_callback,
863
+ @intCast(@intFromPtr(repl)),
864
+ operation,
865
+ buffer[0..bytes_written],
866
+ );
867
+ }
868
+
869
+ fn display_object(repl: *Repl, object: anytype) !void {
870
+ assert(@TypeOf(object.*) == tb.Account or
871
+ @TypeOf(object.*) == tb.Transfer or
872
+ @TypeOf(object.*) == tb.AccountBalance);
873
+
874
+ try repl.terminal.print("{{\n", .{});
875
+ inline for (@typeInfo(@TypeOf(object.*)).@"struct".fields, 0..) |object_field, i| {
876
+ if (comptime std.mem.eql(u8, object_field.name, "reserved")) {
877
+ continue;
878
+ // No need to print out reserved.
879
+ }
880
+
881
+ if (i > 0) {
882
+ try repl.terminal.print(",\n", .{});
883
+ }
884
+
885
+ if (comptime std.mem.eql(u8, object_field.name, "flags")) {
886
+ try repl.terminal.print(" \"" ++ object_field.name ++ "\": [", .{});
887
+ var needs_comma = false;
888
+
889
+ inline for (@typeInfo(object_field.type).@"struct".fields) |flag_field| {
890
+ if (comptime !std.mem.eql(u8, flag_field.name, "padding")) {
891
+ if (@field(@field(object, "flags"), flag_field.name)) {
892
+ if (needs_comma) {
893
+ try repl.terminal.print(",", .{});
894
+ needs_comma = false;
895
+ }
896
+
897
+ try repl.terminal.print("\"{s}\"", .{flag_field.name});
898
+ needs_comma = true;
899
+ }
900
+ }
901
+ }
902
+
903
+ try repl.terminal.print("]", .{});
904
+ } else {
905
+ try repl.terminal.print(
906
+ " \"{s}\": \"{}\"",
907
+ .{ object_field.name, @field(object, object_field.name) },
908
+ );
909
+ }
910
+ }
911
+ try repl.terminal.print("\n}}\n", .{});
912
+ }
913
+
914
+ fn client_request_completed(
915
+ user_data: u128,
916
+ operation: tb.Operation,
917
+ timestamp: u64,
918
+ result: []const u8,
919
+ ) !void {
920
+ const repl: *Repl = @ptrFromInt(@as(usize, @intCast(user_data)));
921
+ assert(repl.request_done == false);
922
+ try repl.debug("Operation completed: {} timestamp={}.\n", .{ operation, timestamp });
923
+
924
+ defer {
925
+ repl.request_done = true;
926
+
927
+ if (!repl.interactive) {
928
+ repl.event_loop_done = true;
929
+ }
930
+ }
931
+
932
+ switch (operation) {
933
+ .create_accounts => {
934
+ const create_account_results = stdx.bytes_as_slice(
935
+ .exact,
936
+ tb.CreateAccountsResult,
937
+ result,
938
+ );
939
+ if (create_account_results.len > 0) {
940
+ for (create_account_results) |*reason| {
941
+ try repl.terminal.print(
942
+ "Failed to create account ({}): {any}.\n",
943
+ .{ reason.index, reason.result },
944
+ );
945
+ }
946
+ }
947
+ },
948
+ .lookup_accounts, .query_accounts => {
949
+ const account_results = stdx.bytes_as_slice(
950
+ .exact,
951
+ tb.Account,
952
+ result,
953
+ );
954
+ if (account_results.len == 0) {
955
+ try repl.fail("No accounts were found.\n", .{});
956
+ } else {
957
+ for (account_results) |*account| {
958
+ try repl.display_object(account);
959
+ }
960
+ }
961
+ },
962
+ .create_transfers => {
963
+ const create_transfer_results = stdx.bytes_as_slice(
964
+ .exact,
965
+ tb.CreateTransfersResult,
966
+ result,
967
+ );
968
+ if (create_transfer_results.len > 0) {
969
+ for (create_transfer_results) |*reason| {
970
+ try repl.terminal.print(
971
+ "Failed to create transfer ({}): {any}.\n",
972
+ .{ reason.index, reason.result },
973
+ );
974
+ }
975
+ }
976
+ },
977
+ .lookup_transfers,
978
+ .get_account_transfers,
979
+ .query_transfers,
980
+ => {
981
+ const transfer_results = stdx.bytes_as_slice(
982
+ .exact,
983
+ tb.Transfer,
984
+ result,
985
+ );
986
+ if (transfer_results.len == 0) {
987
+ try repl.fail("No transfers were found.\n", .{});
988
+ } else {
989
+ for (transfer_results) |*transfer| {
990
+ try repl.display_object(transfer);
991
+ }
992
+ }
993
+ },
994
+ .get_account_balances => {
995
+ const get_account_balances_results = stdx.bytes_as_slice(
996
+ .exact,
997
+ tb.AccountBalance,
998
+ result,
999
+ );
1000
+ if (get_account_balances_results.len == 0) {
1001
+ try repl.fail("No balances were found.\n", .{});
1002
+ } else {
1003
+ for (get_account_balances_results) |*balance| {
1004
+ try repl.display_object(balance);
1005
+ }
1006
+ }
1007
+ },
1008
+ else => unreachable,
1009
+ }
1010
+ }
1011
+
1012
+ fn client_request_callback(
1013
+ user_data: u128,
1014
+ operation_vsr: vsr.Operation,
1015
+ timestamp: u64,
1016
+ result: []u8,
1017
+ ) void {
1018
+ const operation = operation_vsr.cast(tb.Operation);
1019
+ const reply_decoder = vsr.multi_batch.MultiBatchDecoder.init(result, .{
1020
+ .element_size = operation.result_size(),
1021
+ }) catch unreachable;
1022
+ assert(reply_decoder.batch_count() == 1);
1023
+ client_request_completed(
1024
+ user_data,
1025
+ operation,
1026
+ timestamp,
1027
+ reply_decoder.peek(),
1028
+ ) catch |err| {
1029
+ const repl: *Repl = @ptrFromInt(@as(usize, @intCast(user_data)));
1030
+ repl.fail("Error in callback: {any}", .{err}) catch return;
1031
+ };
1032
+ }
1033
+ };
1034
+ }