galaaz 0.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/LICENSE +0 -0
  4. data/README.md +1360 -636
  5. data/Rakefile +61 -41
  6. data/bin/galaaz-bootstrap +137 -0
  7. data/bin/galaaz-jruby +14 -0
  8. data/bin/galaaz_jruby_env.inc.sh +6 -0
  9. data/bin/gbookdown +64 -0
  10. data/bin/gknit +84 -13
  11. data/bin/gknit-draft.rb +0 -0
  12. data/bin/gstudio +5 -3
  13. data/bin/gstudio_irb.rb +0 -0
  14. data/bin/gstudio_pry.rb +0 -0
  15. data/bin/install-tinytex +6 -0
  16. data/bin/run_all_rspec +43 -0
  17. data/bin/run_example +14 -0
  18. data/bin/run_old_rspec +19 -0
  19. data/bin/run_rspec +23 -0
  20. data/bin/run_rspec_subset +38 -0
  21. data/bin/run_slow_rspec +19 -0
  22. data/blogs/R-on-Rails-Planning-Document.md +940 -0
  23. data/blogs/README.md +100 -0
  24. data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +38 -66
  25. data/blogs/galaaz_ggplot/galaaz_ggplot.log +754 -0
  26. data/blogs/galaaz_ggplot/galaaz_ggplot.md +115 -155
  27. data/blogs/galaaz_ggplot/galaaz_ggplot.tex +607 -0
  28. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
  29. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
  30. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
  31. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
  32. data/blogs/galaaz_ggplot/midwest.Rmd +3 -3
  33. data/blogs/galaaz_ggplot/midwest_external_png +0 -0
  34. data/blogs/gknit/gknit.Rmd +47 -52
  35. data/blogs/gknit/gknit.md +1430 -0
  36. data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
  37. data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
  38. data/blogs/gknit/lst.rds +0 -0
  39. data/blogs/gknit/model.rb +1 -1
  40. data/blogs/gknit/stats.bib +0 -0
  41. data/blogs/manual/include_model_local_repro.Rmd +14 -0
  42. data/blogs/manual/include_model_local_repro.md +75 -0
  43. data/blogs/manual/lst.rds +0 -0
  44. data/blogs/manual/manual.Rmd +852 -239
  45. data/blogs/manual/manual.log +1786 -0
  46. data/blogs/manual/manual.md +1360 -636
  47. data/blogs/manual/manual.tex +1883 -1161
  48. data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
  49. data/blogs/manual/manual_files/figure-html/diverging_bar.png +0 -0
  50. data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
  51. data/blogs/manual/model.rb +1 -1
  52. data/blogs/nse_dplyr/nse_dplyr.Rmd +84 -111
  53. data/blogs/nse_dplyr/nse_dplyr.log +928 -0
  54. data/blogs/nse_dplyr/nse_dplyr.md +198 -229
  55. data/blogs/oh_my/not_so.rb +0 -0
  56. data/blogs/oh_my/oh_my.Rmd +1234 -25
  57. data/blogs/oh_my/oh_my.log +804 -0
  58. data/blogs/oh_my/oh_my.md +1663 -86
  59. data/blogs/oh_my/oh_my.tex +821 -0
  60. data/blogs/oh_my/old.Rmd +15 -14
  61. data/blogs/ruby_plot/ruby_plot.Rmd +58 -82
  62. data/blogs/ruby_plot/ruby_plot.log +885 -0
  63. data/blogs/ruby_plot/ruby_plot.md +71 -102
  64. data/blogs/ruby_plot/ruby_plot.tex +940 -0
  65. data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
  66. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
  67. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
  68. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
  69. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
  70. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
  71. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
  72. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
  73. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
  74. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
  75. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
  76. data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
  77. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
  78. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
  79. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
  80. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
  81. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
  82. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  83. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
  84. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
  85. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  86. data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  87. data/blogs/test/test.Rmd +14 -0
  88. data/examples/50Plots_MasterList/Images/midwest-scatterplot.PNG +0 -0
  89. data/examples/50Plots_MasterList/ScatterPlot.rb +0 -0
  90. data/examples/50Plots_MasterList/scatter_plot.rb +0 -0
  91. data/examples/Bibliography/master.bib +0 -0
  92. data/examples/Bibliography/stats.bib +0 -0
  93. data/examples/R/calc.R +0 -0
  94. data/examples/R/java_interop.R +0 -0
  95. data/examples/bioconductor_deseq2_airway/Documentation/DESeq2-airway-walkthrough.md +56 -0
  96. data/examples/bioconductor_deseq2_airway/bench_galaaz_three_same_process.rb +53 -0
  97. data/examples/bioconductor_deseq2_airway/bench_r_three_same_process.R +34 -0
  98. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb +33 -0
  99. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz_optimized.rb +34 -0
  100. data/examples/bioconductor_deseq2_airway/deseq2_airway_minimal.R +30 -0
  101. data/examples/bioconductor_deseq2_airway/deseq2_airway_pipeline_for_bench.R +36 -0
  102. data/examples/islr/all.rb +13 -0
  103. data/examples/islr/ch2.spec.rb +37 -7
  104. data/examples/islr/ch3.spec.rb +11 -2
  105. data/examples/islr/ch3_boston.rb +27 -0
  106. data/examples/islr/ch3_multiple_regression.rb +0 -0
  107. data/examples/islr/ch6.spec.rb +24 -1
  108. data/examples/islr/x_y_rnorm.jpg +0 -0
  109. data/examples/latex_templates/Test-acm_article/acm_proc_article-sp.cls +0 -0
  110. data/examples/latex_templates/Test-acm_article/sigproc.bib +0 -0
  111. data/examples/latex_templates/Test-acs_article/acs-Test-acs_article.bib +0 -0
  112. data/examples/latex_templates/Test-acs_article/acs-my_output.bib +0 -0
  113. data/examples/latex_templates/Test-aea_article/BibFile.bib +0 -0
  114. data/examples/latex_templates/Test-aea_article/Test-aea_article.Rmd +0 -0
  115. data/examples/latex_templates/Test-aea_article/references.bib +0 -0
  116. data/examples/latex_templates/Test-amq_article/Test-amq_article.Rmd +0 -0
  117. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdfsync +0 -0
  118. data/examples/latex_templates/Test-ieee_article/IEEEtran.bst +0 -0
  119. data/examples/latex_templates/Test-ieee_article/mybibfile.bib +0 -0
  120. data/examples/latex_templates/Test-rjournal_article/RJournal.sty +0 -0
  121. data/examples/latex_templates/Test-rjournal_article/RJreferences.bib +0 -0
  122. data/examples/latex_templates/Test-rjournal_article/Test-rjournal_article.Rmd +0 -0
  123. data/examples/misc/baseball.csv +0 -0
  124. data/examples/misc/ggplot.rb +3 -2
  125. data/examples/misc/moneyball.rb +0 -0
  126. data/examples/misc/subsetting.rb +0 -0
  127. data/examples/multithread_shards_to_r/shards_to_r.rb +67 -0
  128. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.Rmd +0 -0
  129. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.Rmd +0 -0
  130. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.Rmd +0 -0
  131. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.Rmd +0 -0
  132. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/attend-grade-relationships.csv +0 -0
  133. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.Rmd +0 -0
  134. data/examples/rmarkdown/svm-xaringan-example/svm-xaringan-example.Rmd +0 -0
  135. data/examples/sthda_ggplot/README.md +0 -0
  136. data/examples/sthda_ggplot/RUN.md +41 -0
  137. data/examples/sthda_ggplot/all.rb +0 -0
  138. data/examples/sthda_ggplot/one_variable_continuous/density_gg.rb +0 -0
  139. data/examples/sthda_ggplot/one_variable_continuous/geom_area.rb +0 -0
  140. data/examples/sthda_ggplot/one_variable_continuous/geom_density.rb +2 -0
  141. data/examples/sthda_ggplot/one_variable_continuous/geom_dotplot.rb +0 -0
  142. data/examples/sthda_ggplot/one_variable_continuous/geom_freqpoly.rb +0 -0
  143. data/examples/sthda_ggplot/one_variable_continuous/geom_histogram.rb +0 -0
  144. data/examples/sthda_ggplot/one_variable_continuous/histogram_density.rb +0 -0
  145. data/examples/sthda_ggplot/one_variable_continuous/stat.rb +0 -0
  146. data/examples/sthda_ggplot/one_variable_discrete/bar.rb +0 -0
  147. data/examples/sthda_ggplot/qplots/box_violin_dot.rb +0 -0
  148. data/examples/sthda_ggplot/qplots/scatter_plots.rb +0 -0
  149. data/examples/sthda_ggplot/scatter_gg.rb +0 -0
  150. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_bin2d.rb +0 -0
  151. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_density2d.rb +0 -0
  152. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_hex.rb +0 -0
  153. data/examples/sthda_ggplot/two_variables_cont_cont/geom_point.rb +0 -0
  154. data/examples/sthda_ggplot/two_variables_cont_cont/geom_smooth.rb +0 -0
  155. data/examples/sthda_ggplot/two_variables_cont_cont/misc.rb +0 -0
  156. data/examples/sthda_ggplot/two_variables_cont_function/geom_area.rb +4 -3
  157. data/examples/sthda_ggplot/two_variables_disc_cont/geom_bar.rb +0 -0
  158. data/examples/sthda_ggplot/two_variables_disc_cont/geom_boxplot.rb +0 -0
  159. data/examples/sthda_ggplot/two_variables_disc_cont/geom_dotplot.rb +0 -0
  160. data/examples/sthda_ggplot/two_variables_disc_cont/geom_jitter.rb +0 -0
  161. data/examples/sthda_ggplot/two_variables_disc_cont/geom_line.rb +0 -0
  162. data/examples/sthda_ggplot/two_variables_disc_cont/geom_violin.rb +0 -0
  163. data/examples/sthda_ggplot/two_variables_disc_disc/geom_jitter.rb +0 -0
  164. data/examples/sthda_ggplot/two_variables_error/geom_crossbar.rb +0 -0
  165. data/ext/new_bridge/Makefile +46 -0
  166. data/ext/new_bridge/galaaz_gatekeeper_phase0.cpp +12 -0
  167. data/ext/new_bridge/galaaz_gatekeeper_phase1.cpp +1639 -0
  168. data/lib/R_interface/galaaz_device.R +20 -0
  169. data/lib/R_interface/include_engine.R +109 -0
  170. data/lib/R_interface/new_bridge_adapter.rb +824 -0
  171. data/lib/R_interface/r.rb +177 -25
  172. data/lib/R_interface/r_arrow.rb +113 -0
  173. data/lib/R_interface/r_libs.R +3 -3
  174. data/lib/R_interface/r_methods.rb +13 -126
  175. data/lib/R_interface/r_module_s.rb +0 -0
  176. data/lib/R_interface/rbinary_operators.rb +20 -2
  177. data/lib/R_interface/rclosure.rb +5 -1
  178. data/lib/R_interface/rdata_frame.rb +34 -70
  179. data/lib/R_interface/rdevice.rb +125 -0
  180. data/lib/R_interface/rdevices.R +0 -0
  181. data/lib/R_interface/renvironment.rb +10 -4
  182. data/lib/R_interface/rexpression.rb +5 -1
  183. data/lib/R_interface/rindexed_object.rb +41 -13
  184. data/lib/R_interface/rlanguage.rb +20 -62
  185. data/lib/R_interface/rlist.rb +115 -25
  186. data/lib/R_interface/rlogical_operators.rb +0 -0
  187. data/lib/R_interface/rmatrix.rb +2 -11
  188. data/lib/R_interface/rmd_indexed_object.rb +5 -1
  189. data/lib/R_interface/robject.rb +348 -290
  190. data/lib/R_interface/rpkg.rb +0 -0
  191. data/lib/R_interface/rsupport.rb +609 -328
  192. data/lib/R_interface/rsupport_scope.rb +2 -1
  193. data/lib/R_interface/rsymbol.rb +50 -0
  194. data/lib/R_interface/ruby_callback.rb +2 -3
  195. data/lib/R_interface/ruby_extensions.rb +225 -175
  196. data/lib/R_interface/runary_operators.rb +0 -0
  197. data/lib/R_interface/rvector.rb +147 -31
  198. data/lib/galaaz.rb +0 -0
  199. data/lib/galaaz_jruby.rb +22 -0
  200. data/lib/gknit/diagnostics.rb +50 -0
  201. data/lib/gknit/draft.rb +23 -17
  202. data/lib/gknit/include_engine.rb +15 -7
  203. data/lib/gknit/knitr_engine.rb +223 -74
  204. data/lib/gknit/rb_engine.rb +3 -3
  205. data/lib/gknit/ruby_engine.rb +0 -0
  206. data/lib/gknit.rb +1 -0
  207. data/lib/new_bridge/bootstrap/windows_bootstrap.rb +285 -0
  208. data/lib/new_bridge/envelope.rb +51 -0
  209. data/lib/new_bridge/eval_result.rb +26 -0
  210. data/lib/new_bridge/framing.rb +39 -0
  211. data/lib/new_bridge/instance_pool_client.rb +38 -0
  212. data/lib/new_bridge/r_instance_manager.rb +404 -0
  213. data/lib/new_bridge/session_client.rb +530 -0
  214. data/lib/new_bridge/tcp_framed.rb +44 -0
  215. data/lib/new_bridge.rb +9 -0
  216. data/lib/util/exec_ruby.rb +95 -20
  217. data/lib/util/inline_file.rb +35 -30
  218. data/new_bridge_specs/benchmark_phase5_5_unboxing_spec.rb +96 -0
  219. data/new_bridge_specs/eval_r_async_spec.rb +113 -0
  220. data/new_bridge_specs/integration_phase5_1_concurrent_spec.rb +50 -0
  221. data/new_bridge_specs/integration_phase5_1_eval_spec.rb +16 -0
  222. data/new_bridge_specs/integration_phase5_1_r_api_spec.rb +25 -0
  223. data/new_bridge_specs/integration_phase5_1_smoke_spec.rb +31 -0
  224. data/new_bridge_specs/integration_phase5_2_dataframe_unboxing_spec.rb +19 -0
  225. data/new_bridge_specs/integration_phase5_2_handle_eval_unboxing_spec.rb +25 -0
  226. data/new_bridge_specs/integration_phase5_3_callback_args_spec.rb +28 -0
  227. data/new_bridge_specs/integration_phase5_3_callback_error_spec.rb +22 -0
  228. data/new_bridge_specs/integration_phase5_3_callback_timeout_spec.rb +28 -0
  229. data/new_bridge_specs/integration_phase5_3_callbacks_smoke_spec.rb +22 -0
  230. data/new_bridge_specs/integration_phase5_3_edge_cases_spec.rb +52 -0
  231. data/new_bridge_specs/integration_phase5_3_nested_spec.rb +30 -0
  232. data/new_bridge_specs/integration_phase5_4_concurrent_sessions_spec.rb +53 -0
  233. data/new_bridge_specs/integration_phase5_4_nested_session_callbacks_spec.rb +49 -0
  234. data/new_bridge_specs/integration_phase5_4_session_routing_spec.rb +38 -0
  235. data/new_bridge_specs/integration_phase5_5_stress_concurrency_spec.rb +52 -0
  236. data/new_bridge_specs/integration_phase5_5_unbox_walk_spec.rb +46 -0
  237. data/new_bridge_specs/phase0_protocol_spec.rb +96 -0
  238. data/new_bridge_specs/phase1_req_ret_spec.rb +66 -0
  239. data/new_bridge_specs/phase2_multi_instance_spec.rb +67 -0
  240. data/new_bridge_specs/phase3_callbacks_spec.rb +71 -0
  241. data/new_bridge_specs/phase4_2_hardening_spec.rb +252 -0
  242. data/new_bridge_specs/phase4_3_r_instance_manager_spec.rb +85 -0
  243. data/new_bridge_specs/phase4_nested_callbacks_spec.rb +123 -0
  244. data/r_requires/ggplot.rb +0 -0
  245. data/r_requires/knitr.rb +0 -0
  246. data/specs/all.rb +15 -11
  247. data/specs/arrow_from_ruby_batches_spec.rb +50 -0
  248. data/specs/arrow_semantics_spec.rb +64 -0
  249. data/specs/bridge_concurrent_spec.rb +46 -0
  250. data/specs/bridge_nested_spec.rb +25 -0
  251. data/specs/dataframe_semantics_spec.rb +122 -0
  252. data/specs/dataframe_single_index_logical_filter_spec.rb +21 -0
  253. data/specs/dispatch_probe_cache_spec.rb +38 -0
  254. data/specs/dispatch_probe_error_class_fallback_spec.rb +20 -0
  255. data/specs/dispatch_probe_fallback_spec.rb +18 -0
  256. data/specs/environment_semantics_spec.rb +89 -0
  257. data/specs/field_access_spec.rb +31 -0
  258. data/specs/figures/bg.jpeg +0 -0
  259. data/specs/figures/bg.png +0 -0
  260. data/specs/figures/bg.svg +168 -57
  261. data/specs/figures/dose_len.png +0 -0
  262. data/specs/figures/no_args.jpeg +0 -0
  263. data/specs/figures/no_args.png +0 -0
  264. data/specs/figures/no_args.svg +168 -57
  265. data/specs/figures/width_height.jpeg +0 -0
  266. data/specs/figures/width_height.png +0 -0
  267. data/specs/figures/width_height_units1.jpeg +0 -0
  268. data/specs/figures/width_height_units1.png +0 -0
  269. data/specs/figures/width_height_units2.jpeg +0 -0
  270. data/specs/figures/width_height_units2.png +0 -0
  271. data/specs/formula_semantics_spec.rb +81 -0
  272. data/specs/galaaz_util_exec_ruby_spec.rb +85 -0
  273. data/specs/galaaz_util_inline_file_spec.rb +54 -0
  274. data/specs/gknit_cli_option_permutation_spec.rb +24 -0
  275. data/specs/gknit_include_engine_spec.rb +72 -0
  276. data/specs/gknit_install_timeout_report_spec.rb +69 -0
  277. data/specs/gknit_internal_error_report_spec.rb +57 -0
  278. data/specs/gknit_vector_map_output_spec.rb +59 -0
  279. data/specs/globalenv_guardrail_spec.rb +52 -0
  280. data/specs/language_expression_semantics_spec.rb +145 -0
  281. data/specs/list_semantics_spec.rb +111 -0
  282. data/specs/new_bridge_bulk_dataframe_transfer_spec.rb +44 -0
  283. data/specs/new_bridge_bulk_vector_transfer_spec.rb +73 -0
  284. data/specs/new_bridge_callback_timeout_spec.rb +69 -0
  285. data/specs/new_bridge_eval_r_fallback_spec.rb +55 -0
  286. data/specs/nil_null_spec.rb +42 -0
  287. data/specs/object_build_phase2_spec.rb +53 -0
  288. data/specs/phase1_callback_bridge_spec.rb +84 -0
  289. data/specs/phase2_gknit_generic_rendering_guardrail_spec.rb +46 -0
  290. data/specs/phase2_gknit_no_raw_code_leakage_spec.rb +43 -0
  291. data/specs/phase3_gknit_generic_graphics_capture_spec.rb +71 -0
  292. data/specs/plot_device_semantics_spec.rb +28 -0
  293. data/specs/plot_snapshot_semantics_spec.rb +58 -0
  294. data/specs/protocol_result_spec.rb +236 -0
  295. data/specs/r_batch_fail_fast_spec.rb +47 -0
  296. data/specs/r_bridge_bootstrap_spec.rb +11 -0
  297. data/specs/r_devices.spec.rb +1 -1
  298. data/specs/r_eval.spec.rb +16 -18
  299. data/specs/r_function.spec.rb +1 -1
  300. data/specs/r_instance_manager_spec.rb +285 -0
  301. data/specs/r_list_apply.spec.rb +15 -15
  302. data/specs/r_matrix.spec.rb +0 -0
  303. data/specs/r_nse.spec.rb +5 -5
  304. data/specs/r_object_send_dispatch_spec.rb +13 -0
  305. data/specs/r_vector_comparator_spec.rb +8 -0
  306. data/specs/r_vector_creation.spec.rb +0 -0
  307. data/specs/r_vector_functions.spec.rb +0 -0
  308. data/specs/r_vector_object.spec.rb +0 -0
  309. data/specs/r_vector_operators.spec.rb +0 -0
  310. data/specs/r_vector_structured_scalar_reads_spec.rb +35 -0
  311. data/specs/r_vector_subsetting.spec.rb +0 -0
  312. data/specs/range_helper_spec.rb +21 -0
  313. data/specs/rsupport_scope_spec.rb +28 -0
  314. data/specs/rsupport_var_name_thread_safety_spec.rb +24 -0
  315. data/specs/scalar_character_spec.rb +44 -0
  316. data/specs/scoped_symbol_dsl_refinement_spec.rb +40 -0
  317. data/specs/session_env_bridge_spec.rb +25 -0
  318. data/specs/simplecov_bootstrap_spec.rb +10 -0
  319. data/specs/spec_helper.rb +10 -0
  320. data/specs/tmp.rb +0 -0
  321. data/specs/unboxing_recursion_regression_spec.rb +30 -0
  322. data/specs/unboxing_spec.rb +49 -0
  323. data/specs/verify_callbacks.rb +42 -0
  324. data/sty/galaaz.sty +0 -0
  325. data/version.rb +1 -1
  326. metadata +194 -64
  327. data/blogs/galaaz_ggplot/galaaz_ggplot.html +0 -520
  328. data/blogs/galaaz_ggplot/galaaz_ggplot.pdf +0 -0
  329. data/blogs/galaaz_ggplot/midwest.html +0 -188
  330. data/blogs/gknit/gknit.html +0 -2266
  331. data/blogs/gknit/gknit.pdf +0 -0
  332. data/blogs/manual/manual.html +0 -4638
  333. data/blogs/manual/manual.pdf +0 -0
  334. data/blogs/manual/manual_files/figure-latex/diverging_bar.pdf +0 -0
  335. data/blogs/nse_dplyr/nse_dplyr.html +0 -878
  336. data/blogs/nse_dplyr/nse_dplyr.pdf +0 -0
  337. data/blogs/oh_my/oh_my.html +0 -568
  338. data/blogs/ruby_plot/ruby_plot.html +0 -544
  339. data/blogs/ruby_plot/ruby_plot.pdf +0 -0
  340. data/examples/latex_templates/Test-acs_article/Test-acs_article.pdf +0 -0
  341. data/examples/latex_templates/Test-aea_article/Test-aea_article.pdf +0 -0
  342. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdf +0 -0
  343. data/examples/latex_templates/Test-amq_article/pics/Figure2.pdf +0 -0
  344. data/examples/latex_templates/Test-asa_article/Test-asa_article.pdf +0 -0
  345. data/examples/latex_templates/Test-ieee_article/Test-ieee_article.pdf +0 -0
  346. data/examples/latex_templates/Test-rjournal_article/RJwrapper.pdf +0 -0
  347. data/examples/latex_templates/Test-springer_article/Test-springer_article.pdf +0 -0
  348. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.pdf +0 -0
  349. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.pdf +0 -0
  350. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.pdf +0 -0
  351. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.pdf +0 -0
  352. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.pdf +0 -0
  353. data/specs/r_dataframe.spec.rb +0 -379
  354. data/specs/r_environment.spec.rb +0 -140
  355. data/specs/r_formula.spec.rb +0 -232
  356. data/specs/r_language.spec.rb +0 -112
  357. data/specs/r_list.spec.rb +0 -293
  358. data/specs/r_plots.spec.rb +0 -72
  359. data/specs/ruby_expression.spec.rb +0 -316
