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
@@ -1,396 +1,677 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- ##########################################################################################
4
- # @author Rodrigo Botafogo
5
- #
6
- # Copyright © 2018 Rodrigo Botafogo. All Rights Reserved. Permission to use, copy, modify,
7
- # and distribute this software and its documentation, without fee and without a signed
8
- # licensing agreement, is hereby granted, provided that the above copyright notice, this
9
- # paragraph and the following two paragraphs appear in all copies, modifications, and
10
- # distributions.
1
+ # rsupport.rb
11
2
  #
12
- # IN NO EVENT SHALL RODRIGO BOTAFOGO BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
13
- # INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF
14
- # THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF RODRIGO BOTAFOGO HAS BEEN ADVISED OF THE
15
- # POSSIBILITY OF SUCH DAMAGE.
3
+ # R::Support provides the core translation layer between Ruby and R: symbol conversion,
4
+ # argument parsing (parse_arg), evaluation (eval), and dispatching (exec_function,
5
+ # process_missing). process_missing is the entry point when an R::Object or the R
6
+ # module receives an unknown method: it handles setters (x=), eval, and generic
7
+ # calls (function, field access, or method with receiver as first arg).
16
8
  #
17
- # RODRIGO BOTAFOGO SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18
- # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
19
- # SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS".
20
- # RODRIGO BOTAFOGO HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
21
- # OR MODIFICATIONS.
22
- ##########################################################################################
23
-
9
+ require 'singleton'
24
10
 
25
11
  module R
26
-
27
- #--------------------------------------------------------------------------------------
28
- # The empty_symbol is necessary to represent indexing with a missing argument such
29
- # as [x, ]. What follows the ',' is an empty_symbol. Whenever we use in Ruby the
30
- # :all symbol, it will be converted to an empty_symbol in R.
31
- #--------------------------------------------------------------------------------------
32
- =begin
33
- @@empty_symbol = Polyglot.eval("R", <<-R)
34
- __missing_arg = quote(f(,0));
35
- __missing_arg[[2]]
36
- R
37
-
38
12
  def self.empty_symbol
39
- @@empty_symbol
13
+ ""
40
14
  end
41
- =end
42
15
 
43
- def self.empty_symbol
44
- Polyglot.eval("R", "missing_arg()")
16
+ # Singleton representing R's NA; used when R returns NA and we keep it as a sentinel in Ruby.
17
+ class NotAvailable
18
+ def to_s; "NA"; end
19
+ def r_interop; "NA"; end
45
20
  end
46
-
47
- # When evaluating to NA, Interop treats it as FALSE. This breaks all expectations
48
- # about NA. We need to protect NA from Interop unboxing. Class NotAvailable
49
- # puts a list around NA, so that no unboxing occurs.
50
- @na = Polyglot.eval("R", <<-R)
51
- list(NA)
52
- R
53
-
54
- class NotAvailable < R::Object
55
- end
56
-
57
- NA = NotAvailable.new(@na)
21
+ NA = NotAvailable.new
58
22
 
59
- #--------------------------------------------------------------------------------------
60
- # This is a support module for evaluating R functions
61
- #--------------------------------------------------------------------------------------
23
+ # Raised when unboxing recurses beyond MAX_UNBOX_DEPTH (prevents stack/memory exhaustion).
24
+ class UnboxDepthError < RuntimeError; end
62
25
 
63
26
  module Support
27
+ @@var_id = 0
28
+ @@var_id_mutex = Mutex.new
29
+ @dispatch_probe_cache = { func: {} }
30
+ # Phase 3: cache bridge dispatch_probe(handle, name) → { is_field, is_func } (FIFO eviction).
31
+ DISPATCH_PROBE_CACHE_MAX = 4096
32
+ @dispatch_probe_handle_cache = {}
33
+ @dispatch_probe_handle_fifo = []
34
+ @dispatch_probe_cache_mx = Mutex.new
35
+ @dispatch_probe_cache_hits = 0
36
+ @dispatch_probe_cache_misses = 0
37
+ TRANSPORT_NL = "\uE000".freeze
38
+
39
+ # Maximum recursion depth when unboxing lists. Beyond this we raise UnboxDepthError.
40
+ MAX_UNBOX_DEPTH = 100
41
+
42
+ class << self
43
+ attr_reader :dispatch_probe_cache_hits, :dispatch_probe_cache_misses
44
+ end
64
45
 
65
- @@exec_counter = 0
66
-
67
- # Using this method gives us more control over what happens when calling do.call
68
- # and allows for debugging. Use it in exec_function when debugging is needed.
69
- @@exec_from_ruby = Polyglot.eval("R", <<-R)
70
- function(build_method, ...) {
71
- # function 'invoke' from package rlang should do a cleaner than
72
- # do.call (this is what the documentation says).
73
- # res = do.call(...);
74
- res = invoke(...);
75
- res2 = build_method(res);
76
- res2
77
- }
78
- R
79
-
80
- #----------------------------------------------------------------------------------------
81
- # @TODO: Fix the 'rescue' clause and open issue with fastR
82
- # Evaluates an R code
83
- # @param string [String] A string of R code that can be correctly parsed by R
84
- #----------------------------------------------------------------------------------------
85
-
46
+ def self.reset_dispatch_probe_cache_stats!
47
+ @dispatch_probe_cache_mx&.synchronize do
48
+ @dispatch_probe_cache_hits = 0
49
+ @dispatch_probe_cache_misses = 0
50
+ end
51
+ end
52
+
53
+ def self.dispatch_probe_handle_cache_size
54
+ @dispatch_probe_cache_mx.synchronize { @dispatch_probe_handle_cache.size }
55
+ end
56
+
57
+ def self.clear_dispatch_probe_handle_cache!
58
+ @dispatch_probe_cache_mx.synchronize do
59
+ @dispatch_probe_handle_cache.clear
60
+ @dispatch_probe_handle_fifo.clear
61
+ end
62
+ end
63
+
64
+ def self.dispatch_probe_cache_key(handle, name)
65
+ "#{handle}\u0000#{name}"
66
+ end
67
+
68
+ def self.store_dispatch_probe_handle_cache(key, is_field, is_func)
69
+ @dispatch_probe_cache_mx.synchronize do
70
+ if @dispatch_probe_handle_cache.key?(key)
71
+ @dispatch_probe_handle_cache[key] = { is_field: is_field, is_func: is_func }
72
+ return
73
+ end
74
+ while @dispatch_probe_handle_cache.size >= DISPATCH_PROBE_CACHE_MAX && !@dispatch_probe_handle_fifo.empty?
75
+ oldest = @dispatch_probe_handle_fifo.shift
76
+ @dispatch_probe_handle_cache.delete(oldest) if oldest
77
+ end
78
+ @dispatch_probe_handle_cache[key] = { is_field: is_field, is_func: is_func }
79
+ @dispatch_probe_handle_fifo << key
80
+ end
81
+ end
82
+
83
+ # Generate a unique R-side variable name (e.g. g2_v1, g2_v2) for assignment results.
84
+ def self.generate_var_name
85
+ @@var_id_mutex.synchronize do
86
+ @@var_id += 1
87
+ "g2_v#{@@var_id}"
88
+ end
89
+ end
90
+
91
+ # Convert a Ruby method name to the R name: __ => ., ___ => ::, rclass => class, eql => ==.
92
+ def self.convert_symbol2r(symbol)
93
+ name = symbol.to_s
94
+ name.gsub!(/___/, "::")
95
+ name.gsub!(/__/, ".")
96
+ case name
97
+ when "rclass" then "class"
98
+ when "eql" then "=="
99
+ else name
100
+ end
101
+ end
102
+
103
+ # Evaluate an R expression (string, Language, or handle). Returns a scalar value or R::Object.build(...).
104
+ # Uses .expression or .r_interop when present so we never treat R's printed output as code.
86
105
  def self.eval(string)
