farce 0.0.1.alpha2-x86-linux-gnu

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 (423) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +26 -0
  3. data/CONTRIBUTING.md +71 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +1524 -0
  6. data/SECURITY.md +10 -0
  7. data/docs/benchmarks.md +185 -0
  8. data/docs/gems/dry-types.md +290 -0
  9. data/docs/gems/msgpack.md +70 -0
  10. data/docs/gems/ractor-shim.md +63 -0
  11. data/docs/modes.md +628 -0
  12. data/docs/scopes.md +649 -0
  13. data/docs/variants.md +346 -0
  14. data/ext/ext_helper.rb +20 -0
  15. data/ext/farce/README.md +21 -0
  16. data/ext/farce/atom.c +1023 -0
  17. data/ext/farce/bounded_map.c +1683 -0
  18. data/ext/farce/containers.h +77 -0
  19. data/ext/farce/counter.c +399 -0
  20. data/ext/farce/darwin.c +100 -0
  21. data/ext/farce/depend +12 -0
  22. data/ext/farce/dict.c +1523 -0
  23. data/ext/farce/dict.h +152 -0
  24. data/ext/farce/drivers.c +237 -0
  25. data/ext/farce/exchanger.c +299 -0
  26. data/ext/farce/extconf.rb +99 -0
  27. data/ext/farce/farce.c +359 -0
  28. data/ext/farce/flag.c +288 -0
  29. data/ext/farce/io.c +338 -0
  30. data/ext/farce/lock.c +510 -0
  31. data/ext/farce/map.c +2240 -0
  32. data/ext/farce/priority_queue.c +2056 -0
  33. data/ext/farce/queue.c +1059 -0
  34. data/ext/farce/reactor.c +820 -0
  35. data/ext/farce/reactor.h +103 -0
  36. data/ext/farce/shareable.h +31 -0
  37. data/ext/farce/signal.c +350 -0
  38. data/ext/farce/transaction.c +354 -0
  39. data/ext/farce/transaction.h +40 -0
  40. data/ext/farce/tree_map.c +1953 -0
  41. data/ext/farce/trie.c +2020 -0
  42. data/ext/farce/unshareable.c +155 -0
  43. data/ext/farce/unshared_io_pool.h +234 -0
  44. data/ext/farce/unshared_signal.c +263 -0
  45. data/ext/farce/unshared_wait.h +193 -0
  46. data/ext/farce/unsupported.c +6 -0
  47. data/ext/farce/vector.c +1195 -0
  48. data/ext/farce/weak_map.c +1714 -0
  49. data/ext/java/org/farce/BoundedMap.java +394 -0
  50. data/ext/java/org/farce/FiberScheduler.java +141 -0
  51. data/ext/java/org/farce/PriorityKey.java +45 -0
  52. data/ext/java/org/farce/PriorityQueue.java +373 -0
  53. data/ext/java/org/farce/QueueSignal.java +28 -0
  54. data/ext/rebind/README.md +14 -0
  55. data/ext/rebind/extconf.rb +10 -0
  56. data/ext/rebind/rebind.c +186 -0
  57. data/lib/farce/_yard/internal.rb +13 -0
  58. data/lib/farce/_yard/macros.rb +61 -0
  59. data/lib/farce/_yard/ractor.rb +46 -0
  60. data/lib/farce/abstract/atom.rb +186 -0
  61. data/lib/farce/abstract/bounded_map.rb +232 -0
  62. data/lib/farce/abstract/collection.rb +151 -0
  63. data/lib/farce/abstract/concurrent_map.rb +381 -0
  64. data/lib/farce/abstract/counter.rb +193 -0
  65. data/lib/farce/abstract/duplicable_map.rb +229 -0
  66. data/lib/farce/abstract/exchanger.rb +26 -0
  67. data/lib/farce/abstract/flag.rb +71 -0
  68. data/lib/farce/abstract/lazy.rb +115 -0
  69. data/lib/farce/abstract/lease.rb +104 -0
  70. data/lib/farce/abstract/lease_map.rb +261 -0
  71. data/lib/farce/abstract/lease_pool.rb +91 -0
  72. data/lib/farce/abstract/lfu_map.rb +20 -0
  73. data/lib/farce/abstract/lru_map.rb +25 -0
  74. data/lib/farce/abstract/map.rb +345 -0
  75. data/lib/farce/abstract/molecule.rb +245 -0
  76. data/lib/farce/abstract/port.rb +74 -0
  77. data/lib/farce/abstract/priority_queue.rb +117 -0
  78. data/lib/farce/abstract/queue.rb +269 -0
  79. data/lib/farce/abstract/scheduler.rb +111 -0
  80. data/lib/farce/abstract/set.rb +910 -0
  81. data/lib/farce/abstract/sorted_set.rb +172 -0
  82. data/lib/farce/abstract/timer_queue.rb +136 -0
  83. data/lib/farce/abstract/tree_map.rb +269 -0
  84. data/lib/farce/abstract/value.rb +68 -0
  85. data/lib/farce/abstract/vector.rb +1126 -0
  86. data/lib/farce/abstract/weak_atom.rb +32 -0
  87. data/lib/farce/abstract/weak_key_map.rb +12 -0
  88. data/lib/farce/abstract/weak_map.rb +13 -0
  89. data/lib/farce/abstract/weak_set.rb +12 -0
  90. data/lib/farce/abstract/weak_value_map.rb +12 -0
  91. data/lib/farce/abstract.rb +17 -0
  92. data/lib/farce/atom.rb +293 -0
  93. data/lib/farce/class_mirror.rb +87 -0
  94. data/lib/farce/clock.rb +121 -0
  95. data/lib/farce/config.rb +229 -0
  96. data/lib/farce/counter.rb +71 -0
  97. data/lib/farce/deduper.rb +122 -0
  98. data/lib/farce/engine/jruby/bounded_map.rb +314 -0
  99. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  100. data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
  101. data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
  102. data/lib/farce/engine/jruby/map.rb +505 -0
  103. data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
  104. data/lib/farce/engine/jruby/signal.rb +147 -0
  105. data/lib/farce/engine/jruby.rb +63 -0
  106. data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
  107. data/lib/farce/engine/jvm/counter.rb +102 -0
  108. data/lib/farce/engine/jvm/extension.rb +32 -0
  109. data/lib/farce/engine/jvm/farce.jar +0 -0
  110. data/lib/farce/engine/jvm/flag.rb +79 -0
  111. data/lib/farce/engine/jvm/priority_queue.rb +217 -0
  112. data/lib/farce/engine/jvm/tree_map.rb +350 -0
  113. data/lib/farce/engine/jvm/types.rb +180 -0
  114. data/lib/farce/engine/jvm.rb +19 -0
  115. data/lib/farce/engine/ruby/3.4/farce.so +0 -0
  116. data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
  117. data/lib/farce/engine/ruby/3.4/port.rb +186 -0
  118. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
  119. data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
  120. data/lib/farce/engine/ruby/3.4/rebind.so +0 -0
  121. data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
  122. data/lib/farce/engine/ruby/4.0/farce.so +0 -0
  123. data/lib/farce/engine/ruby/4.0/port.rb +19 -0
  124. data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
  125. data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
  126. data/lib/farce/engine/ruby/4.0/rebind.so +0 -0
  127. data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
  128. data/lib/farce/engine/ruby/4.1/port.rb +21 -0
  129. data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
  130. data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
  131. data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
  132. data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
  133. data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
  134. data/lib/farce/engine/ruby/shared/lease.rb +26 -0
  135. data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
  136. data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
  137. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
  138. data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
  139. data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
  140. data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
  141. data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
  142. data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
  143. data/lib/farce/engine/ruby/shared/vault.rb +144 -0
  144. data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
  145. data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
  146. data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
  147. data/lib/farce/engine/ruby.rb +130 -0
  148. data/lib/farce/engine/shared/atom.rb +10 -0
  149. data/lib/farce/engine/shared/exchanger.rb +130 -0
  150. data/lib/farce/engine/shared/identity_key.rb +22 -0
  151. data/lib/farce/engine/shared/lease.rb +10 -0
  152. data/lib/farce/engine/shared/lease_pool.rb +10 -0
  153. data/lib/farce/engine/shared/main_scheduler.rb +20 -0
  154. data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
  155. data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
  156. data/lib/farce/engine/shared/port.rb +44 -0
  157. data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
  158. data/lib/farce/engine/shared/proxy_owner.rb +43 -0
  159. data/lib/farce/engine/shared/queue.rb +233 -0
  160. data/lib/farce/engine/shared/ractor_methods.rb +73 -0
  161. data/lib/farce/engine/shared/rebindable.rb +15 -0
  162. data/lib/farce/engine/shared/strict_atom.rb +73 -0
  163. data/lib/farce/engine/shared/strict_map.rb +117 -0
  164. data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
  165. data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
  166. data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
  167. data/lib/farce/engine/shared/trie.rb +317 -0
  168. data/lib/farce/engine/shared/trie_builder.rb +199 -0
  169. data/lib/farce/engine/shared/unshareable.rb +14 -0
  170. data/lib/farce/engine/shared/unshared_atom.rb +256 -0
  171. data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
  172. data/lib/farce/engine/shared/unshared_queue.rb +18 -0
  173. data/lib/farce/engine/shared/unshared_signal.rb +21 -0
  174. data/lib/farce/engine/shared/unshared_vector.rb +377 -0
  175. data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
  176. data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
  177. data/lib/farce/engine/shared/vault.rb +31 -0
  178. data/lib/farce/engine/shared/vector.rb +83 -0
  179. data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
  180. data/lib/farce/engine/shared/weak_atom.rb +20 -0
  181. data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
  182. data/lib/farce/engine/shared/weak_map/index.rb +249 -0
  183. data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
  184. data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
  185. data/lib/farce/engine/shared.rb +63 -0
  186. data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
  187. data/lib/farce/engine/truffleruby/lock.rb +121 -0
  188. data/lib/farce/engine/truffleruby/map.rb +656 -0
  189. data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
  190. data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
  191. data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
  192. data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
  193. data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
  194. data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
  195. data/lib/farce/engine/truffleruby/signal.rb +77 -0
  196. data/lib/farce/engine/truffleruby.rb +148 -0
  197. data/lib/farce/envelope.rb +335 -0
  198. data/lib/farce/error.rb +35 -0
  199. data/lib/farce/exchanger.rb +41 -0
  200. data/lib/farce/flag.rb +27 -0
  201. data/lib/farce/integrations/active_support/blank.rb +68 -0
  202. data/lib/farce/integrations/active_support/clock.rb +17 -0
  203. data/lib/farce/integrations/active_support/duplicable.rb +78 -0
  204. data/lib/farce/integrations/active_support/map.rb +175 -0
  205. data/lib/farce/integrations/active_support/set.rb +60 -0
  206. data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
  207. data/lib/farce/integrations/active_support/vector.rb +231 -0
  208. data/lib/farce/integrations/active_support.rb +9 -0
  209. data/lib/farce/integrations/activesupport.rb +5 -0
  210. data/lib/farce/integrations/bson.rb +108 -0
  211. data/lib/farce/integrations/cbor.rb +51 -0
  212. data/lib/farce/integrations/concurrent.rb +112 -0
  213. data/lib/farce/integrations/dry-types.rb +5 -0
  214. data/lib/farce/integrations/dry_types.rb +694 -0
  215. data/lib/farce/integrations/json.rb +7 -0
  216. data/lib/farce/integrations/msgpack.rb +112 -0
  217. data/lib/farce/integrations/oj.rb +69 -0
  218. data/lib/farce/integrations/psych.rb +191 -0
  219. data/lib/farce/integrations/ractor-sharing.rb +5 -0
  220. data/lib/farce/integrations/ractor-tmvar.rb +5 -0
  221. data/lib/farce/integrations/ractor_sharing.rb +172 -0
  222. data/lib/farce/integrations/ractor_tmvar.rb +64 -0
  223. data/lib/farce/integrations/shared/to_json.rb +42 -0
  224. data/lib/farce/integrations/sorted_set.rb +11 -0
  225. data/lib/farce/integrations/weakref.rb +38 -0
  226. data/lib/farce/integrations/yajl.rb +10 -0
  227. data/lib/farce/integrations.rb +156 -0
  228. data/lib/farce/internal/_frozen_config.rb +11 -0
  229. data/lib/farce/internal/autoloads.rb +47 -0
  230. data/lib/farce/internal/blocking_priority_queue.rb +122 -0
  231. data/lib/farce/internal/converter.rb +106 -0
  232. data/lib/farce/internal/copyable.rb +31 -0
  233. data/lib/farce/internal/delegation.rb +20 -0
  234. data/lib/farce/internal/external_transaction.rb +59 -0
  235. data/lib/farce/internal/fake_ractor.rb +179 -0
  236. data/lib/farce/internal/freeze.rb +118 -0
  237. data/lib/farce/internal/inspect.rb +157 -0
  238. data/lib/farce/internal/key_lock_map.rb +29 -0
  239. data/lib/farce/internal/key_normalizer.rb +289 -0
  240. data/lib/farce/internal/lease_initialization.rb +115 -0
  241. data/lib/farce/internal/lease_map.rb +441 -0
  242. data/lib/farce/internal/lease_pool_state.rb +228 -0
  243. data/lib/farce/internal/lease_state.rb +342 -0
  244. data/lib/farce/internal/lease_waiting.rb +13 -0
  245. data/lib/farce/internal/managed_queue.rb +26 -0
  246. data/lib/farce/internal/map_value_modes.rb +227 -0
  247. data/lib/farce/internal/marshal_support.rb +227 -0
  248. data/lib/farce/internal/mixin.rb +20 -0
  249. data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
  250. data/lib/farce/internal/noncopyable.rb +17 -0
  251. data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
  252. data/lib/farce/internal/pool_supervisor.rb +41 -0
  253. data/lib/farce/internal/pool_worker.rb +61 -0
  254. data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
  255. data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
  256. data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
  257. data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
  258. data/lib/farce/internal/portable_transaction.rb +164 -0
  259. data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
  260. data/lib/farce/internal/reservation_waiting.rb +24 -0
  261. data/lib/farce/internal/scheduled_task.rb +48 -0
  262. data/lib/farce/internal/scheduler_io.rb +171 -0
  263. data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
  264. data/lib/farce/internal/select_scheduler.rb +235 -0
  265. data/lib/farce/internal/storage.rb +162 -0
  266. data/lib/farce/internal/strict_lease.rb +30 -0
  267. data/lib/farce/internal/strict_lease_map.rb +20 -0
  268. data/lib/farce/internal/strict_lease_pool.rb +29 -0
  269. data/lib/farce/internal/thread_pool.rb +166 -0
  270. data/lib/farce/internal/transaction_conflict.rb +9 -0
  271. data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
  272. data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
  273. data/lib/farce/internal/undefined.rb +22 -0
  274. data/lib/farce/internal/unshared_lease.rb +29 -0
  275. data/lib/farce/internal/unshared_lease_pool.rb +34 -0
  276. data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
  277. data/lib/farce/internal/value_serialization.rb +13 -0
  278. data/lib/farce/internal/weak_map_value_modes.rb +58 -0
  279. data/lib/farce/internal/weak_mode_manager.rb +26 -0
  280. data/lib/farce/internal.rb +129 -0
  281. data/lib/farce/lazy.rb +100 -0
  282. data/lib/farce/lazy_ref.rb +40 -0
  283. data/lib/farce/lease.rb +28 -0
  284. data/lib/farce/lease_map.rb +34 -0
  285. data/lib/farce/lease_pool.rb +29 -0
  286. data/lib/farce/lfu_map.rb +53 -0
  287. data/lib/farce/local/atom.rb +23 -0
  288. data/lib/farce/local/counter.rb +88 -0
  289. data/lib/farce/local/flag.rb +65 -0
  290. data/lib/farce/local/lazy.rb +38 -0
  291. data/lib/farce/local/lazy_ref.rb +36 -0
  292. data/lib/farce/local/lease.rb +77 -0
  293. data/lib/farce/local/lease_map.rb +80 -0
  294. data/lib/farce/local/lease_pool.rb +38 -0
  295. data/lib/farce/local/lfu_map.rb +55 -0
  296. data/lib/farce/local/lru_map.rb +62 -0
  297. data/lib/farce/local/map.rb +47 -0
  298. data/lib/farce/local/molecule.rb +45 -0
  299. data/lib/farce/local/priority_queue.rb +53 -0
  300. data/lib/farce/local/queue.rb +32 -0
  301. data/lib/farce/local/scoped.rb +166 -0
  302. data/lib/farce/local/set.rb +18 -0
  303. data/lib/farce/local/sorted_set.rb +50 -0
  304. data/lib/farce/local/timer_queue.rb +41 -0
  305. data/lib/farce/local/tree_map.rb +47 -0
  306. data/lib/farce/local/vector.rb +30 -0
  307. data/lib/farce/local/weak_atom.rb +23 -0
  308. data/lib/farce/local/weak_key_map.rb +39 -0
  309. data/lib/farce/local/weak_map.rb +39 -0
  310. data/lib/farce/local/weak_set.rb +18 -0
  311. data/lib/farce/local/weak_value_map.rb +39 -0
  312. data/lib/farce/local.rb +35 -0
  313. data/lib/farce/lock.rb +54 -0
  314. data/lib/farce/lru_map.rb +63 -0
  315. data/lib/farce/map.rb +58 -0
  316. data/lib/farce/mode_manager.rb +142 -0
  317. data/lib/farce/molecule.rb +60 -0
  318. data/lib/farce/mutable.rb +171 -0
  319. data/lib/farce/pool.rb +332 -0
  320. data/lib/farce/port.rb +134 -0
  321. data/lib/farce/priority_queue.rb +70 -0
  322. data/lib/farce/proxy/register.rb +101 -0
  323. data/lib/farce/proxy/supervisor.rb +137 -0
  324. data/lib/farce/proxy/wrapper.rb +49 -0
  325. data/lib/farce/proxy.rb +291 -0
  326. data/lib/farce/queue.rb +60 -0
  327. data/lib/farce/ractor.rb +269 -0
  328. data/lib/farce/read_write_lock.rb +256 -0
  329. data/lib/farce/reference.rb +158 -0
  330. data/lib/farce/resolv/dns.rb +42 -0
  331. data/lib/farce/resolv.rb +85 -0
  332. data/lib/farce/scheduler.rb +536 -0
  333. data/lib/farce/set.rb +39 -0
  334. data/lib/farce/shareable.rb +150 -0
  335. data/lib/farce/signal.rb +97 -0
  336. data/lib/farce/sorted_set.rb +36 -0
  337. data/lib/farce/strict/atom.rb +34 -0
  338. data/lib/farce/strict/counter.rb +10 -0
  339. data/lib/farce/strict/exchanger.rb +25 -0
  340. data/lib/farce/strict/flag.rb +10 -0
  341. data/lib/farce/strict/lazy.rb +26 -0
  342. data/lib/farce/strict/lazy_ref.rb +26 -0
  343. data/lib/farce/strict/lease.rb +27 -0
  344. data/lib/farce/strict/lease_map.rb +33 -0
  345. data/lib/farce/strict/lease_pool.rb +28 -0
  346. data/lib/farce/strict/lfu_map.rb +27 -0
  347. data/lib/farce/strict/lru_map.rb +28 -0
  348. data/lib/farce/strict/map.rb +54 -0
  349. data/lib/farce/strict/molecule.rb +16 -0
  350. data/lib/farce/strict/port.rb +40 -0
  351. data/lib/farce/strict/priority_queue.rb +20 -0
  352. data/lib/farce/strict/queue.rb +17 -0
  353. data/lib/farce/strict/set.rb +14 -0
  354. data/lib/farce/strict/sorted_set.rb +19 -0
  355. data/lib/farce/strict/timer_queue.rb +13 -0
  356. data/lib/farce/strict/tree_map.rb +27 -0
  357. data/lib/farce/strict/vector.rb +24 -0
  358. data/lib/farce/strict/weak_atom.rb +31 -0
  359. data/lib/farce/strict/weak_key_map.rb +49 -0
  360. data/lib/farce/strict/weak_map.rb +49 -0
  361. data/lib/farce/strict/weak_set.rb +14 -0
  362. data/lib/farce/strict/weak_value_map.rb +48 -0
  363. data/lib/farce/strict.rb +28 -0
  364. data/lib/farce/system.rb +40 -0
  365. data/lib/farce/thread_scheduler.rb +70 -0
  366. data/lib/farce/timer_queue.rb +66 -0
  367. data/lib/farce/transaction/atom.rb +73 -0
  368. data/lib/farce/transaction/map.rb +25 -0
  369. data/lib/farce/transaction/map_operations.rb +143 -0
  370. data/lib/farce/transaction/molecule.rb +90 -0
  371. data/lib/farce/transaction/mutable.rb +61 -0
  372. data/lib/farce/transaction/set.rb +17 -0
  373. data/lib/farce/transaction/set_operations.rb +48 -0
  374. data/lib/farce/transaction/sorted_set.rb +18 -0
  375. data/lib/farce/transaction/tree_map.rb +78 -0
  376. data/lib/farce/transaction/vector.rb +139 -0
  377. data/lib/farce/transaction/wrapper.rb +202 -0
  378. data/lib/farce/transaction.rb +343 -0
  379. data/lib/farce/tree_map.rb +56 -0
  380. data/lib/farce/unsafe/lfu_map.rb +36 -0
  381. data/lib/farce/unsafe/lru_map.rb +36 -0
  382. data/lib/farce/unsafe/tree_map.rb +21 -0
  383. data/lib/farce/unsafe.rb +29 -0
  384. data/lib/farce/unshareable.rb +67 -0
  385. data/lib/farce/unshared/atom.rb +24 -0
  386. data/lib/farce/unshared/counter.rb +10 -0
  387. data/lib/farce/unshared/flag.rb +10 -0
  388. data/lib/farce/unshared/lazy.rb +38 -0
  389. data/lib/farce/unshared/lazy_ref.rb +26 -0
  390. data/lib/farce/unshared/lease.rb +27 -0
  391. data/lib/farce/unshared/lease_map.rb +26 -0
  392. data/lib/farce/unshared/lease_pool.rb +25 -0
  393. data/lib/farce/unshared/lfu_map.rb +20 -0
  394. data/lib/farce/unshared/lru_map.rb +20 -0
  395. data/lib/farce/unshared/map.rb +49 -0
  396. data/lib/farce/unshared/molecule.rb +16 -0
  397. data/lib/farce/unshared/priority_queue.rb +16 -0
  398. data/lib/farce/unshared/queue.rb +27 -0
  399. data/lib/farce/unshared/set.rb +14 -0
  400. data/lib/farce/unshared/sorted_set.rb +28 -0
  401. data/lib/farce/unshared/timer_queue.rb +16 -0
  402. data/lib/farce/unshared/tree_map.rb +21 -0
  403. data/lib/farce/unshared/vector.rb +19 -0
  404. data/lib/farce/unshared/weak_atom.rb +31 -0
  405. data/lib/farce/unshared/weak_key_map.rb +44 -0
  406. data/lib/farce/unshared/weak_map.rb +44 -0
  407. data/lib/farce/unshared/weak_set.rb +14 -0
  408. data/lib/farce/unshared/weak_value_map.rb +43 -0
  409. data/lib/farce/unshared.rb +28 -0
  410. data/lib/farce/vector.rb +228 -0
  411. data/lib/farce/version.rb +8 -0
  412. data/lib/farce/walker/definitions.rb +186 -0
  413. data/lib/farce/walker/modification.rb +355 -0
  414. data/lib/farce/walker.rb +319 -0
  415. data/lib/farce/weak_atom.rb +121 -0
  416. data/lib/farce/weak_key_map.rb +31 -0
  417. data/lib/farce/weak_map.rb +33 -0
  418. data/lib/farce/weak_ref.rb +67 -0
  419. data/lib/farce/weak_set.rb +82 -0
  420. data/lib/farce/weak_value.rb +195 -0
  421. data/lib/farce/weak_value_map.rb +32 -0
  422. data/lib/farce.rb +519 -0
  423. metadata +468 -0
