galaaz 0.4.10 → 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 (391) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/LICENSE +0 -0
  4. data/README.md +3123 -882
  5. data/Rakefile +62 -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 +223 -6
  11. data/bin/gknit-draft +105 -0
  12. data/bin/gknit-draft.rb +28 -0
  13. data/bin/gknit_Rscript +127 -0
  14. data/bin/grun +27 -1
  15. data/bin/gstudio +49 -4
  16. data/bin/{gstudio.rb → gstudio_irb.rb} +0 -0
  17. data/bin/gstudio_pry.rb +7 -0
  18. data/bin/install-tinytex +6 -0
  19. data/bin/run_all_rspec +43 -0
  20. data/bin/run_example +14 -0
  21. data/bin/run_old_rspec +19 -0
  22. data/bin/run_rspec +23 -0
  23. data/bin/run_rspec_subset +38 -0
  24. data/bin/run_slow_rspec +19 -0
  25. data/blogs/R-on-Rails-Planning-Document.md +940 -0
  26. data/blogs/README.md +100 -0
  27. data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +38 -66
  28. data/blogs/galaaz_ggplot/galaaz_ggplot.log +754 -0
  29. data/blogs/galaaz_ggplot/galaaz_ggplot.md +364 -0
  30. data/blogs/galaaz_ggplot/galaaz_ggplot.tex +607 -0
  31. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
  32. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
  33. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
  34. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
  35. data/blogs/galaaz_ggplot/midwest.Rmd +3 -3
  36. data/blogs/galaaz_ggplot/midwest_external_png +0 -0
  37. data/blogs/gknit/gknit.Rmd +52 -55
  38. data/blogs/gknit/gknit.md +94 -94
  39. data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
  40. data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
  41. data/blogs/gknit/lst.rds +0 -0
  42. data/blogs/gknit/model.rb +1 -1
  43. data/blogs/gknit/stats.bib +0 -0
  44. data/blogs/manual/include_model_local_repro.Rmd +14 -0
  45. data/blogs/manual/include_model_local_repro.md +75 -0
  46. data/blogs/manual/lst.rds +0 -0
  47. data/blogs/manual/manual.Rmd +1582 -196
  48. data/blogs/manual/manual.log +1786 -0
  49. data/blogs/manual/manual.md +3107 -890
  50. data/blogs/manual/manual.tex +3018 -1086
  51. data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
  52. data/blogs/manual/manual_files/figure-html/diverging_bar.png +0 -0
  53. data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
  54. data/blogs/manual/model.rb +41 -0
  55. data/blogs/nse_dplyr/nse_dplyr.Rmd +277 -151
  56. data/blogs/nse_dplyr/nse_dplyr.log +928 -0
  57. data/blogs/nse_dplyr/nse_dplyr.md +457 -293
  58. data/blogs/oh_my/not_so.rb +0 -0
  59. data/blogs/oh_my/oh_my.Rmd +1234 -25
  60. data/blogs/oh_my/oh_my.log +804 -0
  61. data/blogs/oh_my/oh_my.md +1808 -228
  62. data/blogs/oh_my/oh_my.tex +821 -0
  63. data/blogs/oh_my/old.Rmd +15 -14
  64. data/blogs/ruby_plot/ruby_plot.Rmd +58 -82
  65. data/blogs/ruby_plot/ruby_plot.log +885 -0
  66. data/blogs/ruby_plot/ruby_plot.md +71 -103
  67. data/blogs/ruby_plot/ruby_plot.tex +940 -0
  68. data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
  69. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
  70. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
  71. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
  72. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
  73. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
  74. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
  75. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
  76. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
  77. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
  78. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
  79. data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
  80. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
  81. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
  82. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
  83. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
  84. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
  85. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  86. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
  87. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
  88. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  89. data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  90. data/blogs/test/test.Rmd +14 -0
  91. data/examples/50Plots_MasterList/Images/midwest-scatterplot.PNG +0 -0
  92. data/examples/50Plots_MasterList/ScatterPlot.rb +0 -0
  93. data/examples/50Plots_MasterList/scatter_plot.rb +0 -0
  94. data/examples/Bibliography/master.bib +50 -0
  95. data/examples/Bibliography/stats.bib +72 -0
  96. data/examples/R/calc.R +0 -0
  97. data/examples/R/java_interop.R +0 -0
  98. data/examples/bioconductor_deseq2_airway/Documentation/DESeq2-airway-walkthrough.md +56 -0
  99. data/examples/bioconductor_deseq2_airway/bench_galaaz_three_same_process.rb +53 -0
  100. data/examples/bioconductor_deseq2_airway/bench_r_three_same_process.R +34 -0
  101. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb +33 -0
  102. data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz_optimized.rb +34 -0
  103. data/examples/bioconductor_deseq2_airway/deseq2_airway_minimal.R +30 -0
  104. data/examples/bioconductor_deseq2_airway/deseq2_airway_pipeline_for_bench.R +36 -0
  105. data/examples/islr/all.rb +13 -0
  106. data/examples/islr/ch2.spec.rb +37 -7
  107. data/examples/islr/ch3.spec.rb +11 -2
  108. data/examples/islr/ch3_boston.rb +27 -0
  109. data/examples/islr/ch3_multiple_regression.rb +0 -0
  110. data/examples/islr/ch6.spec.rb +24 -1
  111. data/examples/islr/x_y_rnorm.jpg +0 -0
  112. data/examples/latex_templates/Test-acm_article/Makefile +16 -0
  113. data/examples/latex_templates/Test-acm_article/Test-acm_article.Rmd +65 -0
  114. data/examples/latex_templates/Test-acm_article/acm_proc_article-sp.cls +1670 -0
  115. data/examples/latex_templates/Test-acm_article/sensys-abstract.cls +703 -0
  116. data/examples/latex_templates/Test-acm_article/sigproc.bib +59 -0
  117. data/examples/latex_templates/Test-acs_article/Test-acs_article.Rmd +260 -0
  118. data/examples/latex_templates/Test-acs_article/acs-Test-acs_article.bib +11 -0
  119. data/examples/latex_templates/Test-acs_article/acs-my_output.bib +11 -0
  120. data/examples/latex_templates/Test-acs_article/acstest.bib +17 -0
  121. data/examples/latex_templates/Test-aea_article/AEA.cls +1414 -0
  122. data/{blogs/gknit/marshal.dump → examples/latex_templates/Test-aea_article/BibFile.bib} +0 -0
  123. data/examples/latex_templates/Test-aea_article/Test-aea_article.Rmd +108 -0
  124. data/examples/latex_templates/Test-aea_article/aea.bst +1269 -0
  125. data/examples/latex_templates/Test-aea_article/multicol.sty +853 -0
  126. data/examples/latex_templates/Test-aea_article/references.bib +0 -0
  127. data/examples/latex_templates/Test-aea_article/setspace.sty +546 -0
  128. data/examples/latex_templates/Test-amq_article/Test-amq_article.Rmd +256 -0
  129. data/examples/latex_templates/Test-amq_article/Test-amq_article.pdfsync +3397 -0
  130. data/examples/latex_templates/Test-ams_article/Test-ams_article.Rmd +215 -0
  131. data/examples/latex_templates/Test-ams_article/amstest.bib +436 -0
  132. data/examples/latex_templates/Test-asa_article/Test-asa_article.Rmd +153 -0
  133. data/examples/latex_templates/Test-asa_article/agsm.bst +1353 -0
  134. data/examples/latex_templates/Test-asa_article/bibliography.bib +233 -0
  135. data/examples/latex_templates/Test-ieee_article/IEEEtran.bst +2409 -0
  136. data/examples/latex_templates/Test-ieee_article/IEEEtran.cls +6346 -0
  137. data/examples/latex_templates/Test-ieee_article/Test-ieee_article.Rmd +175 -0
  138. data/examples/latex_templates/Test-ieee_article/mybibfile.bib +20 -0
  139. data/examples/latex_templates/Test-rjournal_article/RJournal.sty +335 -0
  140. data/examples/latex_templates/Test-rjournal_article/RJreferences.bib +18 -0
  141. data/examples/latex_templates/Test-rjournal_article/Test-rjournal_article.Rmd +52 -0
  142. data/examples/latex_templates/Test-springer_article/Test-springer_article.Rmd +65 -0
  143. data/examples/latex_templates/Test-springer_article/bibliography.bib +26 -0
  144. data/examples/latex_templates/Test-springer_article/spbasic.bst +1658 -0
  145. data/examples/latex_templates/Test-springer_article/spmpsci.bst +1512 -0
  146. data/examples/latex_templates/Test-springer_article/spphys.bst +1443 -0
  147. data/examples/latex_templates/Test-springer_article/svglov3.clo +113 -0
  148. data/examples/latex_templates/Test-springer_article/svjour3.cls +1431 -0
  149. data/examples/misc/baseball.csv +0 -0
  150. data/examples/misc/ggplot.rb +3 -2
  151. data/examples/misc/moneyball.rb +0 -0
  152. data/examples/misc/subsetting.rb +0 -0
  153. data/examples/multithread_shards_to_r/shards_to_r.rb +67 -0
  154. data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.Rmd +73 -0
  155. data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.Rmd +382 -0
  156. data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.Rmd +164 -0
  157. data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.Rmd +92 -0
  158. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/attend-grade-relationships.csv +482 -0
  159. data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.Rmd +280 -0
  160. data/examples/rmarkdown/svm-xaringan-example/svm-xaringan-example.Rmd +386 -0
  161. data/examples/sthda_ggplot/README.md +0 -0
  162. data/examples/sthda_ggplot/RUN.md +41 -0
  163. data/examples/sthda_ggplot/all.rb +0 -0
  164. data/examples/sthda_ggplot/one_variable_continuous/density_gg.rb +0 -0
  165. data/examples/sthda_ggplot/one_variable_continuous/geom_area.rb +0 -0
  166. data/examples/sthda_ggplot/one_variable_continuous/geom_density.rb +2 -0
  167. data/examples/sthda_ggplot/one_variable_continuous/geom_dotplot.rb +0 -0
  168. data/examples/sthda_ggplot/one_variable_continuous/geom_freqpoly.rb +0 -0
  169. data/examples/sthda_ggplot/one_variable_continuous/geom_histogram.rb +0 -0
  170. data/examples/sthda_ggplot/one_variable_continuous/histogram_density.rb +0 -0
  171. data/examples/sthda_ggplot/one_variable_continuous/stat.rb +0 -0
  172. data/examples/sthda_ggplot/one_variable_discrete/bar.rb +0 -0
  173. data/examples/sthda_ggplot/qplots/box_violin_dot.rb +0 -0
  174. data/examples/sthda_ggplot/qplots/scatter_plots.rb +0 -0
  175. data/examples/sthda_ggplot/scatter_gg.rb +0 -0
  176. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_bin2d.rb +0 -0
  177. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_density2d.rb +0 -0
  178. data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_hex.rb +0 -0
  179. data/examples/sthda_ggplot/two_variables_cont_cont/geom_point.rb +0 -0
  180. data/examples/sthda_ggplot/two_variables_cont_cont/geom_smooth.rb +0 -0
  181. data/examples/sthda_ggplot/two_variables_cont_cont/misc.rb +0 -0
  182. data/examples/sthda_ggplot/two_variables_cont_function/geom_area.rb +4 -3
  183. data/examples/sthda_ggplot/two_variables_disc_cont/geom_bar.rb +0 -0
  184. data/examples/sthda_ggplot/two_variables_disc_cont/geom_boxplot.rb +0 -0
  185. data/examples/sthda_ggplot/two_variables_disc_cont/geom_dotplot.rb +0 -0
  186. data/examples/sthda_ggplot/two_variables_disc_cont/geom_jitter.rb +0 -0
  187. data/examples/sthda_ggplot/two_variables_disc_cont/geom_line.rb +0 -0
  188. data/examples/sthda_ggplot/two_variables_disc_cont/geom_violin.rb +0 -0
  189. data/examples/sthda_ggplot/two_variables_disc_disc/geom_jitter.rb +0 -0
  190. data/examples/sthda_ggplot/two_variables_error/geom_crossbar.rb +0 -0
  191. data/ext/new_bridge/Makefile +46 -0
  192. data/ext/new_bridge/galaaz_gatekeeper_phase0.cpp +12 -0
  193. data/ext/new_bridge/galaaz_gatekeeper_phase1.cpp +1639 -0
  194. data/lib/R_interface/galaaz_device.R +20 -0
  195. data/lib/R_interface/include_engine.R +109 -0
  196. data/lib/R_interface/new_bridge_adapter.rb +824 -0
  197. data/lib/R_interface/r.rb +177 -25
  198. data/lib/R_interface/r_arrow.rb +113 -0
  199. data/lib/R_interface/r_libs.R +4 -4
  200. data/lib/R_interface/r_methods.rb +13 -116
  201. data/lib/R_interface/r_module_s.rb +0 -0
  202. data/lib/R_interface/rbinary_operators.rb +20 -2
  203. data/lib/R_interface/rclosure.rb +5 -1
  204. data/lib/R_interface/rdata_frame.rb +34 -70
  205. data/lib/R_interface/rdevice.rb +125 -0
  206. data/lib/R_interface/rdevices.R +0 -0
  207. data/lib/R_interface/renvironment.rb +10 -4
  208. data/lib/R_interface/rexpression.rb +5 -1
  209. data/lib/R_interface/rindexed_object.rb +41 -13
  210. data/lib/R_interface/rlanguage.rb +20 -62
  211. data/lib/R_interface/rlist.rb +115 -25
  212. data/lib/R_interface/rlogical_operators.rb +0 -0
  213. data/lib/R_interface/rmatrix.rb +2 -11
  214. data/lib/R_interface/rmd_indexed_object.rb +5 -1
  215. data/lib/R_interface/robject.rb +348 -290
  216. data/lib/R_interface/rpkg.rb +1 -0
  217. data/lib/R_interface/rsupport.rb +610 -331
  218. data/lib/R_interface/rsupport_scope.rb +2 -1
  219. data/lib/R_interface/rsymbol.rb +50 -0
  220. data/lib/R_interface/ruby_callback.rb +2 -3
  221. data/lib/R_interface/ruby_extensions.rb +225 -175
  222. data/lib/R_interface/runary_operators.rb +0 -0
  223. data/lib/R_interface/rvector.rb +147 -31
  224. data/lib/galaaz.rb +0 -0
  225. data/lib/galaaz_jruby.rb +22 -0
  226. data/lib/gknit/diagnostics.rb +50 -0
  227. data/lib/gknit/draft.rb +111 -0
  228. data/lib/gknit/include_engine.rb +15 -7
  229. data/lib/gknit/knitr_engine.rb +223 -107
  230. data/lib/gknit/rb_engine.rb +3 -3
  231. data/lib/gknit/ruby_engine.rb +0 -0
  232. data/lib/gknit.rb +3 -0
  233. data/lib/new_bridge/bootstrap/windows_bootstrap.rb +285 -0
  234. data/lib/new_bridge/envelope.rb +51 -0
  235. data/lib/new_bridge/eval_result.rb +26 -0
  236. data/lib/new_bridge/framing.rb +39 -0
  237. data/lib/new_bridge/instance_pool_client.rb +38 -0
  238. data/lib/new_bridge/r_instance_manager.rb +404 -0
  239. data/lib/new_bridge/session_client.rb +530 -0
  240. data/lib/new_bridge/tcp_framed.rb +44 -0
  241. data/lib/new_bridge.rb +9 -0
  242. data/lib/util/exec_ruby.rb +95 -46
  243. data/lib/util/inline_file.rb +35 -30
  244. data/new_bridge_specs/benchmark_phase5_5_unboxing_spec.rb +96 -0
  245. data/new_bridge_specs/eval_r_async_spec.rb +113 -0
  246. data/new_bridge_specs/integration_phase5_1_concurrent_spec.rb +50 -0
  247. data/new_bridge_specs/integration_phase5_1_eval_spec.rb +16 -0
  248. data/new_bridge_specs/integration_phase5_1_r_api_spec.rb +25 -0
  249. data/new_bridge_specs/integration_phase5_1_smoke_spec.rb +31 -0
  250. data/new_bridge_specs/integration_phase5_2_dataframe_unboxing_spec.rb +19 -0
  251. data/new_bridge_specs/integration_phase5_2_handle_eval_unboxing_spec.rb +25 -0
  252. data/new_bridge_specs/integration_phase5_3_callback_args_spec.rb +28 -0
  253. data/new_bridge_specs/integration_phase5_3_callback_error_spec.rb +22 -0
  254. data/new_bridge_specs/integration_phase5_3_callback_timeout_spec.rb +28 -0
  255. data/new_bridge_specs/integration_phase5_3_callbacks_smoke_spec.rb +22 -0
  256. data/new_bridge_specs/integration_phase5_3_edge_cases_spec.rb +52 -0
  257. data/new_bridge_specs/integration_phase5_3_nested_spec.rb +30 -0
  258. data/new_bridge_specs/integration_phase5_4_concurrent_sessions_spec.rb +53 -0
  259. data/new_bridge_specs/integration_phase5_4_nested_session_callbacks_spec.rb +49 -0
  260. data/new_bridge_specs/integration_phase5_4_session_routing_spec.rb +38 -0
  261. data/new_bridge_specs/integration_phase5_5_stress_concurrency_spec.rb +52 -0
  262. data/new_bridge_specs/integration_phase5_5_unbox_walk_spec.rb +46 -0
  263. data/new_bridge_specs/phase0_protocol_spec.rb +96 -0
  264. data/new_bridge_specs/phase1_req_ret_spec.rb +66 -0
  265. data/new_bridge_specs/phase2_multi_instance_spec.rb +67 -0
  266. data/new_bridge_specs/phase3_callbacks_spec.rb +71 -0
  267. data/new_bridge_specs/phase4_2_hardening_spec.rb +252 -0
  268. data/new_bridge_specs/phase4_3_r_instance_manager_spec.rb +85 -0
  269. data/new_bridge_specs/phase4_nested_callbacks_spec.rb +123 -0
  270. data/r_requires/ggplot.rb +0 -0
  271. data/r_requires/knitr.rb +0 -0
  272. data/specs/all.rb +15 -11
  273. data/specs/arrow_from_ruby_batches_spec.rb +50 -0
  274. data/specs/arrow_semantics_spec.rb +64 -0
  275. data/specs/bridge_concurrent_spec.rb +46 -0
  276. data/specs/bridge_nested_spec.rb +25 -0
  277. data/specs/dataframe_semantics_spec.rb +122 -0
  278. data/specs/dataframe_single_index_logical_filter_spec.rb +21 -0
  279. data/specs/dispatch_probe_cache_spec.rb +38 -0
  280. data/specs/dispatch_probe_error_class_fallback_spec.rb +20 -0
  281. data/specs/dispatch_probe_fallback_spec.rb +18 -0
  282. data/specs/environment_semantics_spec.rb +89 -0
  283. data/specs/field_access_spec.rb +31 -0
  284. data/specs/figures/bg.jpeg +0 -0
  285. data/specs/figures/bg.png +0 -0
  286. data/specs/figures/bg.svg +168 -57
  287. data/specs/figures/dose_len.png +0 -0
  288. data/specs/figures/no_args.jpeg +0 -0
  289. data/specs/figures/no_args.png +0 -0
  290. data/specs/figures/no_args.svg +168 -57
  291. data/specs/figures/width_height.jpeg +0 -0
  292. data/specs/figures/width_height.png +0 -0
  293. data/specs/figures/width_height_units1.jpeg +0 -0
  294. data/specs/figures/width_height_units1.png +0 -0
  295. data/specs/figures/width_height_units2.jpeg +0 -0
  296. data/specs/figures/width_height_units2.png +0 -0
  297. data/specs/formula_semantics_spec.rb +81 -0
  298. data/specs/galaaz_util_exec_ruby_spec.rb +85 -0
  299. data/specs/galaaz_util_inline_file_spec.rb +54 -0
  300. data/specs/gknit_cli_option_permutation_spec.rb +24 -0
  301. data/specs/gknit_include_engine_spec.rb +72 -0
  302. data/specs/gknit_install_timeout_report_spec.rb +69 -0
  303. data/specs/gknit_internal_error_report_spec.rb +57 -0
  304. data/specs/gknit_vector_map_output_spec.rb +59 -0
  305. data/specs/globalenv_guardrail_spec.rb +52 -0
  306. data/specs/language_expression_semantics_spec.rb +145 -0
  307. data/specs/list_semantics_spec.rb +111 -0
  308. data/specs/new_bridge_bulk_dataframe_transfer_spec.rb +44 -0
  309. data/specs/new_bridge_bulk_vector_transfer_spec.rb +73 -0
  310. data/specs/new_bridge_callback_timeout_spec.rb +69 -0
  311. data/specs/new_bridge_eval_r_fallback_spec.rb +55 -0
  312. data/specs/nil_null_spec.rb +42 -0
  313. data/specs/object_build_phase2_spec.rb +53 -0
  314. data/specs/phase1_callback_bridge_spec.rb +84 -0
  315. data/specs/phase2_gknit_generic_rendering_guardrail_spec.rb +46 -0
  316. data/specs/phase2_gknit_no_raw_code_leakage_spec.rb +43 -0
  317. data/specs/phase3_gknit_generic_graphics_capture_spec.rb +71 -0
  318. data/specs/plot_device_semantics_spec.rb +28 -0
  319. data/specs/plot_snapshot_semantics_spec.rb +58 -0
  320. data/specs/protocol_result_spec.rb +236 -0
  321. data/specs/r_batch_fail_fast_spec.rb +47 -0
  322. data/specs/r_bridge_bootstrap_spec.rb +11 -0
  323. data/specs/r_devices.spec.rb +1 -1
  324. data/specs/r_eval.spec.rb +16 -18
  325. data/specs/r_function.spec.rb +1 -1
  326. data/specs/r_instance_manager_spec.rb +285 -0
  327. data/specs/r_list_apply.spec.rb +15 -15
  328. data/specs/r_matrix.spec.rb +0 -0
  329. data/specs/r_nse.spec.rb +5 -5
  330. data/specs/r_object_send_dispatch_spec.rb +13 -0
  331. data/specs/r_vector_comparator_spec.rb +8 -0
  332. data/specs/r_vector_creation.spec.rb +0 -0
  333. data/specs/r_vector_functions.spec.rb +0 -0
  334. data/specs/r_vector_object.spec.rb +0 -0
  335. data/specs/r_vector_operators.spec.rb +0 -0
  336. data/specs/r_vector_structured_scalar_reads_spec.rb +35 -0
  337. data/specs/r_vector_subsetting.spec.rb +0 -0
  338. data/specs/range_helper_spec.rb +21 -0
  339. data/specs/rsupport_scope_spec.rb +28 -0
  340. data/specs/rsupport_var_name_thread_safety_spec.rb +24 -0
  341. data/specs/scalar_character_spec.rb +44 -0
  342. data/specs/scoped_symbol_dsl_refinement_spec.rb +40 -0
  343. data/specs/session_env_bridge_spec.rb +25 -0
  344. data/specs/simplecov_bootstrap_spec.rb +10 -0
  345. data/specs/spec_helper.rb +10 -0
  346. data/specs/tmp.rb +41 -20
  347. data/specs/unboxing_recursion_regression_spec.rb +30 -0
  348. data/specs/unboxing_spec.rb +49 -0
  349. data/specs/verify_callbacks.rb +42 -0
  350. data/sty/galaaz.sty +0 -0
  351. data/version.rb +1 -1
  352. metadata +239 -71
  353. data/blogs/galaaz_ggplot/galaaz_ggplot.aux +0 -41
  354. data/blogs/galaaz_ggplot/galaaz_ggplot.html +0 -705
  355. data/blogs/galaaz_ggplot/galaaz_ggplot.out +0 -10
  356. data/blogs/galaaz_ggplot/galaaz_ggplot.pdf +0 -0
  357. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-latex/midwest_rb.pdf +0 -0
  358. data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-latex/scatter_plot_rb.pdf +0 -0
  359. data/blogs/galaaz_ggplot/midwest.html +0 -188
  360. data/blogs/gknit/gknit.html +0 -2266
  361. data/blogs/gknit/gknit.pdf +0 -0
  362. data/blogs/gknit/gknit.tex +0 -1358
  363. data/blogs/manual/graph.rb +0 -29
  364. data/blogs/manual/manual.html +0 -2995
  365. data/blogs/manual/manual.pdf +0 -0
  366. data/blogs/manual/manual_files/figure-latex/diverging_bar.pdf +0 -0
  367. data/blogs/nse_dplyr/nse_dplyr.html +0 -960
  368. data/blogs/nse_dplyr/nse_dplyr.pdf +0 -0
  369. data/blogs/nse_dplyr/nse_dplyr.tex +0 -1373
  370. data/blogs/oh_my/oh_my.html +0 -680
  371. data/blogs/ruby_plot/ruby_plot.Rmd_external_figs +0 -662
  372. data/blogs/ruby_plot/ruby_plot.html +0 -729
  373. data/blogs/ruby_plot/ruby_plot.pdf +0 -0
  374. data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.svg +0 -57
  375. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.svg +0 -106
  376. data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.svg +0 -110
  377. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.svg +0 -174
  378. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.svg +0 -236
  379. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.svg +0 -296
  380. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.svg +0 -236
  381. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.svg +0 -218
  382. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.svg +0 -128
  383. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.svg +0 -150
  384. data/examples/paper/paper.rb +0 -36
  385. data/specs/r_dataframe.spec.rb +0 -379
  386. data/specs/r_environment.spec.rb +0 -140
  387. data/specs/r_formula.spec.rb +0 -232
  388. data/specs/r_language.spec.rb +0 -112
  389. data/specs/r_list.spec.rb +0 -293
  390. data/specs/r_plots.spec.rb +0 -72
  391. data/specs/ruby_expression.spec.rb +0 -315