@@ -0,0 +1,824 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require_relative '../new_bridge'
5
+
6
+ module R
7
+ # Phase 5.1 adapter: expose a minimal compatibility surface for legacy call sites
8
+ # that historically depended on ShadowBridge (now deprecated/obsolete).
9
+ # backed by NewBridge::SessionClient.
10
+ #
11
+ # Implemented compatibility methods:
12
+ # - eval_r(code)
13
+ # - eval_r_with_result("g2_v_x <- ...")
14
+ # - ready?
15
+ # - in_callback?
16
+ # - close
17
+ class NewBridgeAdapter
18
+ include Singleton
19
+
20
+ def initialize
21
+ @in_callback = false
22
+ @ready = false
23
+ source_path = ENV['GALAAZ_NEW_BRIDGE_SOURCE'] ||
24
+ File.expand_path('../../ext/new_bridge/galaaz_gatekeeper_phase1.cpp', __dir__)
25
+ @client = NewBridge::SessionClient.new(source_path: source_path)
26
+ @client.start(accept_timeout: 120)
27
+
28
+ # Compatibility with the legacy ShadowBridge setup:
29
+ # - `R.awt` / X11 for plotting (examples/sthda_ggplot).
30
+ # - `missing_arg()` for Ruby :all in `[` / tbl subset (R::Support.parse_arg).
31
+ # Use rlang::missing_arg when available: tibble/tidyselect reject the legacy
32
+ # quote(f(,0))[[2]] sentinel ("Subscript ... can't contain the empty string").
33
+ # The gatekeeper evaluates each REQ in a dedicated per-session env; globals are shared.
34
+ init_timeout = effective_bridge_timeout(timeout: nil)
35
+ @client.eval_r(
36
+ <<~RCODE,
37
+ ({
38
+ assign('awt', function(...) { X11(...) }, envir = .GlobalEnv)
39
+ if (requireNamespace('rlang', quietly = TRUE)) {
40
+ assign('missing_arg', getExportedValue('rlang', 'missing_arg'), envir = .GlobalEnv)
41
+ } else {
42
+ assign('missing_arg', function() { quote(f(,0))[[2]] }, envir = .GlobalEnv)
43
+ }
44
+ # Build tidyselect-compatible range expressions from symbols, e.g. year:day.
45
+ if (!exists('range_helper', envir = .GlobalEnv, inherits = FALSE)) {
46
+ assign('range_helper', function(col_ini, col_end, remove = FALSE) {
47
+ ini <- substitute(col_ini)
48
+ fin <- substitute(col_end)
49
+ rng <- as.call(list(as.name(':'), ini, fin))
50
+ if (isTRUE(remove)) {
51
+ as.call(list(as.name('-'), rng))
52
+ } else {
53
+ rng
54
+ }
55
+ }, envir = .GlobalEnv)
56
+ }
57
+ if (!exists('up_to', envir = .GlobalEnv, inherits = FALSE)) {
58
+ assign('up_to', function(col_ini, col_end, remove = FALSE) {
59
+ range_helper(col_ini, col_end, remove = remove)
60
+ }, envir = .GlobalEnv)
61
+ }
62
+ # Legacy Galaaz: env_names(env) lists bindings (base R has no env_names).
63
+ if (!exists('env_names', envir = .GlobalEnv, inherits = FALSE)) {
64
+ assign('env_names', function(envir) {
65
+ if (missing(envir)) envir <- parent.frame()
66
+ ls(envir = envir, all.names = TRUE, sorted = TRUE)
67
+ }, envir = .GlobalEnv)
68
+ }
69
+ # Session-scoped environment helpers for transient bridge state.
70
+ if (!exists('galaaz_ensure_session_env', envir = .GlobalEnv, inherits = FALSE)) {
71
+ assign('galaaz_ensure_session_env', function(session_id) {
72
+ sid <- as.character(session_id)
73
+ # Primary path: reuse gatekeeper's per-session registry.
74
+ if (exists('.galaaz_sessions', envir = .GlobalEnv, inherits = FALSE)) {
75
+ sessions <- get('.galaaz_sessions', envir = .GlobalEnv, inherits = FALSE)
76
+ if (!is.list(sessions)) {
77
+ sessions <- list()
78
+ }
79
+ if (is.null(sessions[[sid]])) {
80
+ sessions[[sid]] <- new.env(parent = .GlobalEnv)
81
+ assign('.galaaz_sessions', sessions, envir = .GlobalEnv)
82
+ }
83
+ return(sessions[[sid]])
84
+ }
85
+ # Compatibility fallback if gatekeeper registry is unavailable.
86
+ nm <- paste0('galaaz_session_env_', gsub('[^A-Za-z0-9_]', '_', sid))
87
+ if (!exists(nm, envir = .GlobalEnv, inherits = FALSE)) {
88
+ assign(nm, new.env(parent = .GlobalEnv), envir = .GlobalEnv)
89
+ }
90
+ get(nm, envir = .GlobalEnv, inherits = FALSE)
91
+ }, envir = .GlobalEnv)
92
+ }
93
+ if (!exists('galaaz_assign_session', envir = .GlobalEnv, inherits = FALSE)) {
94
+ assign('galaaz_assign_session', function(session_id, key, value) {
95
+ en <- galaaz_ensure_session_env(session_id)
96
+ assign(as.character(key), value, envir = en)
97
+ invisible(NULL)
98
+ }, envir = .GlobalEnv)
99
+ }
100
+ if (!exists('galaaz_get_session', envir = .GlobalEnv, inherits = FALSE)) {
101
+ assign('galaaz_get_session', function(session_id, key) {
102
+ en <- galaaz_ensure_session_env(session_id)
103
+ get(as.character(key), envir = en, inherits = FALSE)
104
+ }, envir = .GlobalEnv)
105
+ }
106
+ if (!exists('galaaz_rm_session', envir = .GlobalEnv, inherits = FALSE)) {
107
+ assign('galaaz_rm_session', function(session_id, key) {
108
+ en <- galaaz_ensure_session_env(session_id)
109
+ rm(list = as.character(key), envir = en, inherits = FALSE)
110
+ invisible(NULL)
111
+ }, envir = .GlobalEnv)
112
+ }
113
+ 0L
114
+ })
115
+ RCODE
116
+ session_id: current_session_id,
117
+ parent_id: callback_parent_id,
118
+ timeout: init_timeout
119
+ )
120
+ @ready = true
121
+ end
122
+
123
+ def ready?
124
+ @ready
125
+ end
126
+
127
+ def in_callback?
128
+ @in_callback
129
+ end
130
+
131
+ # Execute bridge operations within a specific NewBridge session_id.
132
+ # Scope is thread-local and restored after block execution.
133
+ def with_session(session_id)
134
+ old = Thread.current[:galaaz_new_bridge_session_id]
135
+ Thread.current[:galaaz_new_bridge_session_id] = session_id.to_s
136
+ yield
137
+ ensure
138
+ Thread.current[:galaaz_new_bridge_session_id] = old
139
+ end
140
+
141
+ # Minimal textual compatibility for existing call sites that expect
142
+ # "[1] <value>" style output for simple scalar evaluations.
143
+ def eval_r(code, timeout: nil)
144
+ effective_timeout = effective_bridge_timeout(timeout: timeout)
145
+ if ENV['GALAAZ_TIMEOUT_DEBUG']
146
+ STDERR.puts "[TIMEOUT_DEBUG] NewBridgeAdapter.eval_r timeout=#{effective_timeout}s explicit_timeout=#{timeout.inspect} env_bridge=#{ENV['GALAAZ_BRIDGE_TIMEOUT_SEC'].inspect}"
147
+ STDERR.flush
148
+ end
149
+ out = @client.eval_r(code, session_id: current_session_id, parent_id: callback_parent_id, timeout: effective_timeout)
150
+ format_scalar_print(out)
151
+ rescue NewBridge::SessionClient::RProcessError => e
152
+ # Compatibility fallback for side-effect-only evals:
153
+ # only retry when the error clearly matches phase1 scalar restrictions.
154
+ raise unless phase1_scalar_fallback_error?(e)
155
+
156
+ @client.eval_r("({ #{code}; 0L })", session_id: current_session_id, parent_id: callback_parent_id, timeout: effective_timeout)
157
+ ''
158
+ end
159
+
160
+ # Async string eval: +block+ receives +NewBridge::EvalResult+; on success +#value+ is the same
161
+ # formatted string as +eval_r+ (no phase1 scalar fallback retry on async path).
162
+ #
163
+ # @param timeout [nil, Numeric] +nil+ means wait without Ruby-side limit; numeric starts a timer.
164
+ def eval_r_async(code, timeout: nil, &block)
165
+ raise ArgumentError, 'eval_r_async requires a block' unless block
166
+
167
+ @client.eval_r_async(code.to_s,
168
+ session_id: current_session_id,
169
+ parent_id: callback_parent_id,
170
+ timeout: timeout) do |raw|
171
+ mapped =
172
+ if raw.ok?
173
+ begin
174
+ NewBridge::EvalResult.success(format_scalar_print(raw.value))
175
+ rescue StandardError => e
176
+ NewBridge::EvalResult.failure(e)
177
+ end
178
+ else
179
+ raw
180
+ end
181
+ block.call(mapped)
182
+ end
183
+ end
184
+
185
+ # Async +eval_r_with_result+ payload; +block+ receives +NewBridge::EvalResult+ where success +#value+
186
+ # is a Hash +:envelope+, +:var_name+, +:r_expr+ for +R::Support.ruby_result_from_envelope+.
187
+ def eval_r_with_result_async(assignment_code, timeout: nil, &block)
188
+ raise ArgumentError, 'eval_r_with_result_async requires a block' unless block
189
+
190
+ code = "__G_EVAL_WITH_RESULT__#{assignment_code}"
191
+ @client.eval_r_async(code,
192
+ session_id: current_session_id,
193
+ parent_id: callback_parent_id,
194
+ timeout: timeout) do |raw|
195
+ if raw.ok?
196
+ begin
197
+ env = to_legacy_envelope(raw.value)
198
+ m = assignment_code.match(/\A(\S+)\s*<-\s*(.*)\z/m)
199
+ unless m
200
+ block.call(NewBridge::EvalResult.failure(StandardError.new("eval_r_with_result_async: bad assignment #{assignment_code.inspect}")))
201
+ next
202
+ end
203
+ vn = m[1]
204
+ rx = m[2].to_s.strip
205
+ block.call(NewBridge::EvalResult.success(envelope: env, var_name: vn, r_expr: rx))
206
+ rescue StandardError => e
207
+ block.call(NewBridge::EvalResult.failure(e))
208
+ end
209
+ else
210
+ block.call(raw)
211
+ end
212
+ end
213
+ end
214
+
215
+ # Return the printed representation of an R object as a Ruby string.
216
+ def print_r(var_name)
217
+ parsed = @client.eval_r("paste(capture.output(print(#{var_name})), collapse='\\n')",
218
+ session_id: current_session_id,
219
+ parent_id: callback_parent_id,
220
+ timeout: effective_bridge_timeout(timeout: nil))
221
+ if parsed['kind'].to_s == 'character'
222
+ unescape_scalar_character(parsed['value'].to_s)
223
+ else
224
+ parsed['value'].to_s
225
+ end
226
+ end
227
+
228
+ # Legacy-compatible envelope shape expected by existing bridge specs.
229
+ # Supports scalar assignments used by current migration subset.
230
+ def eval_r_with_result(assignment_code)
231
+ parsed = @client.eval_r("__G_EVAL_WITH_RESULT__#{assignment_code}",
232
+ session_id: current_session_id,
233
+ parent_id: callback_parent_id,
234
+ timeout: effective_bridge_timeout(timeout: nil))
235
+ to_legacy_envelope(parsed)
236
+ end
237
+
238
+ # Multiple eval_r_with_result-style assignments in one REQ/RET (Phase 4). Fail-fast: first R error
239
+ # skips remaining ops. Transport still returns HTTP-like success with payload kind +batch_error+.
240
+ # @return [Array<Hash>] legacy envelopes in order
241
+ # @raise [R::BatchEvaluationError] on first failed op (+#failed_index+ is 0-based)
242
+ def batch_eval_r_with_result(assignment_codes)
243
+ codes = Array(assignment_codes).map(&:to_s)
244
+ raise ArgumentError, 'batch_eval_r_with_result requires at least one assignment' if codes.empty?
245
+
246
+ body = codes.join("\u001E")
247
+ parsed = @client.eval_r("__G_BATCH_EVAL_WITH_RESULT__#{body}",
248
+ session_id: current_session_id,
249
+ parent_id: callback_parent_id,
250
+ timeout: effective_bridge_timeout(timeout: nil))
251
+ decode_batch_eval_payload(parsed)
252
+ end
253
+
254
+ # Single round-trip replacement for the two eval_r probes in
255
+ # R::Support.process_missing_dispatch (is_field + is_func).
256
+ # Gatekeeper: __G_DISPATCH_PROBE__|handle|name
257
+ def dispatch_probe(handle, name)
258
+ parsed = @client.eval_r("__G_DISPATCH_PROBE__|#{handle}|#{name}",
259
+ session_id: current_session_id,
260
+ parent_id: callback_parent_id,
261
+ timeout: effective_bridge_timeout(timeout: nil))
262
+ unless parsed['kind'].to_s == 'dispatch_probe'
263
+ raise "dispatch_probe: unexpected payload #{parsed.inspect}"
264
+ end
265
+
266
+ { is_field: parsed['is_field'], is_func: parsed['is_func'] }
267
+ end
268
+
269
+ # Ensure session-scoped environment exists and return its R env handle.
270
+ def ensure_session_env(session_id = current_session_id)
271
+ sid = session_id.to_s
272
+ @client.eval_r("({ galaaz_ensure_session_env(#{sid.inspect}); 0L })",
273
+ session_id: current_session_id,
274
+ parent_id: callback_parent_id,
275
+ timeout: effective_bridge_timeout(timeout: nil))
276
+ nil
277
+ end
278
+
279
+ # Assign value expression into session environment.
280
+ # value_expr must be a valid R expression string.
281
+ def assign_session(session_id, key, value_expr)
282
+ sid = session_id.to_s
283
+ k = key.to_s
284
+ @client.eval_r("({ galaaz_assign_session(#{sid.inspect}, #{k.inspect}, { #{value_expr} }); 0L })",
285
+ session_id: current_session_id,
286
+ parent_id: callback_parent_id,
287
+ timeout: effective_bridge_timeout(timeout: nil))
288
+ nil
289
+ end
290
+
291
+ # Get a value from session environment (parsed payload).
292
+ def get_session(session_id, key)
293
+ sid = session_id.to_s
294
+ k = key.to_s
295
+ @client.eval_r("galaaz_get_session(#{sid.inspect}, #{k.inspect})",
296
+ session_id: current_session_id,
297
+ parent_id: callback_parent_id,
298
+ timeout: effective_bridge_timeout(timeout: nil))
299
+ end
300
+
301
+ # Remove a key from session environment.
302
+ def rm_session(session_id, key)
303
+ sid = session_id.to_s
304
+ k = key.to_s
305
+ @client.eval_r("({ galaaz_rm_session(#{sid.inspect}, #{k.inspect}); 0L })",
306
+ session_id: current_session_id,
307
+ parent_id: callback_parent_id,
308
+ timeout: effective_bridge_timeout(timeout: nil))
309
+ nil
310
+ end
311
+
312
+ def effective_bridge_timeout(timeout: nil)
313
+ return timeout if timeout
314
+
315
+ raw = ENV['GALAAZ_BRIDGE_TIMEOUT_SEC']
316
+ return 60 if raw.nil?
317
+
318
+ s = raw.to_s.strip
319
+ return 60 if s.empty?
320
+
321
+ Integer(s)
322
+ rescue StandardError, ArgumentError
323
+ 60
324
+ end
325
+
326
+ # Phase 5.3 callback stub registration for R::Support.parse_arg.
327
+ # Returns an R function string that forwards callback execution to Ruby
328
+ # using the NewBridge CALL/RET path.
329
+ def register_callback_proc_stub(proc_or_method)
330
+ callback_session_id = current_session_id
331
+ callback_wait_ms = begin
332
+ raw = ENV.fetch('GALAAZ_CALLBACK_TIMEOUT_MS', '120000').to_s.strip
333
+ v = Integer(raw)
334
+ v > 0 ? v : 120_000
335
+ rescue StandardError
336
+ 120_000
337
+ end
338
+ if ENV['GALAAZ_TIMEOUT_DEBUG']
339
+ STDERR.puts "[TIMEOUT_DEBUG] NewBridgeAdapter.register_callback_proc_stub callback_wait_ms=#{callback_wait_ms} env_callback=#{ENV['GALAAZ_CALLBACK_TIMEOUT_MS'].inspect}"
340
+ STDERR.flush
341
+ end
342
+ # Stable R-safe fragment for namespacing callback result keys (session + call id).
343
+ sess_key = callback_session_id.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
344
+ callback_call_id = @client.register_callback do |payload, call_id|
345
+ old = Thread.current[:galaaz_new_bridge_parent_id]
346
+ old_session = Thread.current[:galaaz_new_bridge_session_id]
347
+ Thread.current[:galaaz_new_bridge_parent_id] = call_id
348
+ Thread.current[:galaaz_new_bridge_session_id] = callback_session_id
349
+ @in_callback = true
350
+ begin
351
+ cb_args = callback_payload_to_r_objects(payload)
352
+ # Legacy arity-2 procs (phase 5.3 specs): (first R arg or nil, Ruby CALL envelope id).
353
+ # Knitr/RubyCallback use arity != 2 and receive call(*cb_args).
354
+ ar = proc_or_method.respond_to?(:arity) ? proc_or_method.arity : 0
355
+ raw = if ar == 2 && cb_args.size <= 1
356
+ proc_or_method.call(cb_args[0], call_id)
357
+ else
358
+ proc_or_method.call(*cb_args)
359
+ end
360
+ # Semantic return value: store in session env.
361
+ # Transport RET is ACK-only (SessionClient sends "1"); R stub reads env after CALL/RET.
362
+ slot_key = "r_#{sess_key}_#{call_id.gsub('-', '_')}"
363
+ rhs = raw.is_a?(String) ? raw : R::Support.parse_arg(raw)
364
+ assign_session(callback_session_id, slot_key, rhs)
365
+ # Semantic value is staged in session env; RET must be ACK-only (nil → transport "1").
366
+ nil
367
+ ensure
368
+ @in_callback = false
369
+ Thread.current[:galaaz_new_bridge_session_id] = old_session
370
+ Thread.current[:galaaz_new_bridge_parent_id] = old
371
+ end
372
+ end
373
+
374
+ # Each ... arg is assigned in session env under a temporary handle.
375
+ # Names must be g2_v + digits
376
+ # only — gatekeeper valid_bridge_handle_token rejects g2_v_cb_* (letters after g2_v), which
377
+ # breaks dispatch_probe when Ruby wraps the handle as R::Object.
378
+ "function(...) {
379
+ args <- list(...)
380
+ handles <- character(0)
381
+ if (length(args) > 0L) {
382
+ for (i in seq_along(args)) {
383
+ h <- paste0('g2_v', as.integer(stats::runif(1, 1e7, 9e7 - 1L)), sprintf('%04d', as.integer(i)))
384
+ galaaz_assign_session('#{callback_session_id}', h, args[[i]])
385
+ cls <- paste(class(args[[i]]), collapse = ' ')
386
+ handles <- c(handles, paste0(h, ':', cls))
387
+ }
388
+ }
389
+ payload <- paste(handles, collapse = '|')
390
+ gid <- '#{callback_call_id}'
391
+ galaaz_callback_call_phase3(gid, payload, #{callback_wait_ms})
392
+ sess <- '#{sess_key}'
393
+ nm <- paste0('r_', sess, '_', gsub('-', '_', gid))
394
+ sid <- '#{callback_session_id}'
395
+ if (exists(nm, envir = galaaz_ensure_session_env(sid), inherits = FALSE)) {
396
+ val <- galaaz_get_session(sid, nm)
397
+ galaaz_rm_session(sid, nm)
398
+ return(val)
399
+ }
400
+ stop('callback staged no semantic value in session env')
401
+ }"
402
+ end
403
+
404
+ def close
405
+ @client&.stop
406
+ @ready = false
407
+ end
408
+
409
+ # Structural unboxing helpers (Phase 5.5 hardening):
410
+ # Keep deep unboxing on explicit bridge reads instead of generic method-missing paths.
411
+ def unbox_list_length(var_name)
412
+ probe = probe_node_expr(var_name)
413
+ probe[:length].to_i
414
+ end
415
+
416
+ def unbox_list_element(var_name, one_based_index)
417
+ tmp = ::R::Support.generate_var_name
418
+ probe = probe_node_expr("#{var_name}[[#{one_based_index}]]", assign_to: tmp)
419
+
420
+ case probe[:kind]
421
+ when 'null'
422
+ nil
423
+ when 'list'
424
+ ::R::Object.build(tmp, nil, r_class: 'list')
425
+ when 'scalar_integer'
426
+ probe[:value]
427
+ when 'scalar_double'
428
+ probe[:value]
429
+ when 'scalar_logical'
430
+ probe[:value]
431
+ when 'scalar_character'
432
+ v = probe[:value]
433
+ (v.is_a?(::String) && v =~ /^rb_obj_\d+$/) ? ::R::Support.get_ruby_object(v) : v
434
+ else
435
+ r_class = probe[:r_class].to_s.strip
436
+ return nil if r_class == 'NULL'
437
+ ::R::Object.build(tmp, nil, r_class: r_class.empty? ? 'unknown' : r_class)
438
+ end
439
+ end
440
+
441
+ # Specialized bounded traversal for deep unboxing checks.
442
+ # Walks nested list structures in one bridge call and reports whether limits were exceeded.
443
+ # Returns:
444
+ # { status: :ok|:depth_limit|:node_limit, nodes: Integer, max_depth: Integer }
445
+ def unbox_walk(var_name, max_depth:, max_nodes:)
446
+ cmd = "__G_UNBOX_WALK__|#{var_name}|#{max_depth.to_i}|#{max_nodes.to_i}"
447
+ parsed = @client.eval_r(cmd, session_id: current_session_id, parent_id: callback_parent_id, timeout: effective_bridge_timeout(timeout: nil))
448
+ status =
449
+ case parsed['status'].to_s
450
+ when 'DEPTH' then :depth_limit
451
+ when 'NODE' then :node_limit
452
+ else :ok
453
+ end
454
+ { status: status, nodes: parsed['nodes'].to_i, max_depth: parsed['max_depth'].to_i }
455
+ end
456
+
457
+ # Specialized one-call materialization for nested list/scalar/null trees.
458
+ # Returns:
459
+ # { status: :ok|:depth_limit|:node_limit|:unsupported, nodes:, max_depth:, value: }
460
+ def unbox_materialize(var_name, max_depth:, max_nodes:)
461
+ cmd = "__G_UNBOX_MATERIALIZE__|#{var_name}|#{max_depth.to_i}|#{max_nodes.to_i}"
462
+ parsed = @client.eval_r(cmd, session_id: current_session_id, parent_id: callback_parent_id, timeout: effective_bridge_timeout(timeout: nil))
463
+ status =
464
+ case parsed['status'].to_s
465
+ when 'DEPTH' then :depth_limit
466
+ when 'NODE' then :node_limit
467
+ when 'UNSUPPORTED' then :unsupported
468
+ else :ok
469
+ end
470
+ { status: status, nodes: parsed['nodes'].to_i, max_depth: parsed['max_depth'].to_i, value: parsed['value'] }
471
+ end
472
+
473
+ # Unbox atomic vectors to Ruby Arrays.
474
+ # Primary path: gatekeeper `__G_PULL_VECTOR__` (one round-trip for full vector).
475
+ # Fallback: per-element scalar eval (complex types, older gatekeeper, errors).
476
+ def pull_vector(var_name)
477
+ begin
478
+ raw = eval_pull_vector_payload(var_name)
479
+ out = decode_pull_vector_bulk_payload(raw)
480
+ return out unless out.nil?
481
+ rescue NewBridge::SessionClient::RProcessError
482
+ # Unsupported SEXP type, unknown handle, or legacy binary without pull_vector.
483
+ end
484
+ pull_vector_elementwise(var_name)
485
+ end
486
+
487
+ # Indexed vector unboxing helpers expected by R::Vector#unboxed_get.
488
+ # offset is 0-based (Ruby index), R indexing is 1-based.
489
+ def pull_integer_vector(var_name, _total_size, offset = 0, chunk_size = nil)
490
+ chunk_size ||= 1
491
+ start_1 = offset + 1
492
+ begin
493
+ raw = eval_pull_vector_payload(var_name, slice_start_1based: start_1, slice_count: chunk_size)
494
+ out = decode_pull_vector_bulk_payload(raw, want_kind: 'integer_vector')
495
+ return out if out.is_a?(Array) && out.size == chunk_size
496
+ rescue NewBridge::SessionClient::RProcessError
497
+ end
498
+ pull_integer_vector_elementwise(var_name, offset, chunk_size)
499
+ end
500
+
501
+ def pull_double_vector(var_name, _total_size, offset = 0, chunk_size = nil)
502
+ chunk_size ||= 1
503
+ start_1 = offset + 1
504
+ begin
505
+ raw = eval_pull_vector_payload(var_name, slice_start_1based: start_1, slice_count: chunk_size)
506
+ out = decode_pull_vector_bulk_payload(raw, want_kind: 'double_vector')
507
+ return out if out.is_a?(Array) && out.size == chunk_size
508
+ rescue NewBridge::SessionClient::RProcessError
509
+ end
510
+ pull_double_vector_elementwise(var_name, offset, chunk_size)
511
+ end
512
+
513
+ # Push Ruby numeric array values into an R numeric vector.
514
+ # - ruby_array: Ruby Array of Numeric/nil (nil mapped to NA_real_)
515
+ # - var_name: target R variable handle/name
516
+ # - offset/total_size: when provided, writes a slice using 0-based offset
517
+ def push_double_vector(ruby_array, var_name, offset = nil, _total_size = nil)
518
+ values = Array(ruby_array)
519
+ return nil if values.empty?
520
+
521
+ serialized = values.map do |v|
522
+ if v.nil?
523
+ 'NA_real_'
524
+ elsif v.is_a?(Numeric)
525
+ v.finite? ? v.to_f.to_s : 'NA_real_'
526
+ else
527
+ begin
528
+ Float(v).to_s
529
+ rescue StandardError
530
+ 'NA_real_'
531
+ end
532
+ end
533
+ end.join(', ')
534
+
535
+ if offset
536
+ start_i = offset.to_i + 1
537
+ end_i = offset.to_i + values.length
538
+ @client.eval_r("({ #{var_name}[#{start_i}:#{end_i}] <- c(#{serialized}); 0L })",
539
+ session_id: current_session_id,
540
+ parent_id: callback_parent_id,
541
+ timeout: effective_bridge_timeout(timeout: nil))
542
+ else
543
+ @client.eval_r("({ #{var_name} <- c(#{serialized}); 0L })",
544
+ session_id: current_session_id,
545
+ parent_id: callback_parent_id,
546
+ timeout: effective_bridge_timeout(timeout: nil))
547
+ end
548
+ nil
549
+ end
550
+
551
+ # data.frame unboxing: Ruby hash { "colname" => [values...] }.
552
+ #
553
+ # Column-wise bulk path: assign each column to a temp `g2_v*` handle, then
554
+ # `pull_vector` (uses `__G_PULL_VECTOR__` when supported). Per-cell scalar
555
+ # evals are avoided for atomic columns; list / unsupported columns still
556
+ # fall back inside `pull_vector`.
557
+ def pull_dataframe(var_name)
558
+ t = effective_bridge_timeout(timeout: nil)
559
+ sid = current_session_id
560
+ pid = callback_parent_id
561
+
562
+ ncol = @client.eval_r("length(#{var_name})", session_id: sid, parent_id: pid, timeout: t)['value'].to_i
563
+ return {} if ncol <= 0
564
+
565
+ col_hash = {}
566
+
567
+ (1..ncol).each do |i|
568
+ col_name_parsed = @client.eval_r("names(#{var_name})[#{i}]", session_id: sid, parent_id: pid, timeout: t)
569
+ col_name = col_name_parsed['value']
570
+ col_name = col_name.to_s if col_name
571
+
572
+ tmp = ::R::Support.generate_var_name
573
+ @client.eval_r("({ #{tmp} <- #{var_name}[[#{i}]]; 0L })", session_id: sid, parent_id: pid, timeout: t)
574
+ col_hash[col_name] = pull_vector(tmp)
575
+ end
576
+
577
+ col_hash
578
+ end
579
+
580
+ private
581
+
582
+ # Decode CALL payload from register_callback_proc_stub R side (handle:class|...).
583
+ def callback_payload_to_r_objects(payload)
584
+ s = payload.to_s.strip
585
+ return [] if s.empty?
586
+
587
+ s.split('|').filter_map do |pair|
588
+ next if pair.empty?
589
+
590
+ handle, r_class = pair.split(':', 2)
591
+ R::Object.build(handle, nil, r_class: r_class)
592
+ end
593
+ end
594
+
595
+ def callback_parent_id
596
+ Thread.current[:galaaz_new_bridge_parent_id]
597
+ end
598
+
599
+ def current_session_id
600
+ Thread.current[:galaaz_new_bridge_session_id] || 'default'
601
+ end
602
+
603
+ def probe_node_expr(expr, assign_to: nil)
604
+ sep = "\u001F"
605
+ assign_line = assign_to ? "#{assign_to} <- (#{expr})" : nil
606
+ gk_obj_line = assign_to ? ".gk_obj <- #{assign_to}" : ".gk_obj <- (#{expr})"
607
+ r_code = <<~RCODE
608
+ ({
609
+ #{assign_line}
610
+ #{gk_obj_line}
611
+ .gk_sep <- "#{sep}"
612
+ .gk_kind <- if (is.null(.gk_obj)) {
613
+ "null"
614
+ } else if (is.list(.gk_obj)) {
615
+ "list"
616
+ } else if (length(.gk_obj) == 1L && is.integer(.gk_obj)) {
617
+ "scalar_integer"
618
+ } else if (length(.gk_obj) == 1L && is.double(.gk_obj)) {
619
+ "scalar_double"
620
+ } else if (length(.gk_obj) == 1L && is.logical(.gk_obj)) {
621
+ "scalar_logical"
622
+ } else if (length(.gk_obj) == 1L && is.character(.gk_obj)) {
623
+ "scalar_character"
624
+ } else {
625
+ "other"
626
+ }
627
+ .gk_len <- length(.gk_obj)
628
+ .gk_cls <- paste(class(.gk_obj), collapse=" ")
629
+ .gk_val <- ""
630
+ if (.gk_kind == "scalar_integer") {
631
+ .gk_val <- if (is.na(.gk_obj[[1]])) "__NA__" else as.character(.gk_obj[[1]])
632
+ } else if (.gk_kind == "scalar_double") {
633
+ .gk_val <- if (is.na(.gk_obj[[1]])) "__NA__" else as.character(.gk_obj[[1]])
634
+ } else if (.gk_kind == "scalar_logical") {
635
+ .gk_val <- if (is.na(.gk_obj[[1]])) "__NA__" else if (.gk_obj[[1]]) "TRUE" else "FALSE"
636
+ } else if (.gk_kind == "scalar_character") {
637
+ .gk_val <- if (is.na(.gk_obj[[1]])) "__NA__" else as.character(.gk_obj[[1]])
638
+ }
639
+ paste(.gk_kind, as.character(.gk_len), .gk_cls, .gk_val, sep=.gk_sep)
640
+ })
641
+ RCODE
642
+
643
+ raw = @client.eval_r(r_code, session_id: current_session_id, parent_id: callback_parent_id, timeout: effective_bridge_timeout(timeout: nil))
644
+ token = raw['value'].to_s
645
+ parts = token.split(sep, 4)
646
+ kind = parts[0].to_s
647
+ len = parts[1].to_i
648
+ r_class = parts[2].to_s
649
+ raw_val = parts[3].to_s
650
+ value =
651
+ case kind
652
+ when 'scalar_integer'
653
+ raw_val == '__NA__' ? nil : raw_val.to_i
654
+ when 'scalar_double'
655
+ raw_val == '__NA__' ? nil : raw_val.to_f
656
+ when 'scalar_logical'
657
+ raw_val == '__NA__' ? nil : (raw_val == 'TRUE')
658
+ when 'scalar_character'
659
+ raw_val == '__NA__' ? nil : raw_val
660
+ else
661
+ nil
662
+ end
663
+
664
+ { kind: kind, length: len, r_class: r_class, value: value }
665
+ end
666
+
667
+ def format_scalar_print(parsed)
668
+ kind = parsed['kind']
669
+ val = parsed['value']
670
+ case kind
671
+ when 'integer', 'double'
672
+ "[1] #{val.nil? ? 'NA' : val}"
673
+ when 'logical'
674
+ "[1] #{val.nil? ? 'NA' : (val ? 'TRUE' : 'FALSE')}"
675
+ when 'character'
676
+ "[1] \"#{unescape_scalar_character(val.to_s)}\""
677
+ else
678
+ parsed.to_s
679
+ end
680
+ end
681
+
682
+ def decode_batch_eval_payload(parsed)
683
+ raise "batch: expected Hash payload, got #{parsed.class}" unless parsed.is_a?(Hash)
684
+
685
+ case parsed['kind'].to_s
686
+ when 'batch_error'
687
+ idx = parsed['index']
688
+ idx = idx.to_i if idx && !idx.is_a?(Integer)
689
+ msg = parsed['message'].to_s
690
+ raise R::BatchEvaluationError.new(msg, idx)
691
+ when 'batch_eval'
692
+ Array(parsed['results']).map { |sub| to_legacy_envelope(sub) }
693
+ else
694
+ raise "batch: unknown payload kind #{parsed['kind'].inspect}"
695
+ end
696
+ end
697
+
698
+ def to_legacy_envelope(parsed)
699
+ ptype = parsed['type'].to_s
700
+ val = parsed['value']
701
+ case ptype
702
+ when 'scalar_integer'
703
+ { type: :scalar_integer, value: val }
704
+ when 'scalar_double'
705
+ { type: :scalar_double, value: val }
706
+ when 'scalar_logical'
707
+ { type: :scalar_logical, value: val }
708
+ when 'scalar_character'
709
+ { type: :scalar_character, value: val.nil? ? nil : unescape_scalar_character(val.to_s) }
710
+ when 'scalar_symbol'
711
+ { type: :scalar_symbol, value: val.to_s }
712
+ when 'handle'
713
+ wt = parsed['wrapper_tag']
714
+ wt = nil if wt.nil? || wt.to_s.strip.empty?
715
+ { type: :handle, handle: parsed['handle'].to_s, r_class: parsed['r_class'].to_s, wrapper_tag: wt&.to_s }
716
+ else
717
+ raise "Result protocol: unknown payload type #{parsed.inspect}"
718
+ end
719
+ end
720
+
721
+ def unescape_scalar_character(str)
722
+ str.to_s
723
+ .gsub(/\\n/, "\n")
724
+ .gsub(/\\"/, '"')
725
+ .gsub(/\\\\/, "\\")
726
+ end
727
+
728
+ # Gatekeeper bulk vector read: `__G_PULL_VECTOR__|handle` or slice `|start|count` (1-based start).
729
+ def eval_pull_vector_payload(var_name, slice_start_1based: nil, slice_count: nil)
730
+ cmd =
731
+ if slice_start_1based.nil?
732
+ "__G_PULL_VECTOR__|#{var_name}"
733
+ else
734
+ "__G_PULL_VECTOR__|#{var_name}|#{slice_start_1based.to_i}|#{slice_count.to_i}"
735
+ end
736
+ @client.eval_r(cmd,
737
+ session_id: current_session_id,
738
+ parent_id: callback_parent_id,
739
+ timeout: effective_bridge_timeout(timeout: nil))
740
+ end
741
+
742
+ def vector_bulk_kind?(k)
743
+ %w[integer_vector double_vector logical_vector character_vector].include?(k.to_s)
744
+ end
745
+
746
+ # Returns Ruby Array or nil if +parsed+ is not a bulk vector payload.
747
+ def decode_pull_vector_bulk_payload(parsed, want_kind: nil)
748
+ return nil unless parsed.is_a?(Hash)
749
+
750
+ k = parsed['kind'].to_s
751
+ return nil unless vector_bulk_kind?(k)
752
+ return nil if want_kind && k != want_kind.to_s
753
+
754
+ vals = Array(parsed['values'])
755
+ case k
756
+ when 'integer_vector', 'double_vector'
757
+ vals
758
+ when 'logical_vector'
759
+ vals.map { |v| v.nil? ? R::NA : v }
760
+ when 'character_vector'
761
+ vals.map { |v| v.nil? ? nil : unescape_scalar_character(v.to_s) }
762
+ else
763
+ nil
764
+ end
765
+ end
766
+
767
+ def pull_vector_elementwise(var_name)
768
+ len = @client.eval_r("length(#{var_name})",
769
+ session_id: current_session_id,
770
+ parent_id: callback_parent_id,
771
+ timeout: effective_bridge_timeout(timeout: nil))['value'].to_i
772
+ return [] if len <= 0
773
+
774
+ (1..len).map do |i|
775
+ parsed = @client.eval_r("#{var_name}[[#{i}]]",
776
+ session_id: current_session_id,
777
+ parent_id: callback_parent_id,
778
+ timeout: effective_bridge_timeout(timeout: nil))
779
+ case parsed['kind']
780
+ when 'integer', 'double', 'character'
781
+ parsed['kind'] == 'character' ? unescape_scalar_character(parsed['value'].to_s) : parsed['value']
782
+ when 'logical'
783
+ parsed['value'].nil? ? R::NA : parsed['value']
784
+ else
785
+ parsed['value']
786
+ end
787
+ end
788
+ end
789
+
790
+ def pull_integer_vector_elementwise(var_name, offset, chunk_size)
791
+ start_i = offset + 1
792
+ end_i = offset + chunk_size
793
+ (start_i..end_i).map do |i|
794
+ parsed = @client.eval_r("#{var_name}[[#{i}]]",
795
+ session_id: current_session_id,
796
+ parent_id: callback_parent_id,
797
+ timeout: effective_bridge_timeout(timeout: nil))
798
+ parsed['value']
799
+ end
800
+ end
801
+
802
+ def pull_double_vector_elementwise(var_name, offset, chunk_size)
803
+ start_i = offset + 1
804
+ end_i = offset + chunk_size
805
+ (start_i..end_i).map do |i|
806
+ parsed = @client.eval_r("#{var_name}[[#{i}]]",
807
+ session_id: current_session_id,
808
+ parent_id: callback_parent_id,
809
+ timeout: effective_bridge_timeout(timeout: nil))
810
+ parsed['value']
811
+ end
812
+ end
813
+
814
+ def phase1_scalar_fallback_error?(error)
815
+ msg = error.message.to_s.strip
816
+ return true if msg =~ /\Aphase1 requires length-1 scalar\z/
817
+ return true if msg =~ /\Aunsupported type\z/
818
+ return true if msg =~ /payload_error\("phase1 requires length-1 scalar"\)/
819
+ return true if msg =~ /payload_error\("unsupported type"\)/
820
+ false
821
+ end
822
+ end
823
+ end
824
+