87
- begin
88
- Polyglot.eval("R", string)
89
- rescue RuntimeError
90
- raise NoMethodError.new("Method #{string} not found in R environment")
106
+ puts "DEBUG: Support.eval(#{string.inspect})" if ENV['GALAAZ_DEBUG']
107
+
108
+ var_name = self.generate_var_name
109
+ r_code = if string.respond_to?(:expression) && string.expression
110
+ string.expression.to_s
111
+ elsif string.respond_to?(:r_interop) && string.r_interop.is_a?(::String) && string.r_interop.start_with?("g2_v")
112
+ string.r_interop
113
+ else
114
+ string.to_s
115
+ end
116
+
117
+ # Determine if we need to wrap in braces or use eval()
118
+ # If it's a handle, we MUST use eval() in R to get its value if it's a symbol
119
+ final_r_code = if r_code.start_with?("g2_v")
120
+ "eval(#{r_code})"
121
+ elsif r_code.include?("\n") && !r_code.strip.start_with?("{") && !r_code.include?("<-") && !r_code.include?("=")
122
+ "{#{r_code}\n}"
123
+ else
124
+ r_code
125
+ end
126
+
127
+ assignment = "#{var_name} <- { #{final_r_code}\n }"
128
+ envelope = R.bridge.eval_r_with_result(assignment)
129
+ if ENV['GALAAZ_DEBUG']
130
+ puts "DEBUG: eval envelope=#{envelope.inspect}"
131
+ end
132
+ raise "Result protocol: no envelope (buffer missing or invalid)" unless envelope
133
+
134
+ case envelope[:type]
135
+ when :scalar_double, :scalar_integer, :scalar_logical
136
+ return envelope[:value]
137
+ when :scalar_character
138
+ return R::Object.build(var_name, nil, r_class: "character")
139
+ when :scalar_symbol
140
+ sym_name = envelope[:value].to_s
141
+ return sym_name.gsub("::", "___").gsub(".", "__").to_sym
142
+ when :handle
143
+ # Protocol spec: eval returns scalar symbol as Ruby Symbol. R sends symbol/name as handle (type 4); unbox here only.
144
+ r_class = envelope[:r_class].to_s.strip
145
+ if r_class == "name" || r_class == "symbol"
146
+ raw = R.bridge.eval_r("as.character(#{envelope[:handle]})").to_s
147
+ m = raw.match(/\[1\]\s*"([^"]*)"/)
148
+ name = m ? m[1] : raw.strip
149
+ return name.gsub("::", "___").gsub(".", "__").to_sym
150
+ end
151
+ return R::Object.build(envelope[:handle], nil, r_class: envelope[:r_class], wrapper_tag: envelope[:wrapper_tag])
152
+ else
153
+ raise "Result protocol: unknown envelope type #{envelope[:type].inspect}"
91
154
  end
92
155
  end
93
156
 
94
- #----------------------------------------------------------------------------------------
95
- # @param object [Object] Any Ruby object
96
- # @return boolean if the object is an interop or not
97
- #----------------------------------------------------------------------------------------
98
-
99
- def self.interop(object)
100
- Truffle::Interop.foreign?(object)
157
+ # String suitable for expression display (to_s). For R::Language use stored expression; for other R::Object
158
+ # use R's deparse() so display reflects the current value (not the creating R code stored in .expression).
159
+ def self.expression_display_arg(arg)
160
+ puts "DEBUG expression_display_arg: arg=#{arg.inspect} class=#{arg.class}" if ENV["GALAAZ_DEBUG"]
161
+ return arg.expression if arg.is_a?(R::Language) && arg.respond_to?(:expression) && arg.expression
162
+ return self.get_deparse_string(arg) if arg.is_a?(R::Object)
163
+ self.parse_arg(arg).to_s
101
164
  end
102
-
103
- #----------------------------------------------------------------------------------------
104
- # @param arg [Object] A Ruby object to be converted to R to be used by an R function, or
105
- # whatever needs it
106
- # @return Object that can be used in R
107
- #----------------------------------------------------------------------------------------
108
165
 