@@ -1,398 +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
- # @param internal [Boolean] true if returning to an internal object, i.e., does not
213
- # wrap the return object in a Ruby object
214
- # @args [Array] Array of arguments for the function
215
- #----------------------------------------------------------------------------------------
216
-
217
- def self.exec_function(function, *args)
218
369
 
219
- begin
220
-
221
- # If the execution counter is 0, function was not recursively called
222
- # Starts capturing output
223
- if (@@exec_counter == 0)
224
- R::Support.eval("unlockBinding('r_capture', globalenv())")
225
- @@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)
226
377
  end
227
-
228
- @@exec_counter = @@exec_counter + 1
229
-
230
- # function has no arguments, call it directly
231
- if (args.length == 0)
232
- res = R::Object.build(function.call)
233
- else
234
- pl = R::Support.parse2list(*args)
235
- res = @@exec_from_ruby.call(R::Object.method(:build), function, pl)
236
- # 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})"
237
424
  end
238
-
239
- @@exec_counter = @@exec_counter - 1
240
-
241
- # When execution counter back to 0, print the captured output if the length
242
- # of the output is greater than 0
243
- if (@@exec_counter == 0)
244
- R::Support.stop_capture.call(@@con)
245
- if (R::Support.eval("length(r_capture) > 0")[0])
246
- cap = R::Object.build(R::Support.eval("r_capture"))
247
- (0...cap.size).each do |i|
248
- puts cap >> i
249
- end
425
+
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))
250
440
  end
