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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/LICENSE +0 -0
- data/README.md +1360 -636
- data/Rakefile +61 -41
- data/bin/galaaz-bootstrap +137 -0
- data/bin/galaaz-jruby +14 -0
- data/bin/galaaz_jruby_env.inc.sh +6 -0
- data/bin/gbookdown +64 -0
- data/bin/gknit +84 -13
- data/bin/gknit-draft.rb +0 -0
- data/bin/gstudio +5 -3
- data/bin/gstudio_irb.rb +0 -0
- data/bin/gstudio_pry.rb +0 -0
- data/bin/install-tinytex +6 -0
- data/bin/run_all_rspec +43 -0
- data/bin/run_example +14 -0
- data/bin/run_old_rspec +19 -0
- data/bin/run_rspec +23 -0
- data/bin/run_rspec_subset +38 -0
- data/bin/run_slow_rspec +19 -0
- data/blogs/R-on-Rails-Planning-Document.md +940 -0
- data/blogs/README.md +100 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +38 -66
- data/blogs/galaaz_ggplot/galaaz_ggplot.log +754 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot.md +115 -155
- data/blogs/galaaz_ggplot/galaaz_ggplot.tex +607 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/midwest_rb.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-html/scatter_plot_rb.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/midwest_rb.png +0 -0
- data/blogs/galaaz_ggplot/galaaz_ggplot_files/figure-markdown_github/scatter_plot_rb.png +0 -0
- data/blogs/galaaz_ggplot/midwest.Rmd +3 -3
- data/blogs/galaaz_ggplot/midwest_external_png +0 -0
- data/blogs/gknit/gknit.Rmd +47 -52
- data/blogs/gknit/gknit.md +1430 -0
- data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
- data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
- data/blogs/gknit/lst.rds +0 -0
- data/blogs/gknit/model.rb +1 -1
- data/blogs/gknit/stats.bib +0 -0
- data/blogs/manual/include_model_local_repro.Rmd +14 -0
- data/blogs/manual/include_model_local_repro.md +75 -0
- data/blogs/manual/lst.rds +0 -0
- data/blogs/manual/manual.Rmd +852 -239
- data/blogs/manual/manual.log +1786 -0
- data/blogs/manual/manual.md +1360 -636
- data/blogs/manual/manual.tex +1883 -1161
- data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
- data/blogs/manual/manual_files/figure-html/diverging_bar.png +0 -0
- data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
- data/blogs/manual/model.rb +1 -1
- data/blogs/nse_dplyr/nse_dplyr.Rmd +84 -111
- data/blogs/nse_dplyr/nse_dplyr.log +928 -0
- data/blogs/nse_dplyr/nse_dplyr.md +198 -229
- data/blogs/oh_my/not_so.rb +0 -0
- data/blogs/oh_my/oh_my.Rmd +1234 -25
- data/blogs/oh_my/oh_my.log +804 -0
- data/blogs/oh_my/oh_my.md +1663 -86
- data/blogs/oh_my/oh_my.tex +821 -0
- data/blogs/oh_my/old.Rmd +15 -14
- data/blogs/ruby_plot/ruby_plot.Rmd +58 -82
- data/blogs/ruby_plot/ruby_plot.log +885 -0
- data/blogs/ruby_plot/ruby_plot.md +71 -102
- data/blogs/ruby_plot/ruby_plot.tex +940 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/dose_len.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_delivery.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facet_by_dose.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_by_delivery_color2.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_decorations.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_points.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/final_box_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/dose_len.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_delivery.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facet_by_dose.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_by_delivery_color2.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_decorations.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_points.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_box_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
- data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
- data/blogs/test/test.Rmd +14 -0
- data/examples/50Plots_MasterList/Images/midwest-scatterplot.PNG +0 -0
- data/examples/50Plots_MasterList/ScatterPlot.rb +0 -0
- data/examples/50Plots_MasterList/scatter_plot.rb +0 -0
- data/examples/Bibliography/master.bib +0 -0
- data/examples/Bibliography/stats.bib +0 -0
- data/examples/R/calc.R +0 -0
- data/examples/R/java_interop.R +0 -0
- data/examples/bioconductor_deseq2_airway/Documentation/DESeq2-airway-walkthrough.md +56 -0
- data/examples/bioconductor_deseq2_airway/bench_galaaz_three_same_process.rb +53 -0
- data/examples/bioconductor_deseq2_airway/bench_r_three_same_process.R +34 -0
- data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz.rb +33 -0
- data/examples/bioconductor_deseq2_airway/deseq2_airway_galaaz_optimized.rb +34 -0
- data/examples/bioconductor_deseq2_airway/deseq2_airway_minimal.R +30 -0
- data/examples/bioconductor_deseq2_airway/deseq2_airway_pipeline_for_bench.R +36 -0
- data/examples/islr/all.rb +13 -0
- data/examples/islr/ch2.spec.rb +37 -7
- data/examples/islr/ch3.spec.rb +11 -2
- data/examples/islr/ch3_boston.rb +27 -0
- data/examples/islr/ch3_multiple_regression.rb +0 -0
- data/examples/islr/ch6.spec.rb +24 -1
- data/examples/islr/x_y_rnorm.jpg +0 -0
- data/examples/latex_templates/Test-acm_article/acm_proc_article-sp.cls +0 -0
- data/examples/latex_templates/Test-acm_article/sigproc.bib +0 -0
- data/examples/latex_templates/Test-acs_article/acs-Test-acs_article.bib +0 -0
- data/examples/latex_templates/Test-acs_article/acs-my_output.bib +0 -0
- data/examples/latex_templates/Test-aea_article/BibFile.bib +0 -0
- data/examples/latex_templates/Test-aea_article/Test-aea_article.Rmd +0 -0
- data/examples/latex_templates/Test-aea_article/references.bib +0 -0
- data/examples/latex_templates/Test-amq_article/Test-amq_article.Rmd +0 -0
- data/examples/latex_templates/Test-amq_article/Test-amq_article.pdfsync +0 -0
- data/examples/latex_templates/Test-ieee_article/IEEEtran.bst +0 -0
- data/examples/latex_templates/Test-ieee_article/mybibfile.bib +0 -0
- data/examples/latex_templates/Test-rjournal_article/RJournal.sty +0 -0
- data/examples/latex_templates/Test-rjournal_article/RJreferences.bib +0 -0
- data/examples/latex_templates/Test-rjournal_article/Test-rjournal_article.Rmd +0 -0
- data/examples/misc/baseball.csv +0 -0
- data/examples/misc/ggplot.rb +3 -2
- data/examples/misc/moneyball.rb +0 -0
- data/examples/misc/subsetting.rb +0 -0
- data/examples/multithread_shards_to_r/shards_to_r.rb +67 -0
- data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.Rmd +0 -0
- data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.Rmd +0 -0
- data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.Rmd +0 -0
- data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.Rmd +0 -0
- data/examples/rmarkdown/svm-rmarkdown-syllabus-example/attend-grade-relationships.csv +0 -0
- data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.Rmd +0 -0
- data/examples/rmarkdown/svm-xaringan-example/svm-xaringan-example.Rmd +0 -0
- data/examples/sthda_ggplot/README.md +0 -0
- data/examples/sthda_ggplot/RUN.md +41 -0
- data/examples/sthda_ggplot/all.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/density_gg.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/geom_area.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/geom_density.rb +2 -0
- data/examples/sthda_ggplot/one_variable_continuous/geom_dotplot.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/geom_freqpoly.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/geom_histogram.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/histogram_density.rb +0 -0
- data/examples/sthda_ggplot/one_variable_continuous/stat.rb +0 -0
- data/examples/sthda_ggplot/one_variable_discrete/bar.rb +0 -0
- data/examples/sthda_ggplot/qplots/box_violin_dot.rb +0 -0
- data/examples/sthda_ggplot/qplots/scatter_plots.rb +0 -0
- data/examples/sthda_ggplot/scatter_gg.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_bin2d.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_density2d.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_bivariate/geom_hex.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_cont/geom_point.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_cont/geom_smooth.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_cont/misc.rb +0 -0
- data/examples/sthda_ggplot/two_variables_cont_function/geom_area.rb +4 -3
- data/examples/sthda_ggplot/two_variables_disc_cont/geom_bar.rb +0 -0
- data/examples/sthda_ggplot/two_variables_disc_cont/geom_boxplot.rb +0 -0
- data/examples/sthda_ggplot/two_variables_disc_cont/geom_dotplot.rb +0 -0
- data/examples/sthda_ggplot/two_variables_disc_cont/geom_jitter.rb +0 -0
- data/examples/sthda_ggplot/two_variables_disc_cont/geom_line.rb +0 -0
- data/examples/sthda_ggplot/two_variables_disc_cont/geom_violin.rb +0 -0
- data/examples/sthda_ggplot/two_variables_disc_disc/geom_jitter.rb +0 -0
- data/examples/sthda_ggplot/two_variables_error/geom_crossbar.rb +0 -0
- data/ext/new_bridge/Makefile +46 -0
- data/ext/new_bridge/galaaz_gatekeeper_phase0.cpp +12 -0
- data/ext/new_bridge/galaaz_gatekeeper_phase1.cpp +1639 -0
- data/lib/R_interface/galaaz_device.R +20 -0
- data/lib/R_interface/include_engine.R +109 -0
- data/lib/R_interface/new_bridge_adapter.rb +824 -0
- data/lib/R_interface/r.rb +177 -25
- data/lib/R_interface/r_arrow.rb +113 -0
- data/lib/R_interface/r_libs.R +3 -3
- data/lib/R_interface/r_methods.rb +13 -126
- data/lib/R_interface/r_module_s.rb +0 -0
- data/lib/R_interface/rbinary_operators.rb +20 -2
- data/lib/R_interface/rclosure.rb +5 -1
- data/lib/R_interface/rdata_frame.rb +34 -70
- data/lib/R_interface/rdevice.rb +125 -0
- data/lib/R_interface/rdevices.R +0 -0
- data/lib/R_interface/renvironment.rb +10 -4
- data/lib/R_interface/rexpression.rb +5 -1
- data/lib/R_interface/rindexed_object.rb +41 -13
- data/lib/R_interface/rlanguage.rb +20 -62
- data/lib/R_interface/rlist.rb +115 -25
- data/lib/R_interface/rlogical_operators.rb +0 -0
- data/lib/R_interface/rmatrix.rb +2 -11
- data/lib/R_interface/rmd_indexed_object.rb +5 -1
- data/lib/R_interface/robject.rb +348 -290
- data/lib/R_interface/rpkg.rb +0 -0
- data/lib/R_interface/rsupport.rb +609 -328
- data/lib/R_interface/rsupport_scope.rb +2 -1
- data/lib/R_interface/rsymbol.rb +50 -0
- data/lib/R_interface/ruby_callback.rb +2 -3
- data/lib/R_interface/ruby_extensions.rb +225 -175
- data/lib/R_interface/runary_operators.rb +0 -0
- data/lib/R_interface/rvector.rb +147 -31
- data/lib/galaaz.rb +0 -0
- data/lib/galaaz_jruby.rb +22 -0
- data/lib/gknit/diagnostics.rb +50 -0
- data/lib/gknit/draft.rb +23 -17
- data/lib/gknit/include_engine.rb +15 -7
- data/lib/gknit/knitr_engine.rb +223 -74
- data/lib/gknit/rb_engine.rb +3 -3
- data/lib/gknit/ruby_engine.rb +0 -0
- data/lib/gknit.rb +1 -0
- data/lib/new_bridge/bootstrap/windows_bootstrap.rb +285 -0
- data/lib/new_bridge/envelope.rb +51 -0
- data/lib/new_bridge/eval_result.rb +26 -0
- data/lib/new_bridge/framing.rb +39 -0
- data/lib/new_bridge/instance_pool_client.rb +38 -0
- data/lib/new_bridge/r_instance_manager.rb +404 -0
- data/lib/new_bridge/session_client.rb +530 -0
- data/lib/new_bridge/tcp_framed.rb +44 -0
- data/lib/new_bridge.rb +9 -0
- data/lib/util/exec_ruby.rb +95 -20
- data/lib/util/inline_file.rb +35 -30
- data/new_bridge_specs/benchmark_phase5_5_unboxing_spec.rb +96 -0
- data/new_bridge_specs/eval_r_async_spec.rb +113 -0
- data/new_bridge_specs/integration_phase5_1_concurrent_spec.rb +50 -0
- data/new_bridge_specs/integration_phase5_1_eval_spec.rb +16 -0
- data/new_bridge_specs/integration_phase5_1_r_api_spec.rb +25 -0
- data/new_bridge_specs/integration_phase5_1_smoke_spec.rb +31 -0
- data/new_bridge_specs/integration_phase5_2_dataframe_unboxing_spec.rb +19 -0
- data/new_bridge_specs/integration_phase5_2_handle_eval_unboxing_spec.rb +25 -0
- data/new_bridge_specs/integration_phase5_3_callback_args_spec.rb +28 -0
- data/new_bridge_specs/integration_phase5_3_callback_error_spec.rb +22 -0
- data/new_bridge_specs/integration_phase5_3_callback_timeout_spec.rb +28 -0
- data/new_bridge_specs/integration_phase5_3_callbacks_smoke_spec.rb +22 -0
- data/new_bridge_specs/integration_phase5_3_edge_cases_spec.rb +52 -0
- data/new_bridge_specs/integration_phase5_3_nested_spec.rb +30 -0
- data/new_bridge_specs/integration_phase5_4_concurrent_sessions_spec.rb +53 -0
- data/new_bridge_specs/integration_phase5_4_nested_session_callbacks_spec.rb +49 -0
- data/new_bridge_specs/integration_phase5_4_session_routing_spec.rb +38 -0
- data/new_bridge_specs/integration_phase5_5_stress_concurrency_spec.rb +52 -0
- data/new_bridge_specs/integration_phase5_5_unbox_walk_spec.rb +46 -0
- data/new_bridge_specs/phase0_protocol_spec.rb +96 -0
- data/new_bridge_specs/phase1_req_ret_spec.rb +66 -0
- data/new_bridge_specs/phase2_multi_instance_spec.rb +67 -0
- data/new_bridge_specs/phase3_callbacks_spec.rb +71 -0
- data/new_bridge_specs/phase4_2_hardening_spec.rb +252 -0
- data/new_bridge_specs/phase4_3_r_instance_manager_spec.rb +85 -0
- data/new_bridge_specs/phase4_nested_callbacks_spec.rb +123 -0
- data/r_requires/ggplot.rb +0 -0
- data/r_requires/knitr.rb +0 -0
- data/specs/all.rb +15 -11
- data/specs/arrow_from_ruby_batches_spec.rb +50 -0
- data/specs/arrow_semantics_spec.rb +64 -0
- data/specs/bridge_concurrent_spec.rb +46 -0
- data/specs/bridge_nested_spec.rb +25 -0
- data/specs/dataframe_semantics_spec.rb +122 -0
- data/specs/dataframe_single_index_logical_filter_spec.rb +21 -0
- data/specs/dispatch_probe_cache_spec.rb +38 -0
- data/specs/dispatch_probe_error_class_fallback_spec.rb +20 -0
- data/specs/dispatch_probe_fallback_spec.rb +18 -0
- data/specs/environment_semantics_spec.rb +89 -0
- data/specs/field_access_spec.rb +31 -0
- data/specs/figures/bg.jpeg +0 -0
- data/specs/figures/bg.png +0 -0
- data/specs/figures/bg.svg +168 -57
- data/specs/figures/dose_len.png +0 -0
- data/specs/figures/no_args.jpeg +0 -0
- data/specs/figures/no_args.png +0 -0
- data/specs/figures/no_args.svg +168 -57
- data/specs/figures/width_height.jpeg +0 -0
- data/specs/figures/width_height.png +0 -0
- data/specs/figures/width_height_units1.jpeg +0 -0
- data/specs/figures/width_height_units1.png +0 -0
- data/specs/figures/width_height_units2.jpeg +0 -0
- data/specs/figures/width_height_units2.png +0 -0
- data/specs/formula_semantics_spec.rb +81 -0
- data/specs/galaaz_util_exec_ruby_spec.rb +85 -0
- data/specs/galaaz_util_inline_file_spec.rb +54 -0
- data/specs/gknit_cli_option_permutation_spec.rb +24 -0
- data/specs/gknit_include_engine_spec.rb +72 -0
- data/specs/gknit_install_timeout_report_spec.rb +69 -0
- data/specs/gknit_internal_error_report_spec.rb +57 -0
- data/specs/gknit_vector_map_output_spec.rb +59 -0
- data/specs/globalenv_guardrail_spec.rb +52 -0
- data/specs/language_expression_semantics_spec.rb +145 -0
- data/specs/list_semantics_spec.rb +111 -0
- data/specs/new_bridge_bulk_dataframe_transfer_spec.rb +44 -0
- data/specs/new_bridge_bulk_vector_transfer_spec.rb +73 -0
- data/specs/new_bridge_callback_timeout_spec.rb +69 -0
- data/specs/new_bridge_eval_r_fallback_spec.rb +55 -0
- data/specs/nil_null_spec.rb +42 -0
- data/specs/object_build_phase2_spec.rb +53 -0
- data/specs/phase1_callback_bridge_spec.rb +84 -0
- data/specs/phase2_gknit_generic_rendering_guardrail_spec.rb +46 -0
- data/specs/phase2_gknit_no_raw_code_leakage_spec.rb +43 -0
- data/specs/phase3_gknit_generic_graphics_capture_spec.rb +71 -0
- data/specs/plot_device_semantics_spec.rb +28 -0
- data/specs/plot_snapshot_semantics_spec.rb +58 -0
- data/specs/protocol_result_spec.rb +236 -0
- data/specs/r_batch_fail_fast_spec.rb +47 -0
- data/specs/r_bridge_bootstrap_spec.rb +11 -0
- data/specs/r_devices.spec.rb +1 -1
- data/specs/r_eval.spec.rb +16 -18
- data/specs/r_function.spec.rb +1 -1
- data/specs/r_instance_manager_spec.rb +285 -0
- data/specs/r_list_apply.spec.rb +15 -15
- data/specs/r_matrix.spec.rb +0 -0
- data/specs/r_nse.spec.rb +5 -5
- data/specs/r_object_send_dispatch_spec.rb +13 -0
- data/specs/r_vector_comparator_spec.rb +8 -0
- data/specs/r_vector_creation.spec.rb +0 -0
- data/specs/r_vector_functions.spec.rb +0 -0
- data/specs/r_vector_object.spec.rb +0 -0
- data/specs/r_vector_operators.spec.rb +0 -0
- data/specs/r_vector_structured_scalar_reads_spec.rb +35 -0
- data/specs/r_vector_subsetting.spec.rb +0 -0
- data/specs/range_helper_spec.rb +21 -0
- data/specs/rsupport_scope_spec.rb +28 -0
- data/specs/rsupport_var_name_thread_safety_spec.rb +24 -0
- data/specs/scalar_character_spec.rb +44 -0
- data/specs/scoped_symbol_dsl_refinement_spec.rb +40 -0
- data/specs/session_env_bridge_spec.rb +25 -0
- data/specs/simplecov_bootstrap_spec.rb +10 -0
- data/specs/spec_helper.rb +10 -0
- data/specs/tmp.rb +0 -0
- data/specs/unboxing_recursion_regression_spec.rb +30 -0
- data/specs/unboxing_spec.rb +49 -0
- data/specs/verify_callbacks.rb +42 -0
- data/sty/galaaz.sty +0 -0
- data/version.rb +1 -1
- metadata +194 -64
- data/blogs/galaaz_ggplot/galaaz_ggplot.html +0 -520
- data/blogs/galaaz_ggplot/galaaz_ggplot.pdf +0 -0
- data/blogs/galaaz_ggplot/midwest.html +0 -188
- data/blogs/gknit/gknit.html +0 -2266
- data/blogs/gknit/gknit.pdf +0 -0
- data/blogs/manual/manual.html +0 -4638
- data/blogs/manual/manual.pdf +0 -0
- data/blogs/manual/manual_files/figure-latex/diverging_bar.pdf +0 -0
- data/blogs/nse_dplyr/nse_dplyr.html +0 -878
- data/blogs/nse_dplyr/nse_dplyr.pdf +0 -0
- data/blogs/oh_my/oh_my.html +0 -568
- data/blogs/ruby_plot/ruby_plot.html +0 -544
- data/blogs/ruby_plot/ruby_plot.pdf +0 -0
- data/examples/latex_templates/Test-acs_article/Test-acs_article.pdf +0 -0
- data/examples/latex_templates/Test-aea_article/Test-aea_article.pdf +0 -0
- data/examples/latex_templates/Test-amq_article/Test-amq_article.pdf +0 -0
- data/examples/latex_templates/Test-amq_article/pics/Figure2.pdf +0 -0
- data/examples/latex_templates/Test-asa_article/Test-asa_article.pdf +0 -0
- data/examples/latex_templates/Test-ieee_article/Test-ieee_article.pdf +0 -0
- data/examples/latex_templates/Test-rjournal_article/RJwrapper.pdf +0 -0
- data/examples/latex_templates/Test-springer_article/Test-springer_article.pdf +0 -0
- data/examples/rmarkdown/svm-rmarkdown-anon-ms-example/svm-rmarkdown-anon-ms-example.pdf +0 -0
- data/examples/rmarkdown/svm-rmarkdown-article-example/svm-rmarkdown-article-example.pdf +0 -0
- data/examples/rmarkdown/svm-rmarkdown-beamer-example/svm-rmarkdown-beamer-example.pdf +0 -0
- data/examples/rmarkdown/svm-rmarkdown-cv/svm-rmarkdown-cv.pdf +0 -0
- data/examples/rmarkdown/svm-rmarkdown-syllabus-example/svm-rmarkdown-syllabus-example.pdf +0 -0
- data/specs/r_dataframe.spec.rb +0 -379
- data/specs/r_environment.spec.rb +0 -140
- data/specs/r_formula.spec.rb +0 -232
- data/specs/r_language.spec.rb +0 -112
- data/specs/r_list.spec.rb +0 -293
- data/specs/r_plots.spec.rb +0 -72
- data/specs/ruby_expression.spec.rb +0 -316
|
@@ -0,0 +1,1639 @@
|
|
|
1
|
+
// -----------------------------------------------------------------------------
|
|
2
|
+
// galaaz_gatekeeper_phase1.cpp
|
|
3
|
+
//
|
|
4
|
+
// Gatekeeper runtime loaded inside the R process. It is the R-side half of the
|
|
5
|
+
// NewBridge protocol and is responsible for:
|
|
6
|
+
//
|
|
7
|
+
// 1) Opening a TCP connection from R -> Ruby.
|
|
8
|
+
// 2) Reading framed MsgPack envelopes from Ruby.
|
|
9
|
+
// 3) Evaluating REQ payloads in a per-session R environment.
|
|
10
|
+
// 4) Writing RET envelopes back to Ruby (JSON payload, success/error status).
|
|
11
|
+
// 5) During callbacks, servicing nested REQ traffic while waiting for RET.
|
|
12
|
+
//
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// High-level protocol
|
|
15
|
+
//
|
|
16
|
+
// Ruby sends: [uint32 length][msgpack envelope bytes]
|
|
17
|
+
// R reads and parses envelope fields:
|
|
18
|
+
// - call_id
|
|
19
|
+
// - type ("REQ" / "RET" / "CALL")
|
|
20
|
+
// - session_id
|
|
21
|
+
// - instance_id
|
|
22
|
+
// - payload
|
|
23
|
+
// - parent_id (optional nil)
|
|
24
|
+
//
|
|
25
|
+
// R executes payload code and returns RET:
|
|
26
|
+
// status="success" with JSON scalar payload, OR
|
|
27
|
+
// status="error" with JSON error payload.
|
|
28
|
+
//
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
// Session model
|
|
31
|
+
//
|
|
32
|
+
// Each session_id maps to a dedicated R environment in .galaaz_sessions. This
|
|
33
|
+
// provides isolation for user variables across concurrent sessions while still
|
|
34
|
+
// inheriting from global env for base functions/operators.
|
|
35
|
+
//
|
|
36
|
+
// -----------------------------------------------------------------------------
|
|
37
|
+
// Callback model
|
|
38
|
+
//
|
|
39
|
+
// galaaz_callback_call() is called by R code to invoke Ruby callbacks. While
|
|
40
|
+
// waiting for callback RET, it runs a nested service loop that continues to
|
|
41
|
+
// process inbound REQ messages. This avoids deadlocks in recursive callback
|
|
42
|
+
// scenarios (Ruby -> R -> Ruby -> R ...).
|
|
43
|
+
//
|
|
44
|
+
// POSIX sockets only (Linux/macOS/WSL).
|
|
45
|
+
// -----------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
#include <Rcpp.h>
|
|
48
|
+
#include <arpa/inet.h>
|
|
49
|
+
#include <chrono>
|
|
50
|
+
#include <cstdint>
|
|
51
|
+
#include <cstring>
|
|
52
|
+
#include <cstdlib>
|
|
53
|
+
#include <map>
|
|
54
|
+
#include <netdb.h>
|
|
55
|
+
#include <netinet/in.h>
|
|
56
|
+
#include <poll.h>
|
|
57
|
+
#include <sstream>
|
|
58
|
+
#include <string>
|
|
59
|
+
#include <sys/socket.h>
|
|
60
|
+
#include <sys/un.h>
|
|
61
|
+
#include <unistd.h>
|
|
62
|
+
#include <vector>
|
|
63
|
+
|
|
64
|
+
// Active bridge socket used by main loop and callback path.
|
|
65
|
+
static int g_bridge_fd = -1;
|
|
66
|
+
// Instance id of the currently serviced REQ; reused by callback CALL messages.
|
|
67
|
+
static std::string g_current_instance_id = "default";
|
|
68
|
+
// Depth of galaaz_callback_call frames. Nested REQ evals run while this is > 0.
|
|
69
|
+
static int g_callback_nesting_depth = 0;
|
|
70
|
+
|
|
71
|
+
struct CallbackNestingGuard {
|
|
72
|
+
CallbackNestingGuard() { ++g_callback_nesting_depth; }
|
|
73
|
+
~CallbackNestingGuard() { --g_callback_nesting_depth; }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Under nested callback stacks, rlang's uncaught error formatter can fail
|
|
77
|
+
// (path_trim_prefix -> strsplit) and overwrite geterrmessage() with that
|
|
78
|
+
// secondary failure. Catching with tryCatch and re-stopping with
|
|
79
|
+
// conditionMessage() preserves the causal dplyr/rlang message for Ruby.
|
|
80
|
+
static std::string wrap_expr_preserve_condition_message(const std::string& expr_code) {
|
|
81
|
+
return "tryCatch((" + expr_code +
|
|
82
|
+
"), error = function(e) stop(conditionMessage(e), call. = FALSE))";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static std::string maybe_wrap_nested_eval_expr(const std::string& expr_code) {
|
|
86
|
+
if (g_callback_nesting_depth <= 0) return expr_code;
|
|
87
|
+
return wrap_expr_preserve_condition_message(expr_code);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Raise an R exception with a stable gatekeeper prefix.
|
|
91
|
+
void die(const char* m) { Rcpp::stop("galaaz_run_bridge: %s", m); }
|
|
92
|
+
|
|
93
|
+
// Whether debug logging is enabled (GALAAZ_DEBUG != "0").
|
|
94
|
+
bool dbg_on() {
|
|
95
|
+
const char* e = std::getenv("GALAAZ_DEBUG");
|
|
96
|
+
return e && std::string(e) != "0";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Emit debug messages to R console.
|
|
100
|
+
void dbg(const std::string& msg) {
|
|
101
|
+
if (!dbg_on()) return;
|
|
102
|
+
Rcpp::Rcout << "[galaaz_gatekeeper_phase1] " << msg << std::endl;
|
|
103
|
+
Rcpp::Rcout.flush();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Read exactly n bytes from socket; false on EOF/error.
|
|
107
|
+
bool recv_all(int fd, void* buf, size_t n) {
|
|
108
|
+
auto* p = static_cast<char*>(buf);
|
|
109
|
+
size_t g = 0;
|
|
110
|
+
while (g < n) {
|
|
111
|
+
ssize_t r = ::recv(fd, p + g, n - g, 0);
|
|
112
|
+
if (r <= 0) return false;
|
|
113
|
+
g += static_cast<size_t>(r);
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Write exactly n bytes to socket; false on EOF/error.
|
|
119
|
+
bool send_all(int fd, const void* buf, size_t n) {
|
|
120
|
+
auto* p = static_cast<const char*>(buf);
|
|
121
|
+
size_t s = 0;
|
|
122
|
+
while (s < n) {
|
|
123
|
+
ssize_t w = ::send(fd, p + s, n - s, 0);
|
|
124
|
+
if (w <= 0) return false;
|
|
125
|
+
s += static_cast<size_t>(w);
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Minimal MsgPack reader used only by bridge envelopes.
|
|
131
|
+
// Supports maps, strings, nil, basic scalar types, and recursive skip.
|
|
132
|
+
struct Rd {
|
|
133
|
+
const std::vector<uint8_t>& d;
|
|
134
|
+
size_t i;
|
|
135
|
+
explicit Rd(const std::vector<uint8_t>& x) : d(x), i(0) {}
|
|
136
|
+
uint8_t u8() {
|
|
137
|
+
if (i >= d.size()) Rcpp::stop("msgpack truncated");
|
|
138
|
+
return d[i++];
|
|
139
|
+
}
|
|
140
|
+
uint16_t be16() { return (static_cast<uint16_t>(u8()) << 8) | u8(); }
|
|
141
|
+
uint32_t be32() {
|
|
142
|
+
return (static_cast<uint32_t>(u8()) << 24) | (static_cast<uint32_t>(u8()) << 16) |
|
|
143
|
+
(static_cast<uint32_t>(u8()) << 8) | u8();
|
|
144
|
+
}
|
|
145
|
+
std::string str(size_t n) {
|
|
146
|
+
if (i + n > d.size()) Rcpp::stop("msgpack str truncated");
|
|
147
|
+
std::string s(reinterpret_cast<const char*>(d.data() + i), n);
|
|
148
|
+
i += n;
|
|
149
|
+
return s;
|
|
150
|
+
}
|
|
151
|
+
// Decode a MsgPack string given its already-read type tag.
|
|
152
|
+
//
|
|
153
|
+
// Tags used here:
|
|
154
|
+
// - 0xa0..0xbf : fixstr (length embedded in low 5 bits)
|
|
155
|
+
// - 0xd9 : str8 (next 1 byte is length)
|
|
156
|
+
// - 0xda : str16 (next 2 bytes are length, big-endian)
|
|
157
|
+
// - 0xdb : str32 (next 4 bytes are length, big-endian)
|
|
158
|
+
//
|
|
159
|
+
// We branch directly on tag values instead of a generic decoder because the
|
|
160
|
+
// bridge protocol only needs these string forms; this keeps parsing compact.
|
|
161
|
+
std::string read_str_tag(uint8_t c) {
|
|
162
|
+
if (c >= 0xa0 && c <= 0xbf) return str(c & 0x1f);
|
|
163
|
+
if (c == 0xd9) return str(u8());
|
|
164
|
+
if (c == 0xda) return str(be16());
|
|
165
|
+
if (c == 0xdb) return str(be32());
|
|
166
|
+
Rcpp::stop("msgpack: expected string");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Skip exactly one MsgPack value (whatever its type), advancing cursor `i`.
|
|
170
|
+
//
|
|
171
|
+
// Why this exists:
|
|
172
|
+
// - Our envelope parser only needs a subset of fields and scalar forms.
|
|
173
|
+
// - Unknown keys/complex payloads should still be consumed safely.
|
|
174
|
+
// - Skipping by wire format is cheaper than fully decoding to R objects.
|
|
175
|
+
//
|
|
176
|
+
// MsgPack tag groups used below (hex):
|
|
177
|
+
// c0=nil, c2=false, c3=true
|
|
178
|
+
// 00..7f=positive fixint, e0..ff=negative fixint
|
|
179
|
+
// a0..bf=fixstr, d9=str8, da=str16, db=str32
|
|
180
|
+
// cc/cd/ce/cf=uint8/16/32/64, d0/d1/d2/d3=int8/16/32/64
|
|
181
|
+
// cb=float64
|
|
182
|
+
// 90..9f=fixarray, dc=array16, dd=array32
|
|
183
|
+
// 80..8f=fixmap, de=map16, df=map32
|
|
184
|
+
//
|
|
185
|
+
// The order intentionally checks tiny fixed-size tags first (fast common
|
|
186
|
+
// path), then length-prefixed scalars, then recursive container types.
|
|
187
|
+
void skip_one() {
|
|
188
|
+
uint8_t c = u8();
|
|
189
|
+
// nil / bool consume only the tag byte.
|
|
190
|
+
if (c == 0xc0 || c == 0xc2 || c == 0xc3) return;
|
|
191
|
+
// positive fixint: value encoded in tag itself.
|
|
192
|
+
if (c >= 0x00 && c <= 0x7f) return;
|
|
193
|
+
// negative fixint: value encoded in tag itself.
|
|
194
|
+
if (c >= 0xe0) return;
|
|
195
|
+
// fixstr: low 5 bits carry payload length.
|
|
196
|
+
if (c >= 0xa0 && c <= 0xbf) {
|
|
197
|
+
i += c & 0x1f;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// str8: next byte is string length.
|
|
201
|
+
if (c == 0xd9) {
|
|
202
|
+
i += u8();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// str16: next 2 bytes are length.
|
|
206
|
+
if (c == 0xda) {
|
|
207
|
+
i += be16();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// str32: next 4 bytes are length.
|
|
211
|
+
if (c == 0xdb) {
|
|
212
|
+
i += be32();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// uint8 / int8: one payload byte.
|
|
216
|
+
if (c == 0xcc || c == 0xd0) {
|
|
217
|
+
u8();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// uint16 / int16: two payload bytes.
|
|
221
|
+
if (c == 0xcd || c == 0xd1) {
|
|
222
|
+
be16();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// uint32 / int32: four payload bytes.
|
|
226
|
+
if (c == 0xce || c == 0xd2) {
|
|
227
|
+
be32();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
// uint64 / int64 / float64: eight payload bytes.
|
|
231
|
+
if (c == 0xcf || c == 0xd3 || c == 0xcb) {
|
|
232
|
+
i += 8;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// fixarray: low 4 bits carry element count, then recursively skip elements.
|
|
236
|
+
if (c >= 0x90 && c <= 0x9f) {
|
|
237
|
+
size_t n = c & 0x0f;
|
|
238
|
+
for (size_t k = 0; k < n; k++) skip_one();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// array16: next 2 bytes carry element count.
|
|
242
|
+
if (c == 0xdc) {
|
|
243
|
+
size_t n = be16();
|
|
244
|
+
for (size_t k = 0; k < n; k++) skip_one();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// array32: next 4 bytes carry element count.
|
|
248
|
+
if (c == 0xdd) {
|
|
249
|
+
size_t n = be32();
|
|
250
|
+
for (size_t k = 0; k < n; k++) skip_one();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// fixmap: low 4 bits carry pair count; skip key then value for each pair.
|
|
254
|
+
if (c >= 0x80 && c <= 0x8f) {
|
|
255
|
+
size_t n = c & 0x0f;
|
|
256
|
+
for (size_t k = 0; k < n; k++) {
|
|
257
|
+
skip_one();
|
|
258
|
+
skip_one();
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// map16: next 2 bytes carry pair count.
|
|
263
|
+
if (c == 0xde) {
|
|
264
|
+
size_t n = be16();
|
|
265
|
+
for (size_t k = 0; k < n; k++) {
|
|
266
|
+
skip_one();
|
|
267
|
+
skip_one();
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// map32: next 4 bytes carry pair count.
|
|
272
|
+
if (c == 0xdf) {
|
|
273
|
+
size_t n = be32();
|
|
274
|
+
for (size_t k = 0; k < n; k++) {
|
|
275
|
+
skip_one();
|
|
276
|
+
skip_one();
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
Rcpp::stop("msgpack skip unsupported 0x%02x", c);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Parse envelope map into:
|
|
284
|
+
// f[key] -> string-encoded scalar value (or empty for skipped complex)
|
|
285
|
+
// nils[key] -> true if MsgPack value is nil
|
|
286
|
+
// Keys must be strings.
|
|
287
|
+
//
|
|
288
|
+
// This decoder is intentionally selective:
|
|
289
|
+
// - It eagerly decodes only scalar tags we currently use in envelopes.
|
|
290
|
+
// - It skips unsupported/complex values with skip_one(), preserving stream
|
|
291
|
+
// alignment without paying object-construction cost.
|
|
292
|
+
// - Storing scalar values as strings keeps downstream dispatch simple.
|
|
293
|
+
void parse_envelope(std::map<std::string, std::string>& f, std::map<std::string, bool>& nils) {
|
|
294
|
+
uint8_t c = u8();
|
|
295
|
+
size_t n = 0;
|
|
296
|
+
// Envelope root must be a map: fixmap/map16/map32.
|
|
297
|
+
if (c >= 0x80 && c <= 0x8f)
|
|
298
|
+
n = c & 0x0f;
|
|
299
|
+
else if (c == 0xde)
|
|
300
|
+
n = be16();
|
|
301
|
+
else if (c == 0xdf)
|
|
302
|
+
n = be32();
|
|
303
|
+
else
|
|
304
|
+
Rcpp::stop("msgpack: not a map");
|
|
305
|
+
|
|
306
|
+
for (size_t j = 0; j < n; j++) {
|
|
307
|
+
uint8_t kt = u8();
|
|
308
|
+
std::string key = read_str_tag(kt);
|
|
309
|
+
if (i >= d.size()) Rcpp::stop("msgpack: missing val");
|
|
310
|
+
uint8_t vt = d[i];
|
|
311
|
+
// nil
|
|
312
|
+
if (vt == 0xc0) {
|
|
313
|
+
u8();
|
|
314
|
+
nils[key] = true;
|
|
315
|
+
// string tags (fixstr/str8/str16/str32)
|
|
316
|
+
} else if (vt >= 0xa0 && vt <= 0xbf || vt == 0xd9 || vt == 0xda || vt == 0xdb) {
|
|
317
|
+
f[key] = read_str_tag(u8());
|
|
318
|
+
// positive fixint
|
|
319
|
+
} else if (vt >= 0x00 && vt <= 0x7f) {
|
|
320
|
+
f[key] = std::to_string(static_cast<int>(u8()));
|
|
321
|
+
// negative fixint
|
|
322
|
+
} else if (vt >= 0xe0) {
|
|
323
|
+
f[key] = std::to_string(static_cast<int>(static_cast<int8_t>(u8())));
|
|
324
|
+
// uint8
|
|
325
|
+
} else if (vt == 0xcc) {
|
|
326
|
+
u8();
|
|
327
|
+
f[key] = std::to_string(static_cast<unsigned>(u8()));
|
|
328
|
+
// uint32
|
|
329
|
+
} else if (vt == 0xce) {
|
|
330
|
+
u8();
|
|
331
|
+
uint32_t x = be32();
|
|
332
|
+
f[key] = std::to_string(x);
|
|
333
|
+
// int32
|
|
334
|
+
} else if (vt == 0xd2) {
|
|
335
|
+
u8();
|
|
336
|
+
int32_t x = static_cast<int32_t>((static_cast<uint32_t>(u8()) << 24) | (static_cast<uint32_t>(u8()) << 16) |
|
|
337
|
+
(static_cast<uint32_t>(u8()) << 8) | u8());
|
|
338
|
+
f[key] = std::to_string(x);
|
|
339
|
+
// float64
|
|
340
|
+
} else if (vt == 0xcb) {
|
|
341
|
+
u8();
|
|
342
|
+
double v;
|
|
343
|
+
std::memcpy(&v, d.data() + i, 8);
|
|
344
|
+
i += 8;
|
|
345
|
+
f[key] = std::to_string(v);
|
|
346
|
+
// true
|
|
347
|
+
} else if (vt == 0xc3) {
|
|
348
|
+
u8();
|
|
349
|
+
f[key] = "TRUE";
|
|
350
|
+
// false
|
|
351
|
+
} else if (vt == 0xc2) {
|
|
352
|
+
u8();
|
|
353
|
+
f[key] = "FALSE";
|
|
354
|
+
} else {
|
|
355
|
+
// Unknown/complex value: skip bytes so next key-value pair is aligned.
|
|
356
|
+
skip_one();
|
|
357
|
+
f[key] = "";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// MsgPack pack helpers for the specific envelope shapes we emit.
|
|
364
|
+
// They intentionally support only what the bridge writes on the wire.
|
|
365
|
+
void pack_str(std::vector<uint8_t>& o, const std::string& s) {
|
|
366
|
+
size_t n = s.size();
|
|
367
|
+
// Choose the smallest legal string encoding:
|
|
368
|
+
// - fixstr (< 32 bytes) = 1-byte header
|
|
369
|
+
// - str8 (< 256) = 2-byte header
|
|
370
|
+
// - str16 (otherwise) = 3-byte header
|
|
371
|
+
//
|
|
372
|
+
// This keeps envelope size small and is faster than always using str32.
|
|
373
|
+
if (n < 32)
|
|
374
|
+
o.push_back(static_cast<uint8_t>(0xa0 | n));
|
|
375
|
+
else if (n < 256) {
|
|
376
|
+
o.push_back(0xd9);
|
|
377
|
+
o.push_back(static_cast<uint8_t>(n));
|
|
378
|
+
} else {
|
|
379
|
+
o.push_back(0xda);
|
|
380
|
+
o.push_back(static_cast<uint8_t>((n >> 8) & 0xff));
|
|
381
|
+
o.push_back(static_cast<uint8_t>(n & 0xff));
|
|
382
|
+
}
|
|
383
|
+
o.insert(o.end(), s.begin(), s.end());
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
void pack_nil(std::vector<uint8_t>& o) { o.push_back(0xc0); }
|
|
387
|
+
void pack_true(std::vector<uint8_t>& o) { o.push_back(0xc3); }
|
|
388
|
+
void pack_false(std::vector<uint8_t>& o) { o.push_back(0xc2); }
|
|
389
|
+
void pack_bool(std::vector<uint8_t>& o, bool v) { v ? pack_true(o) : pack_false(o); }
|
|
390
|
+
void pack_int32(std::vector<uint8_t>& o, int32_t x) {
|
|
391
|
+
o.push_back(0xd2);
|
|
392
|
+
o.push_back(static_cast<uint8_t>((x >> 24) & 0xff));
|
|
393
|
+
o.push_back(static_cast<uint8_t>((x >> 16) & 0xff));
|
|
394
|
+
o.push_back(static_cast<uint8_t>((x >> 8) & 0xff));
|
|
395
|
+
o.push_back(static_cast<uint8_t>(x & 0xff));
|
|
396
|
+
}
|
|
397
|
+
void pack_double64(std::vector<uint8_t>& o, double v) {
|
|
398
|
+
o.push_back(0xcb);
|
|
399
|
+
uint64_t bits = 0;
|
|
400
|
+
std::memcpy(&bits, &v, 8);
|
|
401
|
+
o.push_back(static_cast<uint8_t>((bits >> 56) & 0xff));
|
|
402
|
+
o.push_back(static_cast<uint8_t>((bits >> 48) & 0xff));
|
|
403
|
+
o.push_back(static_cast<uint8_t>((bits >> 40) & 0xff));
|
|
404
|
+
o.push_back(static_cast<uint8_t>((bits >> 32) & 0xff));
|
|
405
|
+
o.push_back(static_cast<uint8_t>((bits >> 24) & 0xff));
|
|
406
|
+
o.push_back(static_cast<uint8_t>((bits >> 16) & 0xff));
|
|
407
|
+
o.push_back(static_cast<uint8_t>((bits >> 8) & 0xff));
|
|
408
|
+
o.push_back(static_cast<uint8_t>(bits & 0xff));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
void pack_map5(std::vector<uint8_t>& o) { o.push_back(static_cast<uint8_t>(0x80 | 5)); }
|
|
412
|
+
|
|
413
|
+
void pack_map6(std::vector<uint8_t>& o) { o.push_back(static_cast<uint8_t>(0x80 | 6)); }
|
|
414
|
+
|
|
415
|
+
void pack_map4(std::vector<uint8_t>& o) { o.push_back(static_cast<uint8_t>(0x80 | 4)); }
|
|
416
|
+
void pack_map3(std::vector<uint8_t>& o) { o.push_back(static_cast<uint8_t>(0x80 | 3)); }
|
|
417
|
+
void pack_map2(std::vector<uint8_t>& o) { o.push_back(static_cast<uint8_t>(0x80 | 2)); }
|
|
418
|
+
|
|
419
|
+
// Build a CALL envelope (R -> Ruby callback request).
|
|
420
|
+
//
|
|
421
|
+
// Wire shape is intentionally fixed and tiny:
|
|
422
|
+
// {call_id, type="CALL", payload, instance_id}
|
|
423
|
+
//
|
|
424
|
+
// Why fixed-order explicit writes (instead of generic map builder):
|
|
425
|
+
// - lower allocation overhead in hot callback paths,
|
|
426
|
+
// - no dynamic reflection/branching for field selection,
|
|
427
|
+
// - easier protocol auditing because bytes are deterministic.
|
|
428
|
+
std::vector<uint8_t> make_call(const std::string& call_id, const std::string& payload,
|
|
429
|
+
const std::string& instance_id) {
|
|
430
|
+
std::vector<uint8_t> o;
|
|
431
|
+
pack_map4(o);
|
|
432
|
+
pack_str(o, "call_id");
|
|
433
|
+
pack_str(o, call_id);
|
|
434
|
+
pack_str(o, "type");
|
|
435
|
+
pack_str(o, "CALL");
|
|
436
|
+
pack_str(o, "payload");
|
|
437
|
+
pack_str(o, payload);
|
|
438
|
+
pack_str(o, "instance_id");
|
|
439
|
+
pack_str(o, instance_id);
|
|
440
|
+
return o;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Build a RET envelope (R -> Ruby response).
|
|
444
|
+
//
|
|
445
|
+
// Wire shape:
|
|
446
|
+
// {call_id, type="RET", status, payload, instance_id, parent_id=nil}
|
|
447
|
+
//
|
|
448
|
+
// We always include parent_id (currently nil) to keep envelope shape stable
|
|
449
|
+
// across phases; this avoids Ruby-side "missing key" branches and makes logs
|
|
450
|
+
// easier to compare between callback/non-callback flows.
|
|
451
|
+
std::vector<uint8_t> make_ret(const std::string& call_id, const std::string& status,
|
|
452
|
+
const std::vector<uint8_t>& payload_bytes, const std::string& instance_id) {
|
|
453
|
+
std::vector<uint8_t> o;
|
|
454
|
+
pack_map6(o);
|
|
455
|
+
pack_str(o, "call_id");
|
|
456
|
+
pack_str(o, call_id);
|
|
457
|
+
pack_str(o, "type");
|
|
458
|
+
pack_str(o, "RET");
|
|
459
|
+
pack_str(o, "status");
|
|
460
|
+
pack_str(o, status);
|
|
461
|
+
pack_str(o, "payload");
|
|
462
|
+
o.insert(o.end(), payload_bytes.begin(), payload_bytes.end());
|
|
463
|
+
pack_str(o, "instance_id");
|
|
464
|
+
pack_str(o, instance_id);
|
|
465
|
+
pack_str(o, "parent_id");
|
|
466
|
+
pack_nil(o);
|
|
467
|
+
return o;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
std::vector<uint8_t> payload_error(const std::string& msg) {
|
|
471
|
+
std::vector<uint8_t> p;
|
|
472
|
+
pack_map2(p);
|
|
473
|
+
pack_str(p, "kind");
|
|
474
|
+
pack_str(p, "error");
|
|
475
|
+
pack_str(p, "message");
|
|
476
|
+
pack_str(p, msg);
|
|
477
|
+
return p;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
struct EvalResult {
|
|
481
|
+
bool ok;
|
|
482
|
+
std::vector<uint8_t> payload;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
std::vector<uint8_t> payload_unbox_walk(const std::string& status, int nodes, int max_depth) {
|
|
486
|
+
std::vector<uint8_t> p;
|
|
487
|
+
// { kind, status, nodes, max_depth }
|
|
488
|
+
p.push_back(static_cast<uint8_t>(0x80 | 4));
|
|
489
|
+
pack_str(p, "kind");
|
|
490
|
+
pack_str(p, "unbox_walk");
|
|
491
|
+
pack_str(p, "status");
|
|
492
|
+
pack_str(p, status);
|
|
493
|
+
pack_str(p, "nodes");
|
|
494
|
+
pack_int32(p, nodes);
|
|
495
|
+
pack_str(p, "max_depth");
|
|
496
|
+
pack_int32(p, max_depth);
|
|
497
|
+
return p;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
void pack_array_header(std::vector<uint8_t>& o, uint32_t n) {
|
|
501
|
+
if (n < 16) {
|
|
502
|
+
o.push_back(static_cast<uint8_t>(0x90 | n));
|
|
503
|
+
} else if (n <= 0xffff) {
|
|
504
|
+
o.push_back(0xdc);
|
|
505
|
+
o.push_back(static_cast<uint8_t>((n >> 8) & 0xff));
|
|
506
|
+
o.push_back(static_cast<uint8_t>(n & 0xff));
|
|
507
|
+
} else {
|
|
508
|
+
o.push_back(0xdd);
|
|
509
|
+
o.push_back(static_cast<uint8_t>((n >> 24) & 0xff));
|
|
510
|
+
o.push_back(static_cast<uint8_t>((n >> 16) & 0xff));
|
|
511
|
+
o.push_back(static_cast<uint8_t>((n >> 8) & 0xff));
|
|
512
|
+
o.push_back(static_cast<uint8_t>(n & 0xff));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
std::vector<uint8_t> payload_unbox_materialize(const std::string& status, int nodes, int max_depth,
|
|
517
|
+
const std::vector<uint8_t>& value_bytes) {
|
|
518
|
+
std::vector<uint8_t> p;
|
|
519
|
+
// { kind, status, nodes, max_depth, value }
|
|
520
|
+
p.push_back(static_cast<uint8_t>(0x80 | 5));
|
|
521
|
+
pack_str(p, "kind");
|
|
522
|
+
pack_str(p, "unbox_materialize");
|
|
523
|
+
pack_str(p, "status");
|
|
524
|
+
pack_str(p, status);
|
|
525
|
+
pack_str(p, "nodes");
|
|
526
|
+
pack_int32(p, nodes);
|
|
527
|
+
pack_str(p, "max_depth");
|
|
528
|
+
pack_int32(p, max_depth);
|
|
529
|
+
pack_str(p, "value");
|
|
530
|
+
p.insert(p.end(), value_bytes.begin(), value_bytes.end());
|
|
531
|
+
return p;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
bool starts_with(const std::string& s, const std::string& prefix) {
|
|
535
|
+
return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
std::string trim_copy(const std::string& s) {
|
|
539
|
+
size_t b = 0;
|
|
540
|
+
while (b < s.size() && (s[b] == ' ' || s[b] == '\t' || s[b] == '\n' || s[b] == '\r')) b++;
|
|
541
|
+
size_t e = s.size();
|
|
542
|
+
while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\n' || s[e - 1] == '\r')) e--;
|
|
543
|
+
return s.substr(b, e - b);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
bool valid_var_name(const std::string& name) {
|
|
547
|
+
if (name.empty()) return false;
|
|
548
|
+
for (size_t i = 0; i < name.size(); ++i) {
|
|
549
|
+
char c = name[i];
|
|
550
|
+
bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
|
551
|
+
if (!ok) return false;
|
|
552
|
+
}
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
std::string class_or_typeof_string(SEXP x) {
|
|
557
|
+
SEXP cls = Rf_getAttrib(x, R_ClassSymbol);
|
|
558
|
+
if (TYPEOF(cls) == STRSXP && Rf_length(cls) > 0) {
|
|
559
|
+
std::string out;
|
|
560
|
+
for (R_xlen_t i = 0; i < XLENGTH(cls); ++i) {
|
|
561
|
+
if (i > 0) out += " ";
|
|
562
|
+
SEXP elt = STRING_ELT(cls, i);
|
|
563
|
+
if (elt == NA_STRING) out += "NA";
|
|
564
|
+
else out += CHAR(elt);
|
|
565
|
+
}
|
|
566
|
+
if (!out.empty()) return out;
|
|
567
|
+
}
|
|
568
|
+
// matrix: atomic vector + dim length 2 (same as is.matrix); ensures r_class is not
|
|
569
|
+
// mis-reported as plain integer/numeric when class() is unavailable to the attrib read.
|
|
570
|
+
{
|
|
571
|
+
SEXP dim = Rf_getAttrib(x, R_DimSymbol);
|
|
572
|
+
if (dim != R_NilValue && TYPEOF(dim) == INTSXP && Rf_xlength(dim) == 2) {
|
|
573
|
+
return "matrix array";
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (Rf_inherits(x, "array")) {
|
|
577
|
+
return "array";
|
|
578
|
+
}
|
|
579
|
+
// Keep legacy class labels expected by Ruby Object.build for atomic vectors.
|
|
580
|
+
switch (TYPEOF(x)) {
|
|
581
|
+
case INTSXP: return "integer";
|
|
582
|
+
case REALSXP: return "numeric";
|
|
583
|
+
case LGLSXP: return "logical";
|
|
584
|
+
case STRSXP: return "character";
|
|
585
|
+
case VECSXP: return "list";
|
|
586
|
+
case CLOSXP: return "function";
|
|
587
|
+
case ENVSXP: return "environment";
|
|
588
|
+
case SYMSXP: return "name";
|
|
589
|
+
case EXPRSXP: return "expression";
|
|
590
|
+
default: return std::string(Rf_type2char(TYPEOF(x)));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Normalized wrapper tag for Ruby R::Object.build (see docs/performance_plan.md).
|
|
595
|
+
// Values: vector, data_frame, matrix, list, closure, environment, language, symbol, other.
|
|
596
|
+
std::string wrapper_tag_for_sexp(SEXP val) {
|
|
597
|
+
if (val == R_NilValue) return "other";
|
|
598
|
+
if (Rf_inherits(val, "data.frame")) return "data_frame";
|
|
599
|
+
SEXP dim = Rf_getAttrib(val, R_DimSymbol);
|
|
600
|
+
if (dim != R_NilValue && TYPEOF(dim) == INTSXP && Rf_xlength(dim) == 2) {
|
|
601
|
+
return "matrix";
|
|
602
|
+
}
|
|
603
|
+
if (Rf_inherits(val, "array")) return "matrix";
|
|
604
|
+
if (Rf_inherits(val, "factor")) return "other";
|
|
605
|
+
|
|
606
|
+
switch (TYPEOF(val)) {
|
|
607
|
+
case VECSXP:
|
|
608
|
+
return "list";
|
|
609
|
+
case CLOSXP:
|
|
610
|
+
case BUILTINSXP:
|
|
611
|
+
case SPECIALSXP:
|
|
612
|
+
return "closure";
|
|
613
|
+
case ENVSXP:
|
|
614
|
+
return "environment";
|
|
615
|
+
case LANGSXP:
|
|
616
|
+
return "language";
|
|
617
|
+
case EXPRSXP:
|
|
618
|
+
return "other";
|
|
619
|
+
case LISTSXP:
|
|
620
|
+
return "list";
|
|
621
|
+
case INTSXP:
|
|
622
|
+
case REALSXP:
|
|
623
|
+
case LGLSXP:
|
|
624
|
+
case STRSXP:
|
|
625
|
+
return "vector";
|
|
626
|
+
case SYMSXP:
|
|
627
|
+
return "symbol";
|
|
628
|
+
default:
|
|
629
|
+
return "other";
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
EvalResult payload_eval_with_result(SEXP val, const std::string& var_name) {
|
|
634
|
+
if (TYPEOF(val) == SYMSXP) {
|
|
635
|
+
std::vector<uint8_t> p;
|
|
636
|
+
pack_map2(p);
|
|
637
|
+
pack_str(p, "type");
|
|
638
|
+
pack_str(p, "scalar_symbol");
|
|
639
|
+
pack_str(p, "value");
|
|
640
|
+
pack_str(p, std::string(CHAR(PRINTNAME(val))));
|
|
641
|
+
return {true, p};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (Rf_length(val) == 1) {
|
|
645
|
+
if (TYPEOF(val) == INTSXP) {
|
|
646
|
+
std::vector<uint8_t> p;
|
|
647
|
+
pack_map2(p);
|
|
648
|
+
pack_str(p, "type");
|
|
649
|
+
pack_str(p, "scalar_integer");
|
|
650
|
+
pack_str(p, "value");
|
|
651
|
+
int x = INTEGER(val)[0];
|
|
652
|
+
if (x == NA_INTEGER) pack_nil(p);
|
|
653
|
+
else pack_int32(p, x);
|
|
654
|
+
return {true, p};
|
|
655
|
+
}
|
|
656
|
+
if (TYPEOF(val) == REALSXP) {
|
|
657
|
+
std::vector<uint8_t> p;
|
|
658
|
+
pack_map2(p);
|
|
659
|
+
pack_str(p, "type");
|
|
660
|
+
pack_str(p, "scalar_double");
|
|
661
|
+
pack_str(p, "value");
|
|
662
|
+
double x = REAL(val)[0];
|
|
663
|
+
if (R_IsNA(x)) pack_nil(p);
|
|
664
|
+
else pack_double64(p, x);
|
|
665
|
+
return {true, p};
|
|
666
|
+
}
|
|
667
|
+
if (TYPEOF(val) == LGLSXP) {
|
|
668
|
+
std::vector<uint8_t> p;
|
|
669
|
+
pack_map2(p);
|
|
670
|
+
pack_str(p, "type");
|
|
671
|
+
pack_str(p, "scalar_logical");
|
|
672
|
+
pack_str(p, "value");
|
|
673
|
+
int x = LOGICAL(val)[0];
|
|
674
|
+
if (x == NA_LOGICAL) pack_nil(p);
|
|
675
|
+
else pack_bool(p, x != 0);
|
|
676
|
+
return {true, p};
|
|
677
|
+
}
|
|
678
|
+
if (TYPEOF(val) == STRSXP) {
|
|
679
|
+
std::vector<uint8_t> p;
|
|
680
|
+
pack_map2(p);
|
|
681
|
+
pack_str(p, "type");
|
|
682
|
+
pack_str(p, "scalar_character");
|
|
683
|
+
pack_str(p, "value");
|
|
684
|
+
if (STRING_ELT(val, 0) == NA_STRING) {
|
|
685
|
+
pack_nil(p);
|
|
686
|
+
} else {
|
|
687
|
+
pack_str(p, std::string(CHAR(STRING_ELT(val, 0))));
|
|
688
|
+
}
|
|
689
|
+
return {true, p};
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
std::vector<uint8_t> p;
|
|
694
|
+
pack_map4(p);
|
|
695
|
+
pack_str(p, "type");
|
|
696
|
+
pack_str(p, "handle");
|
|
697
|
+
pack_str(p, "handle");
|
|
698
|
+
pack_str(p, var_name);
|
|
699
|
+
pack_str(p, "r_class");
|
|
700
|
+
pack_str(p, class_or_typeof_string(val));
|
|
701
|
+
pack_str(p, "wrapper_tag");
|
|
702
|
+
pack_str(p, wrapper_tag_for_sexp(val));
|
|
703
|
+
return {true, p};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
EvalResult eval_with_result_cmd(const std::string& cmd, const Rcpp::Environment& env) {
|
|
707
|
+
const std::string prefix = "__G_EVAL_WITH_RESULT__";
|
|
708
|
+
std::string assignment = trim_copy(cmd.substr(prefix.size()));
|
|
709
|
+
if (assignment.empty()) return {false, payload_error("bad eval_with_result args")};
|
|
710
|
+
|
|
711
|
+
if (starts_with(assignment, ".GlobalEnv$")) {
|
|
712
|
+
assignment = assignment.substr(std::string(".GlobalEnv$").size());
|
|
713
|
+
}
|
|
714
|
+
size_t arrow = assignment.find("<-");
|
|
715
|
+
if (arrow == std::string::npos) return {false, payload_error("bad eval_with_result assignment")};
|
|
716
|
+
|
|
717
|
+
std::string var_name = trim_copy(assignment.substr(0, arrow));
|
|
718
|
+
std::string expr_code = trim_copy(assignment.substr(arrow + 2));
|
|
719
|
+
if (!valid_var_name(var_name) || expr_code.empty()) {
|
|
720
|
+
return {false, payload_error("invalid eval_with_result assignment")};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const std::string to_parse = maybe_wrap_nested_eval_expr(expr_code);
|
|
724
|
+
ParseStatus ps = PARSE_OK;
|
|
725
|
+
SEXP px = R_ParseVector(Rf_mkString(to_parse.c_str()), 1, &ps, R_GlobalEnv);
|
|
726
|
+
if (ps != PARSE_OK) return {false, payload_error("parse error")};
|
|
727
|
+
SEXP expr = VECTOR_ELT(px, 0);
|
|
728
|
+
|
|
729
|
+
int err = 0;
|
|
730
|
+
SEXP val = R_tryEval(expr, env, &err);
|
|
731
|
+
if (err) {
|
|
732
|
+
std::string msg = "evaluation error";
|
|
733
|
+
try {
|
|
734
|
+
Rcpp::Function geterr("geterrmessage");
|
|
735
|
+
SEXP m = geterr();
|
|
736
|
+
if (Rf_isString(m) && Rf_length(m) >= 1) {
|
|
737
|
+
msg = Rcpp::as<std::string>(m);
|
|
738
|
+
}
|
|
739
|
+
} catch (...) {
|
|
740
|
+
}
|
|
741
|
+
return {false, payload_error(msg)};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
Rf_defineVar(Rf_install(var_name.c_str()), val, env);
|
|
745
|
+
return payload_eval_with_result(val, var_name);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
static void split_rs_records(const std::string& body, std::vector<std::string>& out) {
|
|
749
|
+
out.clear();
|
|
750
|
+
size_t start = 0;
|
|
751
|
+
while (start <= body.size()) {
|
|
752
|
+
size_t pos = body.find(static_cast<char>(0x1e), start);
|
|
753
|
+
if (pos == std::string::npos) {
|
|
754
|
+
out.push_back(body.substr(start));
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
out.push_back(body.substr(start, pos - start));
|
|
758
|
+
start = pos + 1;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
static std::string r_latest_error_message() {
|
|
763
|
+
try {
|
|
764
|
+
Rcpp::Function geterr("geterrmessage");
|
|
765
|
+
SEXP m = geterr();
|
|
766
|
+
if (Rf_isString(m) && Rf_length(m) >= 1) {
|
|
767
|
+
SEXP elt = STRING_ELT(m, 0);
|
|
768
|
+
if (elt != NA_STRING) {
|
|
769
|
+
const char* s = CHAR(elt);
|
|
770
|
+
if (s) return std::string(s);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
} catch (...) {
|
|
774
|
+
}
|
|
775
|
+
return "evaluation error";
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
std::vector<uint8_t> payload_batch_eval_success(const std::vector<std::vector<uint8_t>>& items) {
|
|
779
|
+
std::vector<uint8_t> p;
|
|
780
|
+
pack_map2(p);
|
|
781
|
+
pack_str(p, "kind");
|
|
782
|
+
pack_str(p, "batch_eval");
|
|
783
|
+
pack_str(p, "results");
|
|
784
|
+
pack_array_header(p, static_cast<uint32_t>(items.size()));
|
|
785
|
+
for (const auto& it : items) {
|
|
786
|
+
p.insert(p.end(), it.begin(), it.end());
|
|
787
|
+
}
|
|
788
|
+
return p;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
std::vector<uint8_t> payload_batch_error(int32_t idx, const std::string& msg) {
|
|
792
|
+
std::vector<uint8_t> p;
|
|
793
|
+
pack_map3(p);
|
|
794
|
+
pack_str(p, "kind");
|
|
795
|
+
pack_str(p, "batch_error");
|
|
796
|
+
pack_str(p, "index");
|
|
797
|
+
pack_int32(p, idx);
|
|
798
|
+
pack_str(p, "message");
|
|
799
|
+
pack_str(p, msg);
|
|
800
|
+
return p;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
EvalResult eval_batch_eval_with_result_cmd(const std::string& cmd, const Rcpp::Environment& env) {
|
|
804
|
+
const std::string prefix = "__G_BATCH_EVAL_WITH_RESULT__";
|
|
805
|
+
if (!starts_with(cmd, prefix)) return {false, payload_error("bad batch eval prefix")};
|
|
806
|
+
std::string body = trim_copy(cmd.substr(prefix.size()));
|
|
807
|
+
std::vector<std::string> ops;
|
|
808
|
+
split_rs_records(body, ops);
|
|
809
|
+
if (ops.empty()) return {false, payload_error("empty batch")};
|
|
810
|
+
const size_t kMaxBatch = 256;
|
|
811
|
+
if (ops.size() > kMaxBatch) return {false, payload_error("batch too large (max 256)")};
|
|
812
|
+
|
|
813
|
+
std::vector<std::vector<uint8_t>> ok_items;
|
|
814
|
+
ok_items.reserve(ops.size());
|
|
815
|
+
for (size_t i = 0; i < ops.size(); ++i) {
|
|
816
|
+
std::string part = trim_copy(ops[i]);
|
|
817
|
+
if (part.empty()) {
|
|
818
|
+
return {true, payload_batch_error(static_cast<int32_t>(i), "empty batch segment")};
|
|
819
|
+
}
|
|
820
|
+
std::string full = std::string("__G_EVAL_WITH_RESULT__") + part;
|
|
821
|
+
EvalResult er = eval_with_result_cmd(full, env);
|
|
822
|
+
if (!er.ok) {
|
|
823
|
+
return {true, payload_batch_error(static_cast<int32_t>(i), r_latest_error_message())};
|
|
824
|
+
}
|
|
825
|
+
ok_items.push_back(std::move(er.payload));
|
|
826
|
+
}
|
|
827
|
+
return {true, payload_batch_eval_success(ok_items)};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
EvalResult eval_unbox_walk_cmd(const std::string& cmd, const Rcpp::Environment& env) {
|
|
831
|
+
// Format: __G_UNBOX_WALK__|<handle>|<max_depth>|<max_nodes>
|
|
832
|
+
std::vector<std::string> parts;
|
|
833
|
+
size_t start = 0;
|
|
834
|
+
while (start <= cmd.size()) {
|
|
835
|
+
size_t pos = cmd.find('|', start);
|
|
836
|
+
if (pos == std::string::npos) pos = cmd.size();
|
|
837
|
+
parts.push_back(cmd.substr(start, pos - start));
|
|
838
|
+
start = pos + 1;
|
|
839
|
+
if (pos == cmd.size()) break;
|
|
840
|
+
}
|
|
841
|
+
if (parts.size() != 4) return {false, payload_error("bad unbox_walk args")};
|
|
842
|
+
|
|
843
|
+
std::string handle = parts[1];
|
|
844
|
+
int max_depth = std::atoi(parts[2].c_str());
|
|
845
|
+
int max_nodes = std::atoi(parts[3].c_str());
|
|
846
|
+
if (handle.empty() || max_depth <= 0 || max_nodes <= 0) {
|
|
847
|
+
return {false, payload_error("invalid unbox_walk params")};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
SEXP sym = Rf_install(handle.c_str());
|
|
851
|
+
SEXP root = Rf_findVar(sym, env);
|
|
852
|
+
if (root == R_UnboundValue) return {false, payload_error("unbox_walk unknown handle")};
|
|
853
|
+
|
|
854
|
+
std::vector<std::pair<SEXP, int>> stack;
|
|
855
|
+
stack.reserve(1024);
|
|
856
|
+
stack.push_back({root, 1});
|
|
857
|
+
|
|
858
|
+
int nodes = 0;
|
|
859
|
+
int maxd = 0;
|
|
860
|
+
std::string status = "OK";
|
|
861
|
+
|
|
862
|
+
while (!stack.empty()) {
|
|
863
|
+
auto cur = stack.back();
|
|
864
|
+
stack.pop_back();
|
|
865
|
+
SEXP x = cur.first;
|
|
866
|
+
int d = cur.second;
|
|
867
|
+
|
|
868
|
+
nodes += 1;
|
|
869
|
+
if (d > maxd) maxd = d;
|
|
870
|
+
if (nodes > max_nodes) { status = "NODE"; break; }
|
|
871
|
+
if (d > max_depth) { status = "DEPTH"; break; }
|
|
872
|
+
|
|
873
|
+
if (x == R_NilValue) continue;
|
|
874
|
+
if (TYPEOF(x) == VECSXP) {
|
|
875
|
+
R_xlen_t n = XLENGTH(x);
|
|
876
|
+
for (R_xlen_t i = n; i > 0; --i) {
|
|
877
|
+
SEXP child = VECTOR_ELT(x, i - 1);
|
|
878
|
+
stack.push_back({child, d + 1});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return {true, payload_unbox_walk(status, nodes, maxd)};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
bool materialize_value(SEXP x, int depth, int max_depth, int max_nodes, int& nodes, int& maxd,
|
|
887
|
+
std::vector<uint8_t>& out, std::string& status) {
|
|
888
|
+
nodes += 1;
|
|
889
|
+
if (depth > maxd) maxd = depth;
|
|
890
|
+
if (nodes > max_nodes) {
|
|
891
|
+
status = "NODE";
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
if (depth > max_depth) {
|
|
895
|
+
status = "DEPTH";
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (x == R_NilValue) {
|
|
900
|
+
pack_nil(out);
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
switch (TYPEOF(x)) {
|
|
905
|
+
case VECSXP: {
|
|
906
|
+
R_xlen_t n = XLENGTH(x);
|
|
907
|
+
pack_array_header(out, static_cast<uint32_t>(n));
|
|
908
|
+
for (R_xlen_t i = 0; i < n; ++i) {
|
|
909
|
+
if (!materialize_value(VECTOR_ELT(x, i), depth + 1, max_depth, max_nodes, nodes, maxd, out, status)) {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
case INTSXP: {
|
|
916
|
+
R_xlen_t n = XLENGTH(x);
|
|
917
|
+
if (n == 1) {
|
|
918
|
+
int v = INTEGER(x)[0];
|
|
919
|
+
if (v == NA_INTEGER) pack_nil(out);
|
|
920
|
+
else pack_int32(out, v);
|
|
921
|
+
} else {
|
|
922
|
+
pack_array_header(out, static_cast<uint32_t>(n));
|
|
923
|
+
for (R_xlen_t i = 0; i < n; ++i) {
|
|
924
|
+
int v = INTEGER(x)[i];
|
|
925
|
+
if (v == NA_INTEGER) pack_nil(out);
|
|
926
|
+
else pack_int32(out, v);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
case REALSXP: {
|
|
932
|
+
R_xlen_t n = XLENGTH(x);
|
|
933
|
+
if (n == 1) {
|
|
934
|
+
double v = REAL(x)[0];
|
|
935
|
+
if (R_IsNA(v)) pack_nil(out);
|
|
936
|
+
else pack_double64(out, v);
|
|
937
|
+
} else {
|
|
938
|
+
pack_array_header(out, static_cast<uint32_t>(n));
|
|
939
|
+
for (R_xlen_t i = 0; i < n; ++i) {
|
|
940
|
+
double v = REAL(x)[i];
|
|
941
|
+
if (R_IsNA(v)) pack_nil(out);
|
|
942
|
+
else pack_double64(out, v);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return true;
|
|
946
|
+
}
|
|
947
|
+
case LGLSXP: {
|
|
948
|
+
R_xlen_t n = XLENGTH(x);
|
|
949
|
+
if (n == 1) {
|
|
950
|
+
int v = LOGICAL(x)[0];
|
|
951
|
+
if (v == NA_LOGICAL) pack_nil(out);
|
|
952
|
+
else pack_bool(out, v != 0);
|
|
953
|
+
} else {
|
|
954
|
+
pack_array_header(out, static_cast<uint32_t>(n));
|
|
955
|
+
for (R_xlen_t i = 0; i < n; ++i) {
|
|
956
|
+
int v = LOGICAL(x)[i];
|
|
957
|
+
if (v == NA_LOGICAL) pack_nil(out);
|
|
958
|
+
else pack_bool(out, v != 0);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
case STRSXP: {
|
|
964
|
+
R_xlen_t n = XLENGTH(x);
|
|
965
|
+
if (n == 1) {
|
|
966
|
+
if (STRING_ELT(x, 0) == NA_STRING) pack_nil(out);
|
|
967
|
+
else pack_str(out, std::string(CHAR(STRING_ELT(x, 0))));
|
|
968
|
+
} else {
|
|
969
|
+
pack_array_header(out, static_cast<uint32_t>(n));
|
|
970
|
+
for (R_xlen_t i = 0; i < n; ++i) {
|
|
971
|
+
if (STRING_ELT(x, i) == NA_STRING) pack_nil(out);
|
|
972
|
+
else pack_str(out, std::string(CHAR(STRING_ELT(x, i))));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
default:
|
|
978
|
+
status = "UNSUPPORTED";
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
EvalResult eval_unbox_materialize_cmd(const std::string& cmd, const Rcpp::Environment& env) {
|
|
984
|
+
// Format: __G_UNBOX_MATERIALIZE__|<handle>|<max_depth>|<max_nodes>
|
|
985
|
+
std::vector<std::string> parts;
|
|
986
|
+
size_t start = 0;
|
|
987
|
+
while (start <= cmd.size()) {
|
|
988
|
+
size_t pos = cmd.find('|', start);
|
|
989
|
+
if (pos == std::string::npos) pos = cmd.size();
|
|
990
|
+
parts.push_back(cmd.substr(start, pos - start));
|
|
991
|
+
start = pos + 1;
|
|
992
|
+
if (pos == cmd.size()) break;
|
|
993
|
+
}
|
|
994
|
+
if (parts.size() != 4) return {false, payload_error("bad unbox_materialize args")};
|
|
995
|
+
|
|
996
|
+
std::string handle = parts[1];
|
|
997
|
+
int max_depth = std::atoi(parts[2].c_str());
|
|
998
|
+
int max_nodes = std::atoi(parts[3].c_str());
|
|
999
|
+
if (handle.empty() || max_depth <= 0 || max_nodes <= 0) {
|
|
1000
|
+
return {false, payload_error("invalid unbox_materialize params")};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
SEXP sym = Rf_install(handle.c_str());
|
|
1004
|
+
SEXP root = Rf_findVar(sym, env);
|
|
1005
|
+
if (root == R_UnboundValue) return {false, payload_error("unbox_materialize unknown handle")};
|
|
1006
|
+
|
|
1007
|
+
std::vector<uint8_t> value_bytes;
|
|
1008
|
+
int nodes = 0;
|
|
1009
|
+
int maxd = 0;
|
|
1010
|
+
std::string status = "OK";
|
|
1011
|
+
bool ok = materialize_value(root, 1, max_depth, max_nodes, nodes, maxd, value_bytes, status);
|
|
1012
|
+
if (!ok && status != "DEPTH" && status != "NODE" && status != "UNSUPPORTED") {
|
|
1013
|
+
status = "UNSUPPORTED";
|
|
1014
|
+
}
|
|
1015
|
+
// IMPORTANT: if traversal aborts (DEPTH/NODE/UNSUPPORTED), the recursive
|
|
1016
|
+
// encoder may have emitted only a prefix of a composite value (e.g. array
|
|
1017
|
+
// header without all declared elements). Returning that partial MsgPack blob
|
|
1018
|
+
// would make the outer RET payload undecodable on Ruby side and appear as a
|
|
1019
|
+
// broken connection. For non-OK statuses, force `value` to MsgPack nil so
|
|
1020
|
+
// envelope decoding always stays valid and status carries the reason.
|
|
1021
|
+
if (!ok) {
|
|
1022
|
+
value_bytes.clear();
|
|
1023
|
+
pack_nil(value_bytes);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return {true, payload_unbox_materialize(status, nodes, maxd, value_bytes)};
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
static bool valid_bridge_handle_token(const std::string& h) {
|
|
1030
|
+
if (h.size() < 5 || h.compare(0, 4, "g2_v") != 0) return false;
|
|
1031
|
+
for (size_t i = 4; i < h.size(); ++i) {
|
|
1032
|
+
if (h[i] < '0' || h[i] > '9') return false;
|
|
1033
|
+
}
|
|
1034
|
+
return true;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
static bool valid_r_method_name_token(const std::string& n) {
|
|
1038
|
+
if (n.empty()) return false;
|
|
1039
|
+
for (size_t i = 0; i < n.size(); ++i) {
|
|
1040
|
+
char c = n[i];
|
|
1041
|
+
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_')) {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return true;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// MsgPack: { kind:"dispatch_probe", is_field:bool, is_func:bool }
|
|
1049
|
+
// Mirrors the two Ruby-side probes in R::Support.process_missing_dispatch in one R eval.
|
|
1050
|
+
std::vector<uint8_t> payload_dispatch_probe(bool is_field, bool is_func) {
|
|
1051
|
+
std::vector<uint8_t> p;
|
|
1052
|
+
pack_map3(p);
|
|
1053
|
+
pack_str(p, "kind");
|
|
1054
|
+
pack_str(p, "dispatch_probe");
|
|
1055
|
+
pack_str(p, "is_field");
|
|
1056
|
+
pack_bool(p, is_field);
|
|
1057
|
+
pack_str(p, "is_func");
|
|
1058
|
+
pack_bool(p, is_func);
|
|
1059
|
+
return p;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
EvalResult eval_dispatch_probe_cmd(const std::string& cmd, const Rcpp::Environment& env) {
|
|
1063
|
+
// Format: __G_DISPATCH_PROBE__|<handle>|<name>
|
|
1064
|
+
std::vector<std::string> parts;
|
|
1065
|
+
size_t start = 0;
|
|
1066
|
+
while (start <= cmd.size()) {
|
|
1067
|
+
size_t pos = cmd.find('|', start);
|
|
1068
|
+
if (pos == std::string::npos) pos = cmd.size();
|
|
1069
|
+
parts.push_back(cmd.substr(start, pos - start));
|
|
1070
|
+
start = pos + 1;
|
|
1071
|
+
if (pos == cmd.size()) break;
|
|
1072
|
+
}
|
|
1073
|
+
if (parts.size() != 3) return {false, payload_error("bad dispatch_probe args")};
|
|
1074
|
+
if (parts[0] != "__G_DISPATCH_PROBE__") return {false, payload_error("bad dispatch_probe args")};
|
|
1075
|
+
|
|
1076
|
+
std::string handle = trim_copy(parts[1]);
|
|
1077
|
+
std::string name = trim_copy(parts[2]);
|
|
1078
|
+
if (!valid_bridge_handle_token(handle) || !valid_r_method_name_token(name)) {
|
|
1079
|
+
return {false, payload_error("invalid dispatch_probe params")};
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
std::ostringstream oss;
|
|
1083
|
+
oss << "c(isTRUE('" << name << "' %in% names(" << handle << ")) || (is.environment(" << handle
|
|
1084
|
+
<< ") && isTRUE(exists('" << name << "', envir = " << handle << ", inherits = FALSE))), "
|
|
1085
|
+
<< "is.function(try(get('" << name << "'), silent=TRUE)))";
|
|
1086
|
+
|
|
1087
|
+
ParseStatus ps = PARSE_OK;
|
|
1088
|
+
SEXP px = R_ParseVector(Rf_mkString(oss.str().c_str()), 1, &ps, R_GlobalEnv);
|
|
1089
|
+
if (ps != PARSE_OK) return {false, payload_error("dispatch_probe parse error")};
|
|
1090
|
+
SEXP expr = VECTOR_ELT(px, 0);
|
|
1091
|
+
int err = 0;
|
|
1092
|
+
SEXP val = R_tryEval(expr, env, &err);
|
|
1093
|
+
if (err) {
|
|
1094
|
+
std::string msg = "evaluation error";
|
|
1095
|
+
try {
|
|
1096
|
+
Rcpp::Function geterr("geterrmessage");
|
|
1097
|
+
SEXP m = geterr();
|
|
1098
|
+
if (Rf_isString(m) && Rf_length(m) >= 1) msg = Rcpp::as<std::string>(m);
|
|
1099
|
+
} catch (...) {
|
|
1100
|
+
}
|
|
1101
|
+
return {false, payload_error(msg)};
|
|
1102
|
+
}
|
|
1103
|
+
if (TYPEOF(val) != LGLSXP || Rf_length(val) < 2) {
|
|
1104
|
+
return {false, payload_error("dispatch_probe unexpected result")};
|
|
1105
|
+
}
|
|
1106
|
+
int a = LOGICAL(val)[0];
|
|
1107
|
+
int b = LOGICAL(val)[1];
|
|
1108
|
+
bool is_field = (a != 0 && a != NA_LOGICAL);
|
|
1109
|
+
bool is_func = (b != 0 && b != NA_LOGICAL);
|
|
1110
|
+
return {true, payload_dispatch_probe(is_field, is_func)};
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Resolve or create the per-session environment for sid.
|
|
1114
|
+
//
|
|
1115
|
+
// Why per-session environments:
|
|
1116
|
+
// - isolation: variables from one session_id do not leak into another,
|
|
1117
|
+
// - correctness under concurrency: each request executes in its own state bag,
|
|
1118
|
+
// - low overhead: environments are lightweight and reused after first creation.
|
|
1119
|
+
Rcpp::Environment session_env(Rcpp::Environment& g, const std::string& sid) {
|
|
1120
|
+
Rcpp::List sessions = Rcpp::as<Rcpp::List>(g[".galaaz_sessions"]);
|
|
1121
|
+
if (!sessions.containsElementNamed(sid.c_str())) {
|
|
1122
|
+
Rcpp::Function newenv("new.env");
|
|
1123
|
+
// Keep base/global operators visible while still storing user variables in this session's env.
|
|
1124
|
+
Rcpp::Environment e = newenv(Rcpp::Named("parent") = g);
|
|
1125
|
+
sessions[sid] = e;
|
|
1126
|
+
g[".galaaz_sessions"] = sessions;
|
|
1127
|
+
}
|
|
1128
|
+
return Rcpp::as<Rcpp::Environment>(sessions[sid]);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// Split `__G_*__|a|b|...` command strings (same rules as other gatekeeper commands).
|
|
1132
|
+
static std::vector<std::string> split_pipe_cmd(const std::string& cmd) {
|
|
1133
|
+
std::vector<std::string> parts;
|
|
1134
|
+
size_t start = 0;
|
|
1135
|
+
while (start <= cmd.size()) {
|
|
1136
|
+
size_t pos = cmd.find('|', start);
|
|
1137
|
+
if (pos == std::string::npos) pos = cmd.size();
|
|
1138
|
+
parts.push_back(cmd.substr(start, pos - start));
|
|
1139
|
+
start = pos + 1;
|
|
1140
|
+
if (pos == cmd.size()) break;
|
|
1141
|
+
}
|
|
1142
|
+
return parts;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Bulk atomic vector read in one MsgPack payload:
|
|
1146
|
+
// {"kind":"integer_vector|double_vector|logical_vector|character_vector","values":[...]}
|
|
1147
|
+
// Forms:
|
|
1148
|
+
// __G_PULL_VECTOR__|<handle> — full vector
|
|
1149
|
+
// __G_PULL_VECTOR__|<handle>|<start_1based>|<count> — contiguous slice (count may be 0)
|
|
1150
|
+
static EvalResult eval_pull_vector_cmd(const std::string& cmd, const Rcpp::Environment& env) {
|
|
1151
|
+
std::vector<std::string> parts = split_pipe_cmd(cmd);
|
|
1152
|
+
if (parts.empty() || parts[0] != "__G_PULL_VECTOR__") {
|
|
1153
|
+
return {false, payload_error("bad pull_vector")};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
std::string handle = trim_copy(parts[1]);
|
|
1157
|
+
if (!valid_bridge_handle_token(handle)) {
|
|
1158
|
+
return {false, payload_error("invalid pull_vector handle")};
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
R_xlen_t from0 = 0;
|
|
1162
|
+
R_xlen_t count = 0;
|
|
1163
|
+
bool full = (parts.size() == 2);
|
|
1164
|
+
if (full) {
|
|
1165
|
+
// count set after resolve
|
|
1166
|
+
} else if (parts.size() == 4) {
|
|
1167
|
+
long long start1 = std::atoll(parts[2].c_str());
|
|
1168
|
+
long long len = std::atoll(parts[3].c_str());
|
|
1169
|
+
if (start1 < 1 || len < 0) {
|
|
1170
|
+
return {false, payload_error("invalid pull_vector slice")};
|
|
1171
|
+
}
|
|
1172
|
+
from0 = static_cast<R_xlen_t>(start1 - 1);
|
|
1173
|
+
count = static_cast<R_xlen_t>(len);
|
|
1174
|
+
} else {
|
|
1175
|
+
return {false, payload_error("bad pull_vector args")};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
SEXP sym = Rf_install(handle.c_str());
|
|
1179
|
+
SEXP vec = Rf_findVar(sym, env);
|
|
1180
|
+
if (vec == R_UnboundValue) {
|
|
1181
|
+
return {false, payload_error("pull_vector unknown handle")};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
R_xlen_t n = XLENGTH(vec);
|
|
1185
|
+
if (full) {
|
|
1186
|
+
from0 = 0;
|
|
1187
|
+
count = n;
|
|
1188
|
+
} else {
|
|
1189
|
+
if (from0 > n || count > n - from0) {
|
|
1190
|
+
return {false, payload_error("pull_vector slice out of bounds")};
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
constexpr R_xlen_t kMaxPull = 100000000;
|
|
1195
|
+
if (count > kMaxPull) {
|
|
1196
|
+
return {false, payload_error("pull_vector too large")};
|
|
1197
|
+
}
|
|
1198
|
+
if (count > static_cast<R_xlen_t>(UINT32_MAX)) {
|
|
1199
|
+
return {false, payload_error("pull_vector too large")};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
auto pack_values_map = [](const char* kind_tag, std::vector<uint8_t>& vals_arr) -> std::vector<uint8_t> {
|
|
1203
|
+
std::vector<uint8_t> p;
|
|
1204
|
+
pack_map2(p);
|
|
1205
|
+
pack_str(p, "kind");
|
|
1206
|
+
pack_str(p, kind_tag);
|
|
1207
|
+
pack_str(p, "values");
|
|
1208
|
+
p.insert(p.end(), vals_arr.begin(), vals_arr.end());
|
|
1209
|
+
return p;
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
if (count == 0) {
|
|
1213
|
+
std::vector<uint8_t> vals;
|
|
1214
|
+
pack_array_header(vals, 0);
|
|
1215
|
+
return {true, pack_values_map("integer_vector", vals)};
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
switch (TYPEOF(vec)) {
|
|
1219
|
+
case INTSXP: {
|
|
1220
|
+
std::vector<uint8_t> vals;
|
|
1221
|
+
pack_array_header(vals, static_cast<uint32_t>(count));
|
|
1222
|
+
for (R_xlen_t i = 0; i < count; ++i) {
|
|
1223
|
+
int v = INTEGER(vec)[from0 + i];
|
|
1224
|
+
if (v == NA_INTEGER) {
|
|
1225
|
+
pack_nil(vals);
|
|
1226
|
+
} else {
|
|
1227
|
+
pack_int32(vals, v);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return {true, pack_values_map("integer_vector", vals)};
|
|
1231
|
+
}
|
|
1232
|
+
case REALSXP: {
|
|
1233
|
+
std::vector<uint8_t> vals;
|
|
1234
|
+
pack_array_header(vals, static_cast<uint32_t>(count));
|
|
1235
|
+
for (R_xlen_t i = 0; i < count; ++i) {
|
|
1236
|
+
double v = REAL(vec)[from0 + i];
|
|
1237
|
+
if (R_IsNA(v)) {
|
|
1238
|
+
pack_nil(vals);
|
|
1239
|
+
} else {
|
|
1240
|
+
pack_double64(vals, v);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return {true, pack_values_map("double_vector", vals)};
|
|
1244
|
+
}
|
|
1245
|
+
case LGLSXP: {
|
|
1246
|
+
std::vector<uint8_t> vals;
|
|
1247
|
+
pack_array_header(vals, static_cast<uint32_t>(count));
|
|
1248
|
+
for (R_xlen_t i = 0; i < count; ++i) {
|
|
1249
|
+
int v = LOGICAL(vec)[from0 + i];
|
|
1250
|
+
if (v == NA_LOGICAL) {
|
|
1251
|
+
pack_nil(vals);
|
|
1252
|
+
} else {
|
|
1253
|
+
pack_bool(vals, v != 0);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return {true, pack_values_map("logical_vector", vals)};
|
|
1257
|
+
}
|
|
1258
|
+
case STRSXP: {
|
|
1259
|
+
std::vector<uint8_t> vals;
|
|
1260
|
+
pack_array_header(vals, static_cast<uint32_t>(count));
|
|
1261
|
+
for (R_xlen_t i = 0; i < count; ++i) {
|
|
1262
|
+
if (STRING_ELT(vec, from0 + i) == NA_STRING) {
|
|
1263
|
+
pack_nil(vals);
|
|
1264
|
+
} else {
|
|
1265
|
+
const char* s = CHAR(STRING_ELT(vec, from0 + i));
|
|
1266
|
+
if (!s) {
|
|
1267
|
+
pack_nil(vals);
|
|
1268
|
+
} else {
|
|
1269
|
+
pack_str(vals, std::string(s));
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return {true, pack_values_map("character_vector", vals)};
|
|
1274
|
+
}
|
|
1275
|
+
default:
|
|
1276
|
+
return {false, payload_error("pull_vector unsupported type")};
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Evaluate one R expression and return a compact MsgPack payload map:
|
|
1281
|
+
// {"kind":"integer|double|logical|character","value":...}
|
|
1282
|
+
// or {"kind":"error","message":"..."}.
|
|
1283
|
+
EvalResult eval_code_payload(const std::string& code, const Rcpp::Environment& env) {
|
|
1284
|
+
if (starts_with(code, "__G_EVAL_WITH_RESULT__")) {
|
|
1285
|
+
return eval_with_result_cmd(code, env);
|
|
1286
|
+
}
|
|
1287
|
+
if (starts_with(code, "__G_BATCH_EVAL_WITH_RESULT__")) {
|
|
1288
|
+
return eval_batch_eval_with_result_cmd(code, env);
|
|
1289
|
+
}
|
|
1290
|
+
if (starts_with(code, "__G_DISPATCH_PROBE__|")) {
|
|
1291
|
+
return eval_dispatch_probe_cmd(code, env);
|
|
1292
|
+
}
|
|
1293
|
+
if (starts_with(code, "__G_UNBOX_WALK__|")) {
|
|
1294
|
+
return eval_unbox_walk_cmd(code, env);
|
|
1295
|
+
}
|
|
1296
|
+
if (starts_with(code, "__G_UNBOX_MATERIALIZE__|")) {
|
|
1297
|
+
return eval_unbox_materialize_cmd(code, env);
|
|
1298
|
+
}
|
|
1299
|
+
if (starts_with(code, "__G_PULL_VECTOR__|")) {
|
|
1300
|
+
return eval_pull_vector_cmd(code, env);
|
|
1301
|
+
}
|
|
1302
|
+
const std::string to_parse = maybe_wrap_nested_eval_expr(code);
|
|
1303
|
+
ParseStatus ps = PARSE_OK;
|
|
1304
|
+
SEXP px = R_ParseVector(Rf_mkString(to_parse.c_str()), 1, &ps, R_GlobalEnv);
|
|
1305
|
+
if (ps != PARSE_OK) return {false, payload_error("parse error")};
|
|
1306
|
+
SEXP expr = VECTOR_ELT(px, 0);
|
|
1307
|
+
int err = 0;
|
|
1308
|
+
SEXP val = R_tryEval(expr, env, &err);
|
|
1309
|
+
if (err) {
|
|
1310
|
+
// Preserve the underlying R exception message for better Ruby-side debugging.
|
|
1311
|
+
// `geterrmessage()` returns the most recent error string in the current R context.
|
|
1312
|
+
std::string msg = "evaluation error";
|
|
1313
|
+
try {
|
|
1314
|
+
Rcpp::Function geterr("geterrmessage");
|
|
1315
|
+
SEXP m = geterr();
|
|
1316
|
+
if (Rf_isString(m) && Rf_length(m) >= 1) {
|
|
1317
|
+
msg = Rcpp::as<std::string>(m);
|
|
1318
|
+
}
|
|
1319
|
+
} catch (...) {
|
|
1320
|
+
// Keep fallback message.
|
|
1321
|
+
}
|
|
1322
|
+
return {false, payload_error(msg)};
|
|
1323
|
+
}
|
|
1324
|
+
if (Rf_length(val) != 1) return {false, payload_error("phase1 requires length-1 scalar")};
|
|
1325
|
+
|
|
1326
|
+
if (TYPEOF(val) == INTSXP) {
|
|
1327
|
+
std::vector<uint8_t> p;
|
|
1328
|
+
pack_map2(p);
|
|
1329
|
+
pack_str(p, "kind");
|
|
1330
|
+
pack_str(p, "integer");
|
|
1331
|
+
pack_str(p, "value");
|
|
1332
|
+
int x = INTEGER(val)[0];
|
|
1333
|
+
if (x == NA_INTEGER) pack_nil(p);
|
|
1334
|
+
else pack_int32(p, x);
|
|
1335
|
+
return {true, p};
|
|
1336
|
+
}
|
|
1337
|
+
if (TYPEOF(val) == REALSXP) {
|
|
1338
|
+
std::vector<uint8_t> p;
|
|
1339
|
+
pack_map2(p);
|
|
1340
|
+
pack_str(p, "kind");
|
|
1341
|
+
pack_str(p, "double");
|
|
1342
|
+
pack_str(p, "value");
|
|
1343
|
+
double x = REAL(val)[0];
|
|
1344
|
+
if (R_IsNA(x)) pack_nil(p);
|
|
1345
|
+
else pack_double64(p, x);
|
|
1346
|
+
return {true, p};
|
|
1347
|
+
}
|
|
1348
|
+
if (TYPEOF(val) == LGLSXP) {
|
|
1349
|
+
std::vector<uint8_t> p;
|
|
1350
|
+
pack_map2(p);
|
|
1351
|
+
pack_str(p, "kind");
|
|
1352
|
+
pack_str(p, "logical");
|
|
1353
|
+
pack_str(p, "value");
|
|
1354
|
+
int x = LOGICAL(val)[0];
|
|
1355
|
+
if (x == NA_LOGICAL) pack_nil(p);
|
|
1356
|
+
else pack_bool(p, x != 0);
|
|
1357
|
+
return {true, p};
|
|
1358
|
+
}
|
|
1359
|
+
if (TYPEOF(val) == STRSXP) {
|
|
1360
|
+
std::vector<uint8_t> p;
|
|
1361
|
+
pack_map2(p);
|
|
1362
|
+
pack_str(p, "kind");
|
|
1363
|
+
pack_str(p, "character");
|
|
1364
|
+
pack_str(p, "value");
|
|
1365
|
+
if (STRING_ELT(val, 0) == NA_STRING) {
|
|
1366
|
+
pack_nil(p);
|
|
1367
|
+
return {true, p};
|
|
1368
|
+
}
|
|
1369
|
+
const char* s = CHAR(STRING_ELT(val, 0));
|
|
1370
|
+
if (!s) {
|
|
1371
|
+
pack_nil(p);
|
|
1372
|
+
return {true, p};
|
|
1373
|
+
}
|
|
1374
|
+
pack_str(p, std::string(s));
|
|
1375
|
+
return {true, p};
|
|
1376
|
+
}
|
|
1377
|
+
return {false, payload_error("unsupported type")};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// Forward declaration for nested REQ servicing.
|
|
1381
|
+
static void process_single_req(int fd, const std::map<std::string, std::string>& fields, Rcpp::Environment& g);
|
|
1382
|
+
|
|
1383
|
+
// R-side callback trampoline:
|
|
1384
|
+
// - Sends CALL envelope to Ruby.
|
|
1385
|
+
// - Waits for matching RET for call_id.
|
|
1386
|
+
// - While waiting, processes nested inbound REQ envelopes.
|
|
1387
|
+
//
|
|
1388
|
+
// Why nested servicing is required:
|
|
1389
|
+
// If Ruby callback code re-enters R (directly or indirectly), the R side must
|
|
1390
|
+
// continue processing REQ frames while blocked waiting for callback RET.
|
|
1391
|
+
// Otherwise both sides can wait on each other (classic bridge deadlock).
|
|
1392
|
+
// [[Rcpp::export(name="galaaz_callback_call_phase3")]]
|
|
1393
|
+
double galaaz_callback_call(std::string call_id, std::string payload, int timeout_ms) {
|
|
1394
|
+
if (g_bridge_fd < 0) Rcpp::stop("galaaz_callback_call: no active bridge connection");
|
|
1395
|
+
CallbackNestingGuard nesting_guard;
|
|
1396
|
+
|
|
1397
|
+
// Send CALL envelope to Ruby.
|
|
1398
|
+
// Frame format: [uint32 length][msgpack bytes].
|
|
1399
|
+
auto call = make_call(call_id, payload, g_current_instance_id);
|
|
1400
|
+
dbg(std::string("TX CALL call_id=") + call_id + " instance_id=" + g_current_instance_id + " payload=" + payload);
|
|
1401
|
+
uint32_t L = static_cast<uint32_t>(call.size());
|
|
1402
|
+
if (!send_all(g_bridge_fd, &L, 4) || !send_all(g_bridge_fd, call.data(), call.size())) {
|
|
1403
|
+
Rcpp::stop("galaaz_callback_call: socket write failed");
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Phase 4: nested wait loop.
|
|
1407
|
+
// We stay in one loop so timeout accounting applies to both waiting and
|
|
1408
|
+
// nested work (REQ servicing), rather than treating them as separate clocks.
|
|
1409
|
+
Rcpp::Environment g = Rcpp::Environment::global_env();
|
|
1410
|
+
auto start_time = std::chrono::steady_clock::now();
|
|
1411
|
+
int remaining_ms = timeout_ms;
|
|
1412
|
+
|
|
1413
|
+
for (;;) {
|
|
1414
|
+
// Poll with remaining timeout.
|
|
1415
|
+
// Using poll() avoids busy-spinning and lets timeout decrease precisely
|
|
1416
|
+
// across nested request handling.
|
|
1417
|
+
if (timeout_ms > 0) {
|
|
1418
|
+
pollfd pfd;
|
|
1419
|
+
pfd.fd = g_bridge_fd;
|
|
1420
|
+
pfd.events = POLLIN;
|
|
1421
|
+
pfd.revents = 0;
|
|
1422
|
+
int pr = ::poll(&pfd, 1, remaining_ms);
|
|
1423
|
+
if (pr < 0) Rcpp::stop("galaaz_callback_call: poll error");
|
|
1424
|
+
if (pr == 0) Rcpp::stop("galaaz_callback_call timeout");
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Read one complete framed message.
|
|
1428
|
+
uint32_t len = 0;
|
|
1429
|
+
if (!recv_all(g_bridge_fd, &len, 4)) Rcpp::stop("galaaz_callback_call: socket closed");
|
|
1430
|
+
if (len > 64u * 1024u * 1024u) Rcpp::stop("galaaz_callback_call: frame too large");
|
|
1431
|
+
|
|
1432
|
+
std::vector<uint8_t> buf(len);
|
|
1433
|
+
if (len && !recv_all(g_bridge_fd, buf.data(), len)) Rcpp::stop("galaaz_callback_call: truncated frame");
|
|
1434
|
+
|
|
1435
|
+
// Parse envelope into lightweight scalar maps (no full object materialize).
|
|
1436
|
+
std::map<std::string, std::string> fields;
|
|
1437
|
+
std::map<std::string, bool> nils;
|
|
1438
|
+
Rd rd(buf);
|
|
1439
|
+
rd.parse_envelope(fields, nils);
|
|
1440
|
+
|
|
1441
|
+
std::string msg_type = fields.count("type") ? fields["type"] : "";
|
|
1442
|
+
|
|
1443
|
+
// Fast-path: this is the RET corresponding to our callback call_id.
|
|
1444
|
+
if (msg_type == "RET" && fields["call_id"] == call_id) {
|
|
1445
|
+
std::string status = fields.count("status") ? fields["status"] : "error";
|
|
1446
|
+
std::string out_payload = fields.count("payload") ? fields["payload"] : "";
|
|
1447
|
+
dbg(std::string("RX RET status=") + status + " payload=" + out_payload);
|
|
1448
|
+
if (status != "success") {
|
|
1449
|
+
Rcpp::stop(out_payload.c_str());
|
|
1450
|
+
}
|
|
1451
|
+
// Phase-3 contract: callback payload is numeric text.
|
|
1452
|
+
// strtod is used for speed and predictable C parsing semantics.
|
|
1453
|
+
char* end = nullptr;
|
|
1454
|
+
const char* start = out_payload.c_str();
|
|
1455
|
+
double v = std::strtod(start, &end);
|
|
1456
|
+
if (end == start) Rcpp::stop("galaaz_callback_call: payload not numeric");
|
|
1457
|
+
return v;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Service nested REQ while waiting for callback RET.
|
|
1461
|
+
// This is the deadlock-avoidance core.
|
|
1462
|
+
if (msg_type == "REQ") {
|
|
1463
|
+
dbg("Servicing nested REQ while waiting for callback RET");
|
|
1464
|
+
process_single_req(g_bridge_fd, fields, g);
|
|
1465
|
+
// Update remaining timeout
|
|
1466
|
+
if (timeout_ms > 0) {
|
|
1467
|
+
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
1468
|
+
std::chrono::steady_clock::now() - start_time).count();
|
|
1469
|
+
remaining_ms = timeout_ms - static_cast<int>(elapsed);
|
|
1470
|
+
if (remaining_ms <= 0) Rcpp::stop("galaaz_callback_call timeout");
|
|
1471
|
+
}
|
|
1472
|
+
continue; // Keep waiting for callback RET after nested work.
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Unexpected message type: ignore frame and continue waiting.
|
|
1476
|
+
// This keeps callback wait robust to protocol evolution/noise.
|
|
1477
|
+
dbg(std::string("Unexpected msg type while waiting for callback RET: ") + msg_type);
|
|
1478
|
+
if (timeout_ms > 0) {
|
|
1479
|
+
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
1480
|
+
std::chrono::steady_clock::now() - start_time).count();
|
|
1481
|
+
remaining_ms = timeout_ms - static_cast<int>(elapsed);
|
|
1482
|
+
if (remaining_ms <= 0) Rcpp::stop("galaaz_callback_call timeout");
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// Extern C forward declarations for .C() / dyn.load() compatibility.
|
|
1488
|
+
extern "C" {
|
|
1489
|
+
void galaaz_run_bridge_c(const char** host, int* port);
|
|
1490
|
+
double galaaz_callback_call_phase3_c(const char** call_id, const char** payload, int* timeout_ms);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// Process a single REQ and send RET.
|
|
1494
|
+
// This is shared by:
|
|
1495
|
+
// - main bridge loop
|
|
1496
|
+
// - nested callback wait loop
|
|
1497
|
+
static void process_single_req(int fd, const std::map<std::string, std::string>& fields, Rcpp::Environment& g) {
|
|
1498
|
+
std::string call_id = fields.count("call_id") ? fields.at("call_id") : "?";
|
|
1499
|
+
std::string sid = fields.count("session_id") ? fields.at("session_id") : "default";
|
|
1500
|
+
std::string iid = fields.count("instance_id") ? fields.at("instance_id") : "default";
|
|
1501
|
+
std::string code = fields.count("payload") ? fields.at("payload") : "";
|
|
1502
|
+
|
|
1503
|
+
try {
|
|
1504
|
+
g_current_instance_id = iid;
|
|
1505
|
+
Rcpp::Environment env = session_env(g, sid);
|
|
1506
|
+
EvalResult out = eval_code_payload(code, env);
|
|
1507
|
+
auto ret = make_ret(call_id, out.ok ? "success" : "error", out.payload, iid);
|
|
1508
|
+
uint32_t L = static_cast<uint32_t>(ret.size());
|
|
1509
|
+
if (!send_all(fd, &L, 4) || !send_all(fd, ret.data(), ret.size())) {
|
|
1510
|
+
dbg("process_single_req: send failed");
|
|
1511
|
+
}
|
|
1512
|
+
} catch (...) {
|
|
1513
|
+
auto ret = make_ret(call_id, "error", payload_error("cpp/r error"), iid);
|
|
1514
|
+
uint32_t L = static_cast<uint32_t>(ret.size());
|
|
1515
|
+
if (!send_all(fd, &L, 4) || !send_all(fd, ret.data(), ret.size())) {
|
|
1516
|
+
dbg("process_single_req: send failed (error path)");
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Main bridge loop entrypoint called from R:
|
|
1522
|
+
// galaaz_run_bridge(bridge_host, port) # first arg: hostname or IPv4 string
|
|
1523
|
+
//
|
|
1524
|
+
// Connects to Ruby host, initializes session registry, then continuously:
|
|
1525
|
+
// - reads framed envelope
|
|
1526
|
+
// - parses MsgPack
|
|
1527
|
+
// - handles REQ via process_single_req
|
|
1528
|
+
// - sends protocol error RET for malformed envelopes
|
|
1529
|
+
// [[Rcpp::export]]
|
|
1530
|
+
void galaaz_run_bridge(std::string bridge_host, int port) {
|
|
1531
|
+
// Local Unix domain socket: port == 0 and bridge_host is an absolute path (e.g. /tmp/galaaz....sock).
|
|
1532
|
+
// Same MsgPack framing as TCP; lower overhead than loopback TCP on Linux/macOS.
|
|
1533
|
+
if (port == 0 && !bridge_host.empty() && bridge_host[0] == '/') {
|
|
1534
|
+
struct sockaddr_un addr;
|
|
1535
|
+
std::memset(&addr, 0, sizeof(addr));
|
|
1536
|
+
addr.sun_family = AF_UNIX;
|
|
1537
|
+
if (bridge_host.size() >= sizeof(addr.sun_path)) {
|
|
1538
|
+
die("unix socket path too long");
|
|
1539
|
+
}
|
|
1540
|
+
std::memcpy(addr.sun_path, bridge_host.c_str(), bridge_host.size() + 1);
|
|
1541
|
+
int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
|
|
1542
|
+
if (fd < 0) die("socket(AF_UNIX)");
|
|
1543
|
+
if (::connect(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) != 0) {
|
|
1544
|
+
::close(fd);
|
|
1545
|
+
die("connect(AF_UNIX)");
|
|
1546
|
+
}
|
|
1547
|
+
g_bridge_fd = fd;
|
|
1548
|
+
|
|
1549
|
+
Rcpp::Environment g = Rcpp::Environment::global_env();
|
|
1550
|
+
g[".galaaz_sessions"] = Rcpp::List();
|
|
1551
|
+
|
|
1552
|
+
for (;;) {
|
|
1553
|
+
uint32_t len = 0;
|
|
1554
|
+
if (!recv_all(fd, &len, 4)) break;
|
|
1555
|
+
if (len > 64u * 1024u * 1024u) break;
|
|
1556
|
+
std::vector<uint8_t> buf(len);
|
|
1557
|
+
if (len && !recv_all(fd, buf.data(), len)) break;
|
|
1558
|
+
|
|
1559
|
+
std::map<std::string, std::string> fields;
|
|
1560
|
+
std::map<std::string, bool> nils;
|
|
1561
|
+
try {
|
|
1562
|
+
Rd rd(buf);
|
|
1563
|
+
rd.parse_envelope(fields, nils);
|
|
1564
|
+
} catch (...) {
|
|
1565
|
+
auto er = make_ret("?", "error", payload_error("bad envelope"), "default");
|
|
1566
|
+
uint32_t L = static_cast<uint32_t>(er.size());
|
|
1567
|
+
if (!send_all(fd, &L, 4) || !send_all(fd, er.data(), er.size())) break;
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (fields["type"] != "REQ") continue;
|
|
1572
|
+
process_single_req(fd, fields, g);
|
|
1573
|
+
}
|
|
1574
|
+
g_bridge_fd = -1;
|
|
1575
|
+
::close(fd);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// Support both literal IPv4 and hostnames (e.g. host.docker.internal)
|
|
1580
|
+
// so containerized runtimes can connect back to Ruby host listener.
|
|
1581
|
+
addrinfo hints;
|
|
1582
|
+
std::memset(&hints, 0, sizeof(hints));
|
|
1583
|
+
hints.ai_family = AF_UNSPEC;
|
|
1584
|
+
hints.ai_socktype = SOCK_STREAM;
|
|
1585
|
+
|
|
1586
|
+
addrinfo* res = nullptr;
|
|
1587
|
+
std::string p = std::to_string(port);
|
|
1588
|
+
if (::getaddrinfo(bridge_host.c_str(), p.c_str(), &hints, &res) != 0) die("getaddrinfo");
|
|
1589
|
+
|
|
1590
|
+
int fd = -1;
|
|
1591
|
+
for (addrinfo* it = res; it != nullptr; it = it->ai_next) {
|
|
1592
|
+
fd = ::socket(it->ai_family, it->ai_socktype, it->ai_protocol);
|
|
1593
|
+
if (fd < 0) continue;
|
|
1594
|
+
if (::connect(fd, it->ai_addr, it->ai_addrlen) == 0) break;
|
|
1595
|
+
::close(fd);
|
|
1596
|
+
fd = -1;
|
|
1597
|
+
}
|
|
1598
|
+
::freeaddrinfo(res);
|
|
1599
|
+
if (fd < 0) die("connect");
|
|
1600
|
+
g_bridge_fd = fd;
|
|
1601
|
+
|
|
1602
|
+
Rcpp::Environment g = Rcpp::Environment::global_env();
|
|
1603
|
+
g[".galaaz_sessions"] = Rcpp::List();
|
|
1604
|
+
|
|
1605
|
+
for (;;) {
|
|
1606
|
+
uint32_t len = 0;
|
|
1607
|
+
if (!recv_all(fd, &len, 4)) break;
|
|
1608
|
+
if (len > 64u * 1024u * 1024u) break;
|
|
1609
|
+
std::vector<uint8_t> buf(len);
|
|
1610
|
+
if (len && !recv_all(fd, buf.data(), len)) break;
|
|
1611
|
+
|
|
1612
|
+
std::map<std::string, std::string> fields;
|
|
1613
|
+
std::map<std::string, bool> nils;
|
|
1614
|
+
try {
|
|
1615
|
+
Rd rd(buf);
|
|
1616
|
+
rd.parse_envelope(fields, nils);
|
|
1617
|
+
} catch (...) {
|
|
1618
|
+
auto er = make_ret("?", "error", payload_error("bad envelope"), "default");
|
|
1619
|
+
uint32_t L = static_cast<uint32_t>(er.size());
|
|
1620
|
+
if (!send_all(fd, &L, 4) || !send_all(fd, er.data(), er.size())) break;
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
if (fields["type"] != "REQ") continue;
|
|
1625
|
+
process_single_req(fd, fields, g);
|
|
1626
|
+
}
|
|
1627
|
+
g_bridge_fd = -1;
|
|
1628
|
+
::close(fd);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// C wrappers for .C() calls:
|
|
1632
|
+
// R passes character vectors as char** and integers as int*.
|
|
1633
|
+
extern "C" void galaaz_run_bridge_c(const char** host, int* port) {
|
|
1634
|
+
galaaz_run_bridge(std::string(*host), *port);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
extern "C" double galaaz_callback_call_phase3_c(const char** call_id, const char** payload, int* timeout_ms) {
|
|
1638
|
+
return galaaz_callback_call(std::string(*call_id), std::string(*payload), *timeout_ms);
|
|
1639
|
+
}
|