109
- def self.parse_arg(arg)
166
+ # R-side deparse of an R::Object to a single string (e.g. "c(1L, 2L, 3L, 4L)").
167
+ # Relies on R evaluating deparse() correctly on the object.
168
+ def self.get_deparse_string(arg)
169
+ puts "DEBUG get_deparse_string: called for #{arg.r_interop.inspect}" if ENV["GALAAZ_DEBUG"]
170
+ dep = self.exec_function("deparse", arg)
171
+ puts "DEBUG get_deparse_string: deparse result class=#{dep.class} value=#{dep.inspect}" if ENV["GALAAZ_DEBUG"]
172
+ str = self.exec_function("paste0", dep, { collapse: "" })
173
+ return str.to_s if str.is_a?(::String)
174
+ raw = R.bridge.eval_r("paste0(#{str.r_interop}, collapse='')").to_s
175
+ puts "DEBUG get_deparse_string: eval_r paste0 raw=#{raw.inspect}" if ENV["GALAAZ_DEBUG"]
176
+ m = raw.match(/\[1\]\s*"([^"]*)"/)
177
+ result = m ? m[1] : raw.strip
178
+ puts "DEBUG get_deparse_string: returning #{result.inspect}" if ENV["GALAAZ_DEBUG"]
179
+ result
180
+ end
110
181
 
111
- case(arg)
112
- when -> (arg) { interop(arg) }
113
- arg
114
- when R::Object
115
- arg.r_interop
182
+ # Turn a Ruby value into an R code fragment (string): handles, scalars, hashes -> list(), arrays -> c(), Procs -> R callback stub, etc.
183
+ def self.parse_arg(arg)
184
+ return arg.r_interop if arg.respond_to?(:r_interop) && arg.r_interop
185
+ return arg.expression if arg.respond_to?(:expression) && arg.expression
186
+
187
+ case arg
188
+ when Hash
189
+ arg.map do |k, v|
190
+ key = k.to_s.gsub(/__/, ".")
191
+ # R list names with spaces or special chars must be backtick-quoted
192
+ key_r = (key =~ /\A[a-zA-Z._][a-zA-Z0-9._]*\z/) ? key : "`#{key.gsub('`', '\\`')}`"
193
+ "#{key_r} = #{self.parse_arg(v)}"
194
+ end.join(", ")
195
+ when Array
196
+ "c(#{arg.map { |v| self.parse_arg(v) }.join(", ")})"
197
+ when Symbol
198
+ # :all means "all" in that dimension; R uses missing argument. Bridge defines missing_arg().
199
+ return "missing_arg()" if arg == :all
200
+ arg.to_s.gsub(/__/,".")
201
+ when R::SymbolRef
202
+ arg.to_r_symbol
203
+ when String
204
+ # If it's already a handle, don't quote it
205
+ if arg.start_with?("g2_v")
206
+ arg
207
+ else
208
+ "'#{arg.gsub("'", "\\\\'")}'"
209
+ end
116
210
  when Numeric
117
- R::Support.eval('c').call(arg)
118
- when NegRange
119
- final_value = (arg.exclude_end?)? (arg.last - 1) : arg.last
120
- R::Support.eval('seq').call(arg.first, final_value)
211
+ arg.to_s + (arg.is_a?(Integer) ? "L" : "")
212
+ when TrueClass
213
+ "TRUE"
214
+ when FalseClass
215
+ "FALSE"
121
216
  when Range
122
- final_value = (arg.exclude_end?)? (arg.last - 1) : arg.last
123
- R::Support.eval('seq').call(arg.first, final_value)
124
- when :all
125
- R.empty_symbol
126
- when Symbol
127
- arg = R::Support.eval('as.name').call(arg.to_s.gsub(/__/,"."))
217
+ final_value = (arg.exclude_end?) ? (arg.last - 1) : arg.last
218
+ "seq(#{arg.first}, #{final_value})"
128
219
  when Proc, Method