441
+ else
442
+ block.call(result)
251
443
  end
252
-
253
- rescue StandardError => e
254
- R::Support.stop_capture.call(@@con) if (@@exec_counter == 0)
255
- raise e
256
444
  end
257
-
258
- res
259
-
260
- end
261
-
262
- #----------------------------------------------------------------------------------------
263
- # @param function_name [String] Name of the R function to execute
264
- # @args [Array] Array of arguments for the function
265
- #----------------------------------------------------------------------------------------
266
-
267
- def self.exec_function_name(function_name, *args)
268
- # @TODO: should check all that can go wrong when calling eval(function_name) to
269
- # raise the proper exception
270
- f = R::Support.eval(function_name)
271
- R::Support.exec_function(f, *args)
272
445
  end
273
446
 
274
- #----------------------------------------------------------------------------------------
275
- # Executes the given R function with the given arguments.
276
- #----------------------------------------------------------------------------------------
277
-
278
- def self.exec_function_i(function, *args)
279
- pl = R::Support.parse2list(*args)
280
- R::Support.eval("invoke").call(function, pl)
447
+ class << self
448
+ alias_method :exec_function_name, :exec_function
281
449
  end
282
450
 
283
- #----------------------------------------------------------------------------------------
284
- # sets the R symbol <name> to the given value
285
- # @param name [String] String representation of the R symbol that needs to be assigned
286
- # @param args [Array] Array with one object to be set to the symbol
287
- #----------------------------------------------------------------------------------------
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
288
472
 