@@ -0,0 +1,355 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ class Walker
7
+ # @api private
8
+ # Ignore the man behind the curtain!
9
+ #
10
+ # This is a helper class for lazy-modification that is also cycle-aware.
11
+ class Modification < Walker
12
+ public_class_method :new
13
+
14
+ # Bound callback replay rounds for cycles that do not converge, regardless of graph depth.
15
+ MAX_CYCLE_REPLAYS = 32
16
+
17
+ # Track an object's replacements, pending writes, and cycle discovery state.
18
+ Node = Struct.new(:original, :result, :destination, :index, :low, :active,
19
+ :plans, :changed, :cyclic, :freeze_result, :finalizer, :skipped, :callback)
20
+
21
+ # Record child references and the writer that installs their resolved values.
22
+ Plan = Struct.new(:children, :results, :writer, :hash_keys, :dirty, :applied, :target, :key_stride)
23
+ private_constant :Node, :Plan, :MAX_CYCLE_REPLAYS
24
+
25
+ # Keep identity and cycle tracking state local to this walk.
26
+ def initialize(...)
27
+ super
28
+ @nodes = {}.compare_by_identity
29
+ @stack = []
30
+ @sequence = 0
31
+ @node = nil
32
+ @replaying = false
33
+ end
34
+
35
+ # Reuse known results and settle each connected cycle once all its nodes are visited.
36
+ def visit(object, &callback)
37
+ parent = @node
38
+ previous_object = @current_object
39
+ if node = @nodes[object]
40
+ if node.active && parent
41
+ parent.low = node.index if parent.low > node.index
42
+ parent.cyclic = node.cyclic = true
43
+ end
44
+ return node.result
45
+ end
46
+ return @seen[object] if @seen.key?(object)
47
+
48
+ return super unless Internal.garbage_collectable?(object)
49
+
50
+ index = @sequence
51
+ @sequence += 1
52
+ node = Node.new(object, object, object, index, index, true)
53
+ node.callback = callback
54
+ @nodes[object] = node
55
+
56
+ @stack << node
57
+
58
+ @node = node
59
+ @current_object = object
60
+ node.result = catch(SKIP) { (node.callback || @callback).call(object, self) }
61
+ node.changed ||= !node.result.equal?(object)
62
+
63
+ if node.low == node.index && !node.cyclic
64
+ @stack.pop
65
+ finish(node)
66
+ node.active = false
67
+ node.plans = nil
68
+ elsif node.low == node.index
69
+ start = @stack.index { it.equal?(node) }
70
+ component = @stack.slice!(start..)
71
+ settle(component)
72
+ component.each { it.active = false }
73
+ end
74
+
75
+ if parent && node.active
76
+ parent.low = node.low if parent.low > node.low
77
+ parent.cyclic = true
78
+ end
79
+
80
+ node.result
81
+ ensure
82
+ @node = parent
83
+ @current_object = previous_object
84
+ end
85
+
86
+ # Traverse alternate targets normally and reuse prepared destinations during replay.
87
+ def traverse(object = current_object)
88
+ return visit(object) { |value| super(value) } if
89
+ @node && Internal.garbage_collectable?(object) && !object.equal?(@node.original) &&
90
+ !object.equal?(@node.destination)
91
+ return @node.destination if @replaying && object.equal?(@node.original)
92
+ super
93
+ end
94
+
95
+ # Record child changes and defer writes until cyclic references can be resolved.
96
+ def update(object, values, hash_keys: false, key_stride: 1, &writer)
97
+ if hash_keys && !(Integer === key_stride && key_stride.positive?)
98
+ raise ArgumentError, "key_stride must be a positive Integer"
99
+ end
100
+
101
+ node = @node
102
+ unless object.equal?(node.original) || object.equal?(node.destination)
103
+ raise ArgumentError, "update requires the current traversal target"
104
+ end
105
+
106
+ results = values
107
+ dirty = false
108
+
109
+ values.each_with_index do |value, index|
110
+ result = visit(value)
111
+ child = @nodes[value]
112
+ node.changed ||= child&.changed
113
+ dirty ||= hash_keys && (index % key_stride).zero? && child && child.changed
114
+
115
+ next if value.equal?(result)
116
+
117
+ dirty = true
118
+ results = values.dup if results.equal?(values)
119
+ results[index] = result
120
+ end
121
+
122
+ node.changed ||= dirty
123
+ plan = Plan.new(values, results, writer, hash_keys, dirty, nil, nil, key_stride)
124
+ (node.plans ||= []) << plan
125
+
126
+ unless node.cyclic
127
+ prepare(node) if plan.dirty
128
+ apply(node, plan) if plan.dirty
129
+ end
130
+ node.destination
131
+ end
132
+
133
+ # Record Data members so changed values can initialize a replacement.
134
+ def rebuild_data(object)
135
+ members = object.class.members
136
+ values = members.map { object.__send__(it) }
137
+ update(object, values) do |target, results|
138
+ target.__send__(:initialize, **members.zip(results).to_h)
139
+ target
140
+ end
141
+ end
142
+
143
+ # Freeze the settled result. With copy enabled, preserve the input's state.
144
+ # Call this instead of freezing a preliminary result inside the callback.
145
+ def freeze_result
146
+ return send_to(current_object, :freeze) unless @node && @node.original.equal?(current_object)
147
+ @node.changed = true unless send_to(@node.original, :frozen?)
148
+ @node.freeze_result = true
149
+ prepare(@node) if @modify != true && !send_to(@node.destination, :frozen?)
150
+ @node.destination
151
+ end
152
+
153
+ # Run once after traversal and cycle resolution. The block receives the
154
+ # result and whether it belongs to a cycle. Cyclic finalizers must retain
155
+ # identity. Acyclic finalizers may return a canonical replacement.
156
+ def finalize(&block)
157
+ raise LocalJumpError, "no block given" unless block
158
+ return block.call(current_object, false) unless @node && @node.original.equal?(current_object)
159
+ @node.finalizer = block
160
+ @node.destination
161
+ end
162
+
163
+ # Exclude the current node from callback replay, freezing, and finalization.
164
+ def skip(object = current_object)
165
+ @node.skipped = true if @node && @node.original.equal?(current_object)
166
+ super
167
+ end
168
+
169
+ # Report whether the object or its visited descendants have changed.
170
+ def changed?(object = current_object)
171
+ node = @nodes[object]
172
+ node ? node.changed == true : (@seen.key?(object) && !@seen[object].equal?(object))
173
+ end
174
+
175
+ # Follow pending references to check that hashing cannot reach unfinished Data.
176
+ def hashable?(object)
177
+ return true unless @unfinished_data && !@unfinished_data.empty?
178
+ !Walker.visit(object, constants:, class_variables:) do |value, traversal|
179
+ traversal.return(true) if @unfinished_data.key?(value)
180
+ node = @nodes[value] || @destinations&.[](value)
181
+ if node && node.plans
182
+ traversal.return(true) if @unfinished_data.key?(node.destination)
183
+ node.plans.each { |plan| plan.children.each { traversal.visit(resolved(it)) } }
184
+ else
185
+ traversal.traverse
186
+ end
187
+ false
188
+ end
189
+ end
190
+
191
+ # Reject keys and set elements whose Data dependencies are still being built.
192
+ def check_hash_key(object)
193
+ return object if hashable?(object)
194
+ raise ArgumentError, "hash key or set element refers to an unfinished Data value"
195
+ end
196
+
197
+ private
198
+
199
+ # Look up the latest result for a recorded child reference.
200
+ def resolved(object)
201
+ node = @nodes[object]
202
+ node ? node.result : @seen.fetch(object, object)
203
+ end
204
+
205
+ # Refresh a pending write from current child results, including changed hash keys.
206
+ def refresh(node, plan)
207
+ dirty = false
208
+ plan.children.each_with_index do |value, index|
209
+ result = resolved(value)
210
+ unless plan.results[index].equal?(result)
211
+ plan.results = plan.children.dup if plan.results.equal?(plan.children)
212
+ plan.results[index] = result
213
+ end
214
+ child = @nodes[value]
215
+ node.changed ||= child&.changed
216
+ dirty ||= !value.equal?(result)
217
+ dirty ||= plan.hash_keys && (index % plan.key_stride).zero? && child && child.changed
218
+ end
219
+ node.changed ||= dirty
220
+ plan.dirty = dirty
221
+ end
222
+
223
+ # Choose a writable destination, copying lazily or allocating unfinished Data.
224
+ def prepare(node, rebuild: false)
225
+ previous = node.destination
226
+ return previous unless rebuild || previous.equal?(node.original)
227
+ object = node.original
228
+ return object if Internal::Noncopyable === object || Abstract::LeaseMap === object
229
+ destination =
230
+ if Data === object
231
+ value = object.class.allocate
232
+ (@unfinished_data ||= {}.compare_by_identity)[value] = true
233
+ value
234
+ elsif @modify == true
235
+ send_to(object, :frozen?) ? send_to(object, :clone, freeze: false) : object
236
+ elsif @modify == :clone
237
+ send_to(object, :clone, freeze: false)
238
+ else
239
+ send_to(object, @modify)
240
+ end
241
+ (@destinations ||= {}.compare_by_identity)[destination] = node unless destination.equal?(object)
242
+ node.destination = destination
243
+ node.result = destination if !node.skipped && (node.result.equal?(object) || node.result.equal?(previous))
244
+ destination
245
+ end
246
+
247
+ # Install resolved children and remember cyclic writes to avoid repeating them.
248
+ def apply(node, plan)
249
+ return if plan.target.equal?(node.destination) && plan.applied &&
250
+ plan.results.each_with_index.all? { |value, index| value.equal?(plan.applied[index]) }
251
+ if plan.children.equal?(node.destination)
252
+ children = plan.children.dup
253
+ plan.results = children if plan.results.equal?(plan.children)
254
+ plan.children = children
255
+ end
256
+ result = plan.writer.call(node.destination, plan.results)
257
+ if result && !result.equal?(node.destination)
258
+ old = node.destination
259
+ node.destination = result
260
+ node.result = result if node.result.equal?(old)
261
+ end
262
+ @unfinished_data&.delete(node.destination) if Data === node.destination
263
+ return unless node.cyclic
264
+ plan.target = node.destination
265
+ plan.applied = plan.results.dup
266
+ end
267
+
268
+ # Resolve a component and freeze all its results before running finalizers.
269
+ def settle(component)
270
+ settle_cycle(component) if component.size > 1 || component.first.cyclic
271
+ component.each { finish(it, finalize: false) }
272
+ component.each { finalize_node(it) } # rubocop:disable Style/CombinableLoops
273
+ end
274
+
275
+ # Apply the requested freezing policy and optionally finalize the result.
276
+ def finish(node, finalize: true)
277
+ return if node.skipped
278
+ @node = node
279
+ @current_object = node.original
280
+ freeze = node.freeze_result || @freeze == true || (@freeze.nil? && send_to(node.original, :frozen?))
281
+ if freeze
282
+ node.changed = true unless send_to(node.result, :frozen?)
283
+ prepare(node) if @modify != true && node.result.equal?(node.original) && !send_to(node.result, :frozen?)
284
+ send_to(node.result, :freeze)
285
+ end
286
+ finalize_node(node) if finalize
287
+ end
288
+
289
+ # Accept a final replacement while preserving the identity of cyclic results.
290
+ def finalize_node(node)
291
+ return if node.skipped || !node.finalizer
292
+ @node = node
293
+ @current_object = node.original
294
+ result = node.finalizer.call(node.result, node.cyclic == true)
295
+ raise ArgumentError, "a cyclic finalizer must preserve identity" if node.cyclic && !result.equal?(node.result)
296
+ node.changed ||= !result.equal?(node.result)
297
+ node.result = result
298
+ end
299
+
300
+ # Propagate replacements and replay callbacks until result identities stabilize.
301
+ def settle_cycle(component)
302
+ # Allocate destinations to a fixed point before wiring back references.
303
+ MAX_CYCLE_REPLAYS.times do
304
+ loop do
305
+ destinations = component.map(&:destination)
306
+ component.each do |node|
307
+ node.plans&.each { refresh(node, it) }
308
+ freezing = !node.skipped && (node.freeze_result || @freeze == true)
309
+ needed = node.plans&.any?(&:dirty) || (freezing && @modify != true && !send_to(node.original, :frozen?))
310
+ prepare(node) if needed
311
+ next unless Data === node.destination && send_to(node.destination, :frozen?) && node.plans&.any? do |plan|
312
+ plan.applied && plan.results.each_with_index.any? { |value, index| !value.equal?(plan.applied[index]) }
313
+ end
314
+ prepare(node, rebuild: true)
315
+ end
316
+ break if component.each_with_index.all? { |node, index| node.destination.equal?(destinations[index]) }
317
+ end
318
+
319
+ component.sort_by { Data === it.destination ? 2 : (it.plans&.any?(&:hash_keys) ? 1 : 0) }.each do |node|
320
+ @node = node
321
+ @current_object = node.original
322
+ node.plans&.each do |plan|
323
+ refresh(node, plan)
324
+ apply(node, plan) if plan.dirty
325
+ end
326
+ end
327
+
328
+ previous = component.map(&:result)
329
+ component.each do |node|
330
+ next if node.skipped
331
+ @node = node
332
+ @current_object = node.original
333
+ @replaying = true
334
+ node.result = catch(SKIP) { (node.callback || @callback).call(node.original, self) }
335
+ node.changed ||= !node.result.equal?(node.original)
336
+ ensure
337
+ @replaying = false
338
+ end
339
+
340
+ next unless component.each_with_index.all? { |node, index| node.result.equal?(previous[index]) }
341
+ component.each do |node|
342
+ next unless node.plans&.any?(&:dirty)
343
+ target = node.destination
344
+ target.rehash if Hash === target && !target.compare_by_identity?
345
+ target.reset if ::Set === target && !target.compare_by_identity?
346
+ end
347
+ return
348
+ end
349
+
350
+ raise ArgumentError, "cyclic modification did not converge"
351
+ end
352
+ end
353
+ private_constant :Modification
354
+ end
355
+ end
@@ -0,0 +1,319 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ # Visit an object graph or transform it with copies only where needed.
7
+ #
8
+ # Traversal follows container elements, hash keys and values, and ordinary
9
+ # instance variables. Farce containers expose their contents, not their storage.
10
+ # Proc traversal follows the receiver, not captured local variables.
11
+ # Module and class traversal includes directly defined public constants and
12
+ # class variables. Set constants: :inherited or class_variables: :inherited
13
+ # to include ancestors, or false to skip either kind. Autoloads are not loaded.
14
+ #
15
+ # {.visit} and {.modify} let the callback choose when to descend with {#traverse}.
16
+ # {.each} descends automatically and yields children before their parent.
17
+ # Shared children and cycles are tracked by identity.
18
+ #
19
+ # @example Scan a namespace without changing it
20
+ # namespace = Module.new
21
+ # namespace.const_set(:VALUE, [42])
22
+ # Farce::Walker.any?(namespace) { Integer === it } # => true
23
+ # Farce::Walker.any?(namespace, constants: false) { Integer === it } # => false
24
+ #
25
+ # @example Replace integers in a copy
26
+ # Farce::Walker.modify([1, [2]], copy: true) do |object, walker|
27
+ # Integer === object ? object + 1 : walker.traverse
28
+ # end # => [2, [3]]
29
+ class Walker
30
+ include Internal::MarshalSupport::Reject
31
+
32
+ module SendTo
33
+ private
34
+
35
+ def send_to(object, method, ...)
36
+ object.__send__(method, ...)
37
+ rescue NoMethodError => e
38
+ raise e unless e.name == method && e.receiver.equal?(object)
39
+ Kernel.instance_method(method).bind_call(object, ...)
40
+ end
41
+ end
42
+
43
+ class Visitor
44
+ include Shareable::Immutable
45
+ include SendTo
46
+
47
+ attr_reader :base_class
48
+
49
+ def initialize(base_class)
50
+ @base_class = base_class
51
+ super()
52
+ end
53
+
54
+ def call(object, walker) = traverse(object, walker)
55
+ end
56
+
57
+ include SendTo
58
+
59
+ REGISTER = ClassMirror.new(Visitor) { _1.new(_2) }
60
+ SKIP = Object.new.freeze
61
+ RETURN = Object.new.freeze
62
+
63
+ private_constant :SendTo, :Visitor, :REGISTER, :SKIP, :RETURN
64
+ private_class_method :new
65
+
66
+ # Register traversal for classes from the main Ractor.
67
+ #
68
+ # Subclasses inherit the nearest definition. Definitions can call `super`.
69
+ # Use {#update} to visit children and record assignments. Its block receives
70
+ # a writable target and transformed children. Return that target from the
71
+ # assignment block and return the update result from the definition.
72
+ #
73
+ # Should only be necessary for classes using storage defined outside of Ruby (like a C struct).
74
+ #
75
+ # @example
76
+ # # Lets assume NativeClass has a natively stored value
77
+ # Farce::Walker.define(NativeClass) do |object, walker|
78
+ # walker.update(object, [object.value]) do |target, values|
79
+ # target.value = values.first
80
+ # target
81
+ # end
82
+ # end
83
+ #
84
+ # @param classes [Array<Class>] classes to handle
85
+ # @yieldparam object [BasicObject] the object to traverse
86
+ # @yieldparam walker [Walker] the current walk
87
+ # @yieldreturn [BasicObject] the resulting object
88
+ # @note The block must be convertible to a shareable proc.
89
+ def self.define(*classes, &)
90
+ definition = Internal.prepare_method_definition(&)
91
+ raise LocalJumpError, "no block given" unless definition
92
+ classes.each { REGISTER.define(it) { define_method(:traverse, definition) } }
93
+ end
94
+
95
+ # Visit an object without automatically descending or assigning replacements.
96
+ # @param object [BasicObject] the root object
97
+ # @!macro walker_module_options
98
+ # @param constants [Boolean, Symbol] true for directly defined public constants,
99
+ # :inherited to include ancestors, or false to skip. Autoloads are not loaded.
100
+ # @param class_variables [Boolean, Symbol] true for directly defined class variables,
101
+ # :inherited to include ancestors, or false to skip
102
+ # @yieldparam object [BasicObject] the current object
103
+ # @yieldparam walker [Walker] call {#traverse}, {#skip}, or {#return} to control the walk
104
+ # @yieldreturn [BasicObject] the result for the current object
105
+ # @return [BasicObject] the root callback's result, or the value passed to {#return}
106
+ # @raise [LocalJumpError] if no block is given
107
+ def self.visit(object, constants: true, class_variables: true, &callback)
108
+ raise LocalJumpError, "no block given" unless callback
109
+ catch(RETURN) { new(callback, false, false, constants, class_variables).visit(object) }
110
+ end
111
+
112
+ # Yield each reachable object once, after its children.
113
+ # @param object [BasicObject] the root object
114
+ # @!macro walker_module_options
115
+ # @yieldparam object [BasicObject] a reachable object
116
+ # @return [BasicObject, Enumerator] the root object, or an enumerator without a block
117
+ def self.each(object, constants: true, class_variables: true)
118
+ return enum_for(:each, object, constants:, class_variables:) unless block_given?
119
+ visit(object, constants:, class_variables:) do |object, walker|
120
+ walker.traverse(object)
121
+ yield(object)
122
+ object
123
+ end
124
+ end
125
+
126
+ # Stop at the first object for which the block is truthy.
127
+ # Without a block, test the objects themselves.
128
+ # @!macro walker_module_options
129
+ # @return [Boolean]
130
+ def self.any?(object, constants: true, class_variables: true, &)
131
+ each(object, constants:, class_variables:).any?(&)
132
+ end
133
+
134
+ # Stop at the first object for which the block is falsey.
135
+ # Without a block, test the objects themselves.
136
+ # @!macro walker_module_options
137
+ # @return [Boolean]
138
+ def self.all?(object, constants: true, class_variables: true, &)
139
+ each(object, constants:, class_variables:).all?(&)
140
+ end
141
+
142
+ # Transform a graph using the callback's return values.
143
+ # Call {#traverse} to transform an object's children. Returning another value
144
+ # replaces the object without automatically visiting that replacement.
145
+ # Unchanged branches retain their identity, including when copy is enabled.
146
+ # Changed cycles are connected before results are frozen or finalized.
147
+ # A cyclic callback may run again as child replacements become known. Keep
148
+ # callbacks repeatable and put publication or caching in {#finalize}.
149
+ # Use {#freeze_result} instead of freezing a preliminary traversal result.
150
+ # Data values are rebuilt only when members change, using their normal initializer.
151
+ # Noncopyable coordination objects, such as leases, are updated in place.
152
+ # Constants and class variables are assigned only when their value
153
+ # changes identity. Constant replacement can emit Ruby redefinition warnings.
154
+ # Replacing an inherited constant defines a local constant. Replacing an
155
+ # inherited class variable updates storage shared with its owner and siblings.
156
+ #
157
+ # A walk is not an atomic snapshot or update. Coordinate concurrent writers
158
+ # when transforming container structure, especially map keys.
159
+ # @param object [BasicObject] the root object
160
+ # @!macro walker_module_options
161
+ # @param copy [Boolean, Symbol] false to edit mutable objects in place, true to
162
+ # duplicate changed objects, or a copy method such as :clone. Unchanged objects
163
+ # may be shared with the input. Frozen objects are cloned only when changed.
164
+ # @param freeze [Boolean, nil] true to freeze results, false to avoid freezing them,
165
+ # or nil to preserve each original object's frozen state
166
+ # @yieldparam object [BasicObject] the original object
167
+ # @yieldparam walker [Walker] the current walk
168
+ # @yieldreturn [BasicObject] the replacement object
169
+ # @return [BasicObject] the transformed root, or the value passed to {#return}
170
+ # @raise [LocalJumpError] if no block is given
171
+ # @raise [ArgumentError] if copy is neither a boolean nor a Symbol
172
+ # @raise [ArgumentError] if cyclic callbacks do not converge within 32 passes,
173
+ # or a cyclic finalizer replaces its result
174
+ # @raise [ArgumentError] if a hash key or set element refers to a Data value
175
+ # still being constructed. Identity-based containers do not hash their keys.
176
+ def self.modify(object, copy: false, freeze: nil, constants: true, class_variables: true, &callback)
177
+ raise LocalJumpError, "no block given" unless callback
178
+
179
+ modify =
180
+ case copy
181
+ when false then true
182
+ when true then :dup
183
+ when Symbol then copy
184
+ else raise ArgumentError, "invalid value for copy: #{copy.inspect}"
185
+ end
186
+
187
+ catch(RETURN) { Modification.new(callback, modify, freeze, constants, class_variables).visit(object) }
188
+ end
189
+
190
+ # @return [false, true, Symbol] false for a read-only walk, true for in-place edits,
191
+ # or the selected copy method
192
+ attr_reader :modify
193
+
194
+ # @return [Boolean, Symbol] the constant traversal policy
195
+ attr_reader :constants
196
+
197
+ # @return [Boolean, Symbol] the class variable traversal policy
198
+ attr_reader :class_variables
199
+
200
+ # @return [BasicObject] the original object currently passed to the callback
201
+ attr_reader :current_object
202
+
203
+ # @!visibility private
204
+ def initialize(callback, modify, freeze, constants, class_variables)
205
+ { constants:, class_variables: }.each do |name, policy|
206
+ next if [true, false, :inherited].include?(policy)
207
+ raise ArgumentError, "invalid value for #{name}: #{policy.inspect}"
208
+ end
209
+ @callback = callback
210
+ @modify = modify
211
+ @freeze = freeze
212
+ @constants = constants
213
+ @class_variables = class_variables
214
+ @visitors = {}.compare_by_identity
215
+ @seen = {}.compare_by_identity
216
+ @seen[UNDEFINED] = UNDEFINED
217
+ @current_object = UNDEFINED
218
+ end
219
+
220
+ # Visit a child, reusing the result if its identity has already been seen.
221
+ # @param object [BasicObject] the child object
222
+ # @return [BasicObject] the callback result or an in-progress copy for a cycle
223
+ def visit(object)
224
+ @seen.fetch(object) do
225
+ current_object = @current_object
226
+ @current_object = object
227
+ @seen[object] = object # set this first to stop recursion
228
+ @seen[object] = catch(SKIP) do
229
+ result = @callback.call(object, self)
230
+ case @modify && @freeze
231
+ when false then result
232
+ when true then send_to(result, :freeze)
233
+ else
234
+ send_to(result, :freeze) if send_to(object, :frozen?) && !send_to(result, :frozen?)
235
+ result
236
+ end
237
+ end
238
+ ensure
239
+ @current_object = current_object
240
+ end
241
+ end
242
+
243
+ # End the current callback and use object as its result without freezing it.
244
+ # @param object [BasicObject] the replacement, defaulting to the current object
245
+ # @return [void]
246
+ def skip(object = current_object) = throw(SKIP, object)
247
+
248
+ # End the whole walk immediately.
249
+ # @param object [BasicObject] the walk's result, defaulting to the current object
250
+ # @return [void]
251
+ def return(object = current_object) = throw(RETURN, object)
252
+
253
+ # Traverse an object's children using its registered class definition.
254
+ # @param object [BasicObject] the object to descend into, defaulting to the current object
255
+ # @return [BasicObject] the object with transformed children when modifying
256
+ def traverse(object = current_object)
257
+ klass = send_to(object, :class)
258
+ visitor = @visitors[klass] ||= REGISTER[klass]
259
+ visitor.call(object, self)
260
+ end
261
+
262
+ # @api private
263
+ def remember(object, result) = @seen[object] = result
264
+
265
+ # Visit child values and describe how to assign their replacements.
266
+ # The assignment block only runs during modification and may run again for cycles.
267
+ # It receives a writable target and the resolved children. Do not mutate object
268
+ # outside this block. Return the result of update from a traversal definition.
269
+ # @param object [BasicObject] the object being traversed
270
+ # @param values [Array] child references in assignment order
271
+ # @param hash_keys [Boolean] rebuild when key descendants change in place
272
+ # @param key_stride [Integer] spacing between keys in values, starting at zero
273
+ # @yieldparam target [BasicObject] the writable destination
274
+ # @yieldparam results [Array] transformed child references
275
+ # @return [BasicObject] the traversal result
276
+ def update(object, values, hash_keys: false, key_stride: 1) # rubocop:disable Lint/UnusedMethodArgument
277
+ values.each { visit(it) }
278
+ object
279
+ end
280
+
281
+ # Request freezing after the current result is settled.
282
+ # With copying enabled, mutable inputs are copied before freezing.
283
+ # @return [BasicObject] the current destination
284
+ # @raise [ArgumentError] outside a modifying walk
285
+ def freeze_result = raise(ArgumentError, "freeze_result requires a modifying walk")
286
+
287
+ # Register a finalizer for the current node. It runs once after resolution
288
+ # and requested freezing. Registering another finalizer replaces the first.
289
+ # Cyclic finalizers must preserve identity. Acyclic finalizers may return a
290
+ # canonical replacement, which is propagated to the parent.
291
+ # @yieldparam result [BasicObject] the settled result
292
+ # @yieldparam cyclic [Boolean] whether the node belongs to a cycle
293
+ # @yieldreturn [BasicObject] the final result
294
+ # @return [BasicObject] the current destination
295
+ # @raise [ArgumentError] outside a modifying walk
296
+ # @raise [LocalJumpError] if no block is given
297
+ def finalize(&) = raise(ArgumentError, "finalize requires a modifying walk")
298
+
299
+ # @api private
300
+ def rebuild_data(object)
301
+ object.class.members.each { visit(object.__send__(it)) }
302
+ object
303
+ end
304
+
305
+ # Whether a visited object or its traversed descendants changed.
306
+ # @param object [BasicObject] the object to inspect
307
+ # @return [Boolean] false for a read-only walk
308
+ def changed?(object = current_object) = false # rubocop:disable Lint/UnusedMethodArgument
309
+
310
+ # @api private
311
+ def hashable?(_object) = true
312
+
313
+ # @api private
314
+ def check_hash_key(object) = object
315
+
316
+ require "farce/walker/definitions"
317
+ require "farce/walker/modification"
318
+ end
319
+ end