129
- R::RubyCallback.build(arg)
130
- # This is a Ruby argument that will be automatically converted
131
- # by the low level Polyglot interface to R
132
- else
133
- arg
220
+ if R.bridge.respond_to?(:register_callback_proc_stub)
221
+ return R.bridge.register_callback_proc_stub(arg)
222
+ end
223
+
224
+ id = R::Support.register_callback(arg)
225
+ "function(...) {
226
+ args <- list(...)
227
+ handles <- character(0)
228
+ if (length(args) > 0) {
229
+ for (i in 1:length(args)) {
230
+ h <- paste0('g2_v_cb_', i, '_', as.integer(runif(1, 1e8, 9e8)))
231
+ assign(h, args[[i]], envir = .GlobalEnv)
232
+ cls <- paste(class(args[[i]]), collapse=' ')
233
+ handles <- c(handles, paste0(h, ':', cls))
234
+ }
235
+ }
236
+ cat('--G_CALLBACK--#{id}--', paste(handles, collapse='|'), '--\\n', sep='')
237
+ flush.console()
238
+ while(TRUE) {
239
+ res_str <- readLines('/dev/shm/galaaz_callback_fifo', n=1)
240
+ if (length(res_str) < 1L) next
241
+ line <- res_str[1]
242
+ if (startsWith(line, '--G_CMD--')) {
243
+ # Extract sequence number if present (regmatches returns list; use [[1]] for sub)
244
+ seq_match <- regmatches(line, regexpr('--G_CMD--seq=([0-9]+)--', line))
245
+ seq_num <- if (length(seq_match) > 0L && length(seq_match[[1]]) > 0L) sub('--G_CMD--seq=([0-9]+)--', '\\\\1', seq_match[[1]]) else '0'
246
+ # Extract actual command
247
+ cmd_part <- sub('--G_CMD--(seq=[0-9]+--)?', '', line)
248
+ cmd <- trimws(cmd_part)
249
+ # Restore newlines (Ruby sends U+E000 as placeholder so FIFO is one line)
250
+ cmd <- gsub(\"#{TRANSPORT_NL}\", \"\\n\", cmd, fixed=TRUE)
251
+ recv_log <- Sys.getenv('GALAAZ_R_RECEIVED_LOG', '')
252
+ if (nchar(recv_log) > 0L) tryCatch({
253
+ write('---CMD---\\n', file=recv_log, append=TRUE)
254
+ write(cmd, file=recv_log, append=TRUE)
255
+ write('\\n', file=recv_log, append=TRUE)
256
+ }, error=function(e) NULL)
257
+ # Log that we're about to execute a command
258
+ cat('[R_CALLBACK]', 'seq=', seq_num, 'Executing:', substr(cmd, 1, 50), '...\\n', sep='')
259
+ # Wrap entire command handling in tryCatch to ensure --G_CMD_END-- is always sent
260
+ # Use capture.output with explicit print() to ensure output is captured
261
+ tryCatch({
262
+ if (nchar(cmd) > 0L) {
263
+ # Evaluate in .GlobalEnv so g2_v* handles created by Ruby are visible.
264
+ # Without this, eval() uses parent.frame() (chunk/engine env) and
265
+ # object 'g2_vNNN' not found occurs when indexing vectors/matrices.
266
+ result <- capture.output(print(eval(parse(text=cmd), envir = .GlobalEnv)))
267
+ cat(result, sep='\\n')
268
+ }
269
+ }, error=function(e) {
270
+ cat('--G_ERR--', conditionMessage(e), '\\n', sep='')
271
+ tb <- paste(capture.output(traceback()), collapse = '\\\\\\\\n')
272
+ if (nchar(tb) > 0L) cat('--G_TRACE--', tb, '\\n', sep='')
273
+ }, finally={
274
+ # Include sequence number in response for synchronization
275
+ cat('--G_CMD_END--seq=', seq_num, '--\\n', sep='')
276
+ flush.console()
277
+ })
278
+ } else if (startsWith(line, '--G_RET--')) {
279
+ res_handle <- trimws(sub('--G_RET--', '', line))
280
+ res_handle <- gsub(\"#{TRANSPORT_NL}\", \"\\n\", res_handle, fixed=TRUE)
281
+ if (nchar(res_handle) > 0L) return(eval(parse(text=res_handle), envir = .GlobalEnv))
282
+ return(invisible(NULL))
283
+ }
284
+ }
285
+ }"
286
+ when nil
287
+ "NULL"
288
+ else
289
+ handle = self.register_ruby_object(arg)
290
+ "'#{handle}'"
134
291
  end
292
+ end
135
293
 
294
+ # R `[` with a true missing dimension (Ruby :all). tidyselect on tbl_df rejects
295
+ # `missing_arg()` results as subscripts ("empty string"); alist(, ) is real missing.
296
+ def self.build_subscript_do_call_alist(all_args)
297
+ inner = all_args.map do |arg|
298
+ (arg.is_a?(::Symbol) && arg == :all) ? nil : parse_arg(arg)
299
+ end
300
+ inner = inner.map { |frag| frag.nil? ? '' : frag }.join(', ')
301
+ "do.call(`[`, alist(#{inner}))"
136
302
  end
137
303
 
138
- #----------------------------------------------------------------------------------------
139
- # Given a hash, convert it to an R argument list
140
- #----------------------------------------------------------------------------------------
141
-
142
- def self.parse_hash(hsh, lst)
143
-
144
- hsh.each_pair do |key, value|
145
- k = key.to_s.gsub(/__/,".")
146
-
147
- case
148
- when (value.is_a? NotAvailable)
149
- na_named = R::Support.eval("`names<-`").call(value.r_interop, k)
150
- lst = R::Support.eval("c").call(lst, na_named)
151
- when (k == 'all')
152
- lst = R::Support.eval("`[[<-`").
153
- call(lst, R::Support.eval('`+`').
154
- call(R::Support.eval('length').call(lst), 1),
155
- R::Support.parse_arg(value))
304
+ # `[<-` with i/j = :all (R::DataFrame#[]=). Same tidyselect / anyNA issues with missing_arg().
305
+ def self.build_subscript_assign_do_call_alist(receiver, kw_hash)
306
+ pairs = kw_hash.map do |k, v|
307
+ key = k.to_s.gsub(/__/, ".")
308
+ key_r = (key =~ /\A[a-zA-Z._][a-zA-Z0-9._]*\z/) ? key : "`#{key.gsub('`', '\\`')}`"
309
+ if v.is_a?(::Symbol) && v == :all
310
+ "#{key_r} = "
156
311
  else
157
- lst = R::Support.eval("`[[<-`").
158
- call(lst, k, R::Support.parse_arg(value))
312
+ "#{key_r} = #{parse_arg(v)}"
159
313
  end
160
314
  end
161
-
162
- lst
163
-
315
+ inner = [parse_arg(receiver), *pairs].join(", ")
316
+ "do.call(`[<-`, alist(#{inner}))"
164
317
  end
165
-
166
- #----------------------------------------------------------------------------------------
167
- # Parses the Ruby arguments into a R list of R objects
168
- # @param args [Ruby Parameter list]
169
- #----------------------------------------------------------------------------------------
170
-
171
- def self.parse2list(*args)
172
-
173
- params = Polyglot.eval("R", "list()")
174
-
175
- args.each_with_index do |arg, i|
176
- if (arg.is_a? Hash)
177
- params = parse_hash(arg, params)
178
- elsif (arg.is_a? NotAvailable)
179
- params = R::Support.eval("c").call(params, arg.r_interop)
180
- elsif (arg.is_a? NilClass)
181
- params = R::Support.eval("`length<-`").call(params, i+1)
182
- else
183
- params = R::Support.eval("`[[<-`").
184
- call(params, i+1, R::Support.parse_arg(arg))
318
+
319
+ MD_INDEX_BACKTICK = '`[`'.freeze
320
+ MD_ASSIGN_BACKTICK = '`[<-`'.freeze
321
+
322
+ # Run an R call: f_name(args...). Builds assignment, gets envelope from bridge, returns R::Object (boxed).
323
+ # Unbox with .to_ruby, .unboxed_get(0), or >> 0. kwargs (e.g. i:, value:) are merged into args for R named-argument calls like `[[<-`(df, i=..., value=...).
324
+ def self.exec_function(function, *args, unbox: false, **kwargs)
325
+ f_name = function.respond_to?(:r_interop) ? function.r_interop : function
326
+
327
+ if args.empty? && kwargs.empty? && (f_name.include?("::") || f_name.start_with?("g2_v"))
328
+ return self.eval(f_name)
329
+ end
330
+
331
+ var_name = self.generate_var_name
332
+ all_args = kwargs.empty? ? args : args + [kwargs]
333
+
334
+ use_subscript_alist =
335
+ kwargs.empty? &&
336
+ f_name == MD_INDEX_BACKTICK &&
337
+ all_args.any? { |a| a.is_a?(::Symbol) && a == :all } &&
338
+ !all_args.any? { |a| a.is_a?(Hash) }
339
+
340
+ use_assign_alist =
341
+ !kwargs.empty? &&
342
+ f_name == MD_ASSIGN_BACKTICK &&
343
+ all_args.size == 2 &&
344
+ all_args[1].is_a?(Hash) &&
345
+ all_args[1].values.any? { |v| v.is_a?(::Symbol) && v == :all }
346
+
347
+ if use_subscript_alist
348
+ r_expr = build_subscript_do_call_alist(all_args)
349
+ elsif use_assign_alist
350
+ r_expr = build_subscript_assign_do_call_alist(all_args[0], all_args[1])
351
+ else
352
+ r_args = all_args.map { |arg| self.parse_arg(arg) }
353
+ # eval(expr, envir): R expects expr as expression; parse_arg on Language returns bare string -> wrap in parse(text=...) so envir is used
354
+ if f_name == "eval" && args.size == 2 && !r_args[0].to_s.start_with?("g2_v", "quote(")
355
+ r_args[0] = "parse(text=#{r_args[0].to_s.inspect})"
185
356
  end
357
+
358
+ r_expr = "#{f_name}(#{r_args.join(", ")})"
186
359
  end
187
-
188
- # if the parameter is an empty list, then add the element NULL to the list
189
- (Polyglot.eval("R", "length").call(params) == 0) ?
190
- Polyglot.eval("R", "list").call(nil) : params
191
-
192
- end
193
-
194
- #----------------------------------------------------------------------------------------
195
- # Converts a Ruby symbol onto an R symbol. Converts '__' to '.' and 'rclass' to
196
- # class
197
- # @param symbol [Symbol] A Ruby symbol to convert to R
198
- #----------------------------------------------------------------------------------------
199
-
200
- def self.convert_symbol2r(symbol)
201
- name = symbol.to_s
202
- # convert '__' to '.'
203
- name.gsub!(/__/,".")
204
- # Method 'rclass' is a substitute for R method 'class'. Needed, as 'class' is also
205
- # a Ruby method on an object
206
- name.gsub!("rclass", "class")
207
- name
360
+ assignment = "#{var_name} <- #{r_expr}"
361
+ envelope = R.bridge.eval_r_with_result(assignment)
362
+ unless envelope
363
+ reason = R.bridge.respond_to?(:last_envelope_nil_reason) && R.bridge.last_envelope_nil_reason
364
+ raise "Result protocol: no envelope (buffer missing or invalid)#{reason ? " [#{reason}]" : ''}"
365
+ end
366
+
367
+ ruby_result_from_envelope(envelope, var_name, r_expr)
208
368
  end
209
-
210
- #----------------------------------------------------------------------------------------
211
- # @param function [R function (Interop)] R function to execute
212
- # @args [Array] Array of arguments for the function
213
- #----------------------------------------------------------------------------------------
214
-
215
- def self.exec_function(function, *args)
216
369
 
217
- begin
218
-
219
- # If the execution counter is 0, function was not recursively called
220
- # Starts capturing output
221
- if (@@exec_counter == 0)
222
- R::Support.eval("unlockBinding('r_capture', globalenv())")
223
- @@con = R::Support.start_capture.call("r_capture")
370
+ # Build the same Ruby value as +exec_function+ from a legacy envelope (+:handle+, scalars, +rb_obj_*+).
371
+ def self.ruby_result_from_envelope(envelope, var_name, r_expr)
372
+ case envelope[:type]
373
+ when :scalar_double, :scalar_integer, :scalar_logical, :scalar_character
374
+ val = envelope[:value]
375
+ if envelope[:type] == :scalar_character && val.is_a?(String) && val =~ /^rb_obj_\d+$/
376
+ return get_ruby_object(val)
224
377
  end
225
-
226
- @@exec_counter = @@exec_counter + 1
227
-
228
- # function has no arguments, call it directly
229
- if (args.length == 0)
230
- res = R::Object.build(function.call)
231
- else
232
- pl = R::Support.parse2list(*args)
233
- res = @@exec_from_ruby.call(R::Object.method(:build), function, pl)
234
- # R::Object.build(R::Support.eval("do.call").call(function, pl))
378
+ r_class = { scalar_double: "numeric", scalar_integer: "integer", scalar_logical: "logical", scalar_character: "character" }[envelope[:type]]
379
+ return R::Object.build(var_name, r_expr, r_class: r_class)
380
+ when :scalar_symbol
381
+ sym_name = envelope[:value].to_s
382
+ return sym_name.gsub("::", "___").gsub(".", "__").to_sym
383
+ when :handle
384
+ return R::Object.build(envelope[:handle], r_expr, r_class: envelope[:r_class], wrapper_tag: envelope[:wrapper_tag])
385
+ else
386
+ raise "Result protocol: unknown envelope type #{envelope[:type].inspect}"
387
+ end
388
+ end
389
+
390
+ # Async variant of +exec_function+; +block+ receives +NewBridge::EvalResult+ (+#value+ is like +exec_function+ return).
391
+ def self.exec_function_async(function, *args, unbox: false, timeout: nil, **kwargs, &block)
392
+ raise ArgumentError, 'exec_function_async requires a block' unless block
393
+
394
+ f_name = function.respond_to?(:r_interop) ? function.r_interop : function
395
+
396
+ if args.empty? && kwargs.empty? && (f_name.include?("::") || f_name.start_with?("g2_v"))
397
+ raise ArgumentError, 'exec_function_async does not support bare handle/namespace reference; use R.eval_r_async'
398
+ end
399
+
400
+ var_name = self.generate_var_name
401
+ all_args = kwargs.empty? ? args : args + [kwargs]
402
+
403
+ use_subscript_alist =
404
+ kwargs.empty? &&
405
+ f_name == MD_INDEX_BACKTICK &&
406
+ all_args.any? { |a| a.is_a?(::Symbol) && a == :all } &&
407
+ !all_args.any? { |a| a.is_a?(Hash) }
408
+
409
+ use_assign_alist =
410
+ !kwargs.empty? &&
411
+ f_name == MD_ASSIGN_BACKTICK &&
412
+ all_args.size == 2 &&
413
+ all_args[1].is_a?(Hash) &&
414
+ all_args[1].values.any? { |v| v.is_a?(::Symbol) && v == :all }
415
+
416
+ if use_subscript_alist
417
+ r_expr = build_subscript_do_call_alist(all_args)
418
+ elsif use_assign_alist
419
+ r_expr = build_subscript_assign_do_call_alist(all_args[0], all_args[1])
420
+ else
421
+ r_args = all_args.map { |arg| self.parse_arg(arg) }
422
+ if f_name == "eval" && args.size == 2 && !r_args[0].to_s.start_with?("g2_v", "quote(")
423
+ r_args[0] = "parse(text=#{r_args[0].to_s.inspect})"
235
424
  end
236
425
 
237
- @@exec_counter = @@exec_counter - 1
238
-
239
- # When execution counter back to 0, print the captured output if the length
240
- # of the output is greater than 0
241
- if (@@exec_counter == 0)
242
- R::Support.stop_capture.call(@@con)
243
- if (R::Support.eval("length(r_capture) > 0")[0])
244
- cap = R::Object.build(R::Support.eval("r_capture"))
245
- (0...cap.size).each do |i|
246
- puts cap >> i
247
- end
426
+ r_expr = "#{f_name}(#{r_args.join(", ")})"
427
+ end
428
+ assignment = "#{var_name} <- #{r_expr}"
429
+
430
+ R.bridge.eval_r_with_result_async(assignment, timeout: timeout) do |result|
431
+ if result.ok?
432
+ begin
433
+ env = result.value[:envelope]
434
+ vn = result.value[:var_name]
435
+ rx = result.value[:r_expr]
436
+ obj = ruby_result_from_envelope(env, vn, rx)
437
+ block.call(NewBridge::EvalResult.success(obj))
438
+ rescue StandardError => e
439
+ block.call(NewBridge::EvalResult.failure(e))
248
440
  end
441
+ else
442
+ block.call(result)
249
443
  end
250
-
251
- rescue StandardError => e
252
- R::Support.stop_capture.call(@@con) if (@@exec_counter == 0)
253
- raise e
254
444
  end
255
-
256
- res
257
-
258
- end
259
-
260
- #----------------------------------------------------------------------------------------
261
- # @param function_name [String] Name of the R function to execute
262
- # @args [Array] Array of arguments for the function
263
- #----------------------------------------------------------------------------------------
264
-
265
- def self.exec_function_name(function_name, *args)
266
- # @TODO: should check all that can go wrong when calling eval(function_name) to
267
- # raise the proper exception
268
- f = R::Support.eval(function_name)
269
- R::Support.exec_function(f, *args)
270
445
  end
271
446
 
272
- #----------------------------------------------------------------------------------------
273
- # Executes the given R function with the given arguments.
274
- #----------------------------------------------------------------------------------------
275
-
276
- def self.exec_function_i(function, *args)
277
- pl = R::Support.parse2list(*args)
278
- R::Support.eval("invoke").call(function, pl)
447
+ class << self
448
+ alias_method :exec_function_name, :exec_function
279
449
  end
280
450
 
281
- #----------------------------------------------------------------------------------------
282
- # sets the R symbol <name> to the given value
283
- # @param name [String] String representation of the R symbol that needs to be assigned
284
- # @param args [Array] Array with one object to be set to the symbol
285
- #----------------------------------------------------------------------------------------
451
+ # Entry point for method_missing: handle setters (x=), eval, or dispatch to R (function / field / method with receiver).
452
+ def self.process_missing(symbol, internal, *args)
453
+ name = self.convert_symbol2r(symbol)
454
+ if ENV['GALAAZ_DEBUG_EVAL'] && name == "expr" && args.size >= 1
455
+ puts "[GALAAZ_DEBUG_EVAL] R.expr(...) called:"
456
+ puts " args[0].class = #{args[0].class}, args[0].inspect = #{args[0].inspect}"
457
+ end
458
+ return process_missing_setter(name, internal, args) if name =~ /(.*)=$/
459
+ return process_missing_eval(name, internal, args) if name == "eval"
460
+ # R.expr(expression_text): build R expression from string/SymbolExprString via parse(text=...), return R::Object.
461
+ if name == "expr" && args.size == 1
462
+ arg = args[0]
463
+ if arg.is_a?(SymbolExprString) || (arg.is_a?(String) && !arg.start_with?("g2_v"))
464
+ str = arg.to_s
465
+ var_name = self.generate_var_name
466
+ R.bridge.eval_r("#{var_name} <- parse(text=#{str.inspect})[[1]]")
467
+ return R::Object.build(var_name)
468
+ end
469
+ end
470
+ process_missing_dispatch(name, internal, args)
471
+ end
286
472
 
287
- def self.set_symbol(name, *args)
288
- args << R::Support.eval("globalenv").call()
289
- R::Support.exec_function_name("assign", name, *args)
473
+ # Handle obj.var = rhs or R.var = rhs: use `var<-` or `$<-` on R::Object, else .GlobalEnv assignment.
474
+ def self.process_missing_setter(name, internal, args)
475
+ var = name[/^(.*)=$/, 1]
476
+ rhs = self.parse_arg(args[0])
477
+ if internal.is_a?(R::Object)
478
+ handle = internal.r_interop
479
+ r_var = (var == "rclass") ? "class" : var
480
+ R.bridge.eval_r(<<~RCODE)
481
+ #{handle} <- tryCatch(
482
+ `#{r_var}<-`(#{handle}, #{rhs}),
483
+ error = function(e) `$<-`(#{handle}, '#{var}', #{rhs})
484
+ )
485
+ RCODE
486
+ else
487
+ R.bridge.eval_r(".GlobalEnv$#{var} <- #{rhs}")
488
+ end
290
489
  end
291
-
292
- #----------------------------------------------------------------------------------------
293
- # Calls the R 'eval' function. This requires some special semantics
294
- # R function 'eval' needs to be called in a special way, since it expects
295
- # the second argument to be an environment. If the arguments are packed
296
- # into a list, then there is no second argument and the function fails to
297
- # use the second argument as environment
298
- #----------------------------------------------------------------------------------------
299
-
300
- def self.r_evaluate(*args)
301
- r_args = args.map { |arg| R::Support.parse_arg(arg) }
302
- R::Object.build(R::Support.eval("eval").call(*r_args))
490
+
491
+ # Handle obj.eval(env) or R.eval(code): expression in context, or single-arg R.eval.
492
+ def self.process_missing_eval(name, internal, args)
493
+ if internal.is_a?(R::Object) && name == "eval" && args.empty?
494
+ return self.exec_function("eval", internal)
495
+ end
496
+ if internal.is_a?(R::Object) && args.size >= 1
497
+ expr = self.parse_arg(internal)
498
+ env = self.parse_arg(args[0])
499
+ unless expr.start_with?("g2_v") || expr.start_with?("quote(")
500
+ puts "DEBUG: Quoting expression: #{expr}" if ENV['GALAAZ_DEBUG']
501
+ expr = "quote(#{expr})"
502
+ end
503
+ var_name = self.generate_var_name
504
+ r_code = "#{var_name} <- eval(#{expr}, #{env})"
505
+ if ENV['GALAAZ_DEBUG_EVAL']
506
+ puts "[GALAAZ_DEBUG_EVAL] expr.eval(env) path:"
507
+ puts " internal.class = #{internal.class}"
508
+ puts " internal.r_interop = #{internal.r_interop.inspect}" if internal.respond_to?(:r_interop)
509
+ puts " internal.expression = #{internal.expression.inspect}" if internal.respond_to?(:expression)
510
+ puts " parse_arg(internal) => expr = #{expr.inspect}"
511
+ puts " parse_arg(args[0]) => env = #{env.inspect}"
512
+ puts " R code sent: #{r_code}"
513
+ end
514
+ R.bridge.eval_r(r_code)
515
+ return R::Object.build(var_name)
516
+ end
517
+ if args.size == 1
518
+ expr_arg = args[0].is_a?(SymbolExprString) ? args[0].to_s : args[0]
519
+ return self.eval(expr_arg)
520
+ end
521
+ self.exec_function(name, *args)
303
522
  end
304
523
 
305
- #----------------------------------------------------------------------------------------
306
- # Process the missing method
307
- # @param symbol [Symbol]
308
- # @param internal [Boolean] true if the method will return to an internal method, i.e.,
309
- # it should not wrap the return value inside an R::Object
310
- # @param object [Ruby Object] the ruby object to which the method is applied, false if
311
- # it is not applied to an object
312
- #----------------------------------------------------------------------------------------
524
+ # Dispatch: R module (eval handle/namespace or exec_function) or R::Object (length / function / field / fallback).
525
+ def self.process_missing_dispatch(name, internal, args)
526
+ if internal == R && args.empty? && (name.include?("::") || name.start_with?("g2_v"))
527
+ return R::Object.build(name)
528
+ end
313
529
 
314
- def self.process_missing(symbol, internal, *args)
530
+ # Phase 3: R.foo(...) — +internal+ is false/true from R.method_missing, not an R::Object. No per-handle
531
+ # field vs function ambiguity; go straight to function execution.
532
+ unless internal.is_a?(R::Object)
533
+ return self.exec_function(name, *args)
534
+ end
315
535
 
316
- name = R::Support.convert_symbol2r(symbol)
317
-
318
- case name
319
- # when missing method has an '=' sign in it...
320
- when ->(x) { x =~ /(.*)=$/ }
321
- R::Support.set_symbol($1, *args)
322
- # when missing method is 'eval'... needs special treatment
323
- when "eval"
324
- R::Support.r_evaluate(*args)
536
+ handle = internal.r_interop
537
+ return self.exec_function("length", internal, *args) if name == "length"
538
+
539
+ # Prefer component/field access (obj.beta => obj[["beta"]]) over calling a global function (beta()).
540
+ # Use [[ instead of $ so we avoid "$ operator is invalid for atomic vectors" when receiver is atomic;
541
+ # [[ on list/data.frame/env returns the element; result protocol maps NA/NULL as appropriate.
542
+ begin
543
+ R.bridge.log_connections_in_r if ENV["GALAAZ_DEBUG_R"].to_s == "1" || ENV["GALAAZ_DEBUG_R"].to_s == "true"
544
+ R.bridge.log_object_in_r(handle) if ENV["GALAAZ_DEBUG_R"].to_s == "1" || ENV["GALAAZ_DEBUG_R"].to_s == "true" || ENV["GALAAZ_DEBUG_OBJECT"].to_s == "1" || ENV["GALAAZ_DEBUG_OBJECT"].to_s == "true"
545
+ rescue StandardError => e
546
+ # Debug dump must not block the is_field check (e.g. if R throws "invalid connection" when inspecting the object).
547
+ File.open(R.bridge.log_path("galaaz_obj_debug.log"), "a") { |f| f.puts "[#{Time.now.strftime('%H:%M:%S.%L')}] log_object_in_r(#{handle}) failed: #{e.message}" }
548
+ end
549
+ # In callback, eval_r can fail with "invalid connection" - wrap in begin/rescue
550
+ is_field = false
551
+ is_func = false
552
+ if R.bridge.respond_to?(:dispatch_probe)
553
+ # Environments are mutable (rm, assign); do not cache probe results — stale is_field breaks semantics.
554
+ cache_probe = !internal.is_a?(::R::Environment)
555
+ probe_key = dispatch_probe_cache_key(handle, name)
556
+ cached_probe = nil
557
+ if cache_probe
558
+ @dispatch_probe_cache_mx.synchronize do
559
+ cached_probe = @dispatch_probe_handle_cache[probe_key]
560
+ @dispatch_probe_cache_hits += 1 if cached_probe
561
+ end
562
+ end
563
+ if cached_probe
564
+ is_field = !!cached_probe[:is_field]
565
+ is_func = !!cached_probe[:is_func]
566
+ else
567
+ begin
568
+ probe = R.bridge.dispatch_probe(handle, name)
569
+ is_field = !!probe[:is_field]
570
+ is_func = !!probe[:is_func]
571
+ if cache_probe
572
+ @dispatch_probe_cache_mx.synchronize do
573
+ @dispatch_probe_cache_misses += 1
574
+ end
575
+ store_dispatch_probe_handle_cache(probe_key, is_field, is_func)
576
+ end
577
+ @dispatch_probe_cache[:func][name] = is_func
578
+ rescue StandardError => e
579
+ raise unless e.message.include?("invalid connection") || e.message.include?("invalid dispatch_probe params")
580
+ is_field = false
581
+ is_func = false
582
+ end
583
+ end
325
584
  else
326
- function = R::Support.eval(name)
327
- internal ? R::Support.exec_function_i(function, *args) :
328
- R::Support.exec_function(function, *args)
585
+ is_field = begin
586
+ R.bridge.eval_r("isTRUE('#{name}' %in% names(#{handle})) || (is.environment(#{handle}) && isTRUE(exists('#{name}', envir = #{handle}, inherits = FALSE)))") == "[1] TRUE"
587
+ rescue RuntimeError => e
588
+ e.message.include?("invalid connection") ? false : raise
589
+ end
590
+ if is_field
591
+ res = self.exec_function_name("`[[`", internal, name)
592
+ return res.call(*args) if !args.empty? && res.respond_to?(:call)
593
+ return res
594
+ end
595
+
596
+ is_func = begin
597
+ cached = @dispatch_probe_cache[:func][name]
598
+ if cached.nil?
599
+ cached = (R.bridge.eval_r("is.function(try(get('#{name}'), silent=TRUE))") == "[1] TRUE")
600
+ @dispatch_probe_cache[:func][name] = cached
601
+ end
602
+ cached
603
+ rescue RuntimeError => e
604
+ e.message.include?("invalid connection") ? false : raise
605
+ end
606
+ end
607
+ if is_field
608
+ res = self.exec_function_name("`[[`", internal, name)
609
+ return res.call(*args) if !args.empty? && res.respond_to?(:call)
610
+ return res
329
611
  end
330
612
 
331
- end
332
-
333
- #----------------------------------------------------------------------------------------
334
- #
335
- #----------------------------------------------------------------------------------------
613
+ return self.exec_function(name, internal, *args) if is_func
336
614
 
337
- def self.print_str(obj)
338
-
339
- lst = obj.as__list
340
- puts lst
341
- =begin
342
- (1..lst.length >> 0).each do |i|
343
- puts lst[[i]]
615
+ # Environment: missing name should raise NoMethodError (like Ruby), not call name(env) in R.
616
+ if internal.is_a?(::R::Environment)
617
+ ::Kernel.raise(::NoMethodError, "undefined method `#{name}' for #{internal.inspect}")
344
618
  end
345
- =end
619
+
620
+ self.exec_function(name, internal, *args)
346
621
  end
347
622
 
348
- #----------------------------------------------------------------------------------------
349
- # Prints a foreign R interop pointer. Used for debug.
350
- #----------------------------------------------------------------------------------------
351
623
 
352
- def self.print_foreign(interop)
353
- puts "===External value==="
354
- R::Support.eval("print").call(interop)
355
- puts "===end external value==="
624
+ @callbacks = {}
625
+ @ruby_objects = {}
626
+ @ruby_obj_id = 0
627
+ @ruby_object_id = 0
628
+
629
+ # Store a Ruby object for passing to R; returns handle string "rb_obj_<id>" for use in R.
630
+ def self.register_ruby_object(obj)
631
+ @ruby_object_id += 1
632
+ id = @ruby_object_id
633
+ @ruby_objects[id] = obj
634
+ "rb_obj_#{id}"
356
635
  end
357
636
 
358
- class << self
359
- alias :pf :print_foreign
637
+ # Register a Proc/Method to be invoked from R (e.g. in outer()); returns callback id for the R stub.
638
+ def self.register_callback(proc)
639
+ @ruby_obj_id += 1
640
+ id = @ruby_obj_id
641
+ @callbacks[id] = proc
642
+ id
360
643
  end
361
-
362
- #----------------------------------------------------------------------------------------
363
- #
364
- #----------------------------------------------------------------------------------------
365
-
366
- end
367
-
368
- end
369
644
 
370
- require_relative 'r_module_s'
371
- require_relative 'rsupport_scope'
645
+ # Retrieve the Ruby proc registered for a callback id (from --G_CALLBACK--id--...).
646
+ def self.get_callback(id)
647
+ @callbacks[id.to_i]
648
+ end
372
649
 
650
+ # Retrieve the Ruby object for a handle "rb_obj_<id>" returned from R.
651
+ def self.get_ruby_object(id_str)
652
+ id = id_str.sub("rb_obj_", "").to_i
653
+ @ruby_objects[id]
654
+ end
373
655
 
374
- =begin
375
- #----------------------------------------------------------------------------------------
376
- # Builds a quo or a formula based on the given expression. If the expression has a
377
- # '=~' operator, then a formula should be constructed, if not, then an quo should be
378
- # constructed
379
- # @param expression [R::Expression]
380
- # @return quo or formula
381
- #----------------------------------------------------------------------------------------
656
+ # Collects assignment strings for R.batch (Phase 4).
657
+ class BatchCollector
658
+ attr_reader :ops
382
659
 
383
- def self.quo_or_formula(expression)
660
+ def initialize
661
+ @ops = []
662
+ end
384
663
 
385
- if expression.formula?
386
- R.as__formula(expression.infix)
387
- else
388
- # add a '~' before the expression so we can parse it as an
389
- # R expression from a string. Didn't find a good way of creating an
390
- # expression from string.
391
- # R.as_quosure(R.as__formula("~ #{expression.infix}"))
392
- R.rhs(R.as__formula("~ #{expression.infix}"))
664
+ # Same form as a single bridge eval_with_result assignment, e.g. "#{vn} <- 1L".
665
+ def eval_with_result(assignment_code)
666
+ @ops << assignment_code.to_s
393
667
  end
394
-
395
668
  end
396
- =end
669
+
670
+ def self.batch_eval_with_result(assignment_codes)
671
+ R.bridge.batch_eval_r_with_result(Array(assignment_codes))
672
+ end
673
+ end
674
+ end
675
+
676
+ require_relative 'r_module_s'
677
+ require_relative 'rsupport_scope'