289
- def self.set_symbol(name, *args)
290
- args << R::Support.eval("globalenv").call()
291
- 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
292
489
  end
293
-
294
- #----------------------------------------------------------------------------------------
295
- # Calls the R 'eval' function. This requires some special semantics
296
- # R function 'eval' needs to be called in a special way, since it expects
297
- # the second argument to be an environment. If the arguments are packed
298
- # into a list, then there is no second argument and the function fails to
299
- # use the second argument as environment
300
- #----------------------------------------------------------------------------------------
301
-
302
- def self.r_evaluate(*args)
303
- r_args = args.map { |arg| R::Support.parse_arg(arg) }
304
- 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)
305
522
  end
306
523
 
307
- #----------------------------------------------------------------------------------------
308
- # Process the missing method
309
- # @param symbol [Symbol]
310
- # @param internal [Boolean] true if the method will return to an internal method, i.e.,
311
- # it should not wrap the return value inside an R::Object
312
- # @param object [Ruby Object] the ruby object to which the method is applied, false if
313
- # it is not applied to an object
314
- #----------------------------------------------------------------------------------------
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
315
529
 
316
- 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
317
535
 
318
- name = R::Support.convert_symbol2r(symbol)
319
-
320
- case name
321
- # when missing method has an '=' sign in it...
322
- when ->(x) { x =~ /(.*)=$/ }
323
- R::Support.set_symbol($1, *args)
324
- # when missing method is 'eval'... needs special treatment
325
- when "eval"
326
- 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
327
584
  else
328
- function = R::Support.eval(name)
329
- internal ? R::Support.exec_function_i(function, *args) :
330
- 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
331
611
  end
332
612
 
333
- end
334
-
335
- #----------------------------------------------------------------------------------------
336
- #
337
- #----------------------------------------------------------------------------------------
613
+ return self.exec_function(name, internal, *args) if is_func
338
614
 
339
- def self.print_str(obj)
340
-
341
- lst = obj.as__list
342
- puts lst
343
- =begin
344
- (1..lst.length >> 0).each do |i|
345
- 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}")
346
618
  end
347
- =end
619
+
620
+ self.exec_function(name, internal, *args)
348
621
  end
349
622
 
350
- #----------------------------------------------------------------------------------------
351
- # Prints a foreign R interop pointer. Used for debug.
352
- #----------------------------------------------------------------------------------------
353
623
 
354
- def self.print_foreign(interop)
355
- puts "===External value==="
356
- R::Support.eval("print").call(interop)
357
- 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}"
358
635
  end
359
636
 
360
- class << self
361
- 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
362
643
  end
363
-
364
- #----------------------------------------------------------------------------------------
365
- #
366
- #----------------------------------------------------------------------------------------
367
-
368
- end
369
-
370
- end
371
644
 
372
- require_relative 'r_module_s'
373
- 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
374
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
375
655
 
376
- =begin
377
- #----------------------------------------------------------------------------------------
378
- # Builds a quo or a formula based on the given expression. If the expression has a
379
- # '=~' operator, then a formula should be constructed, if not, then an quo should be
380
- # constructed
381
- # @param expression [R::Expression]
382
- # @return quo or formula
383
- #----------------------------------------------------------------------------------------
656
+ # Collects assignment strings for R.batch (Phase 4).
657
+ class BatchCollector
658
+ attr_reader :ops
384
659
 
385
- def self.quo_or_formula(expression)
660
+ def initialize
661
+ @ops = []
662
+ end
386
663
 
387
- if expression.formula?
388
- R.as__formula(expression.infix)
389
- else
390
- # add a '~' before the expression so we can parse it as an
391
- # R expression from a string. Didn't find a good way of creating an
392
- # expression from string.
393
- # R.as_quosure(R.as__formula("~ #{expression.infix}"))
394
- 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
395
667
  end
396
-
397
668
  end
398
- =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'