Embedding CRuby interpreter without internal headers has been difficult

for few years because:
* NODE is no longer accessible.
* rb_iseq_eval_main crashes without preparing with rb_thread_t.
* some existing APIs calls exit(3) without giving the opportunity to
  finalize or handle errors to the client.
* No general-purpose function to compile a source to an iseq are
  published in the public headers.

This commit solves the problems.

	* include/ruby/ruby.h: Grouped APIs for embedding CRuby interpreter.
	  (ruby_setup, ruby_compile_main_from_file,
	  ruby_compile_main_from_string, ruby_eval_main,
	  ruby_set_script_name): new APIs to embed CRuby.
	  (ruby_opaque_t) Opaque pointer to an internal data, to NODE or iseq
	  in particular.

	* eval.c (ruby_setup): Similar to ruby_init but returns an error code
	  instead of exit(3) on error.
	  (ruby_eval_main): Similar to ruby_exec_node but returns the
	  evaluation result.
	  (ruby_eval_main_internal): renamed from ruby_exec_internal.

	* ruby.c (toplevel_context): new helper function.
	  (PREPARE_EVAL_MAIN): moved.
	  (process_options): refactored with new functions.
	  (parse_and_compile_main) new helper funciton.
	  (ruby_compile_main_from_file, ruby_compile_main_from_string) new API
	  (ruby_set_script_name): new API.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36079 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
yugui 2012-06-14 02:22:08 +00:00
parent f8601bd903
commit 8c9a453f2d
5 changed files with 306 additions and 97 deletions

View File

@ -1,3 +1,26 @@
Thu Jun 14 10:44:41 2012 Yuki Yugui Sonoda <yugui@google.com>
* include/ruby/ruby.h: Grouped APIs for embedding CRuby interpreter.
(ruby_setup, ruby_compile_main_from_file,
ruby_compile_main_from_string, ruby_eval_main,
ruby_set_script_name): new APIs to embed CRuby.
(ruby_opaque_t) Opaque pointer to an internal data, to NODE or iseq
in particular.
* eval.c (ruby_setup): Similar to ruby_init but returns an error code
instead of exit(3) on error.
(ruby_eval_main): Similar to ruby_exec_node but returns the
evaluation result.
(ruby_eval_main_internal): renamed from ruby_exec_internal.
* ruby.c (toplevel_context): new helper function.
(PREPARE_EVAL_MAIN): moved.
(process_options): refactored with new functions.
(parse_and_compile_main) new helper funciton.
(ruby_compile_main_from_file, ruby_compile_main_from_string) new API
(ruby_set_script_name): new API.
Thu Jun 14 10:39:48 2012 Yuki Yugui Sonoda <yugui@google.com>
* eval.c: Add doxygen comments.

93
eval.c
View File

@ -31,16 +31,18 @@ VALUE rb_eSysStackError;
#include "eval_error.c"
#include "eval_jump.c"
/* initialize ruby */
void
ruby_init(void)
/* Initializes the Ruby VM and builtin libraries.
* @retval 0 if succeeded.
* @retval non-zero an error occured.
*/
int
ruby_setup(void)
{
static int initialized = 0;
int state;
if (initialized)
return;
return 0;
initialized = 1;
ruby_init_stack((void *)&state);
@ -54,11 +56,22 @@ ruby_init(void)
}
POP_TAG();
if (!state) GET_VM()->running = 1;
return state;
}
/* Calls ruby_setup() and check error.
*
* Prints errors and calls exit(3) if an error occured.
*/
void
ruby_init(void)
{
int state = ruby_setup();
if (state) {
error_print();
exit(EXIT_FAILURE);
}
GET_VM()->running = 1;
}
/*! Processes command line arguments and compiles the Ruby source to execute.
@ -71,7 +84,7 @@ ruby_init(void)
* @return an opaque pointer to the compiled source or an internal special value.
* @sa ruby_executable_node().
*/
void *
ruby_opaque_t
ruby_options(int argc, char **argv)
{
int state;
@ -217,26 +230,6 @@ ruby_cleanup(volatile int ex)
return ex;
}
static int
ruby_exec_internal(void *n)
{
volatile int state;
VALUE iseq = (VALUE)n;
rb_thread_t *th = GET_THREAD();
if (!n) return 0;
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
SAVE_ROOT_JMPBUF(th, {
th->base_block = 0;
rb_iseq_eval_main(iseq);
});
}
POP_TAG();
return state;
}
/*! Calls ruby_cleanup() and exits the process */
void
ruby_stop(int ex)
@ -257,7 +250,7 @@ ruby_stop(int ex)
* @retval 0 if the given value is such a special value.
*/
int
ruby_executable_node(void *n, int *status)
ruby_executable_node(ruby_opaque_t n, int *status)
{
VALUE v = (VALUE)n;
int s;
@ -273,12 +266,33 @@ ruby_executable_node(void *n, int *status)
return FALSE;
}
static int
ruby_eval_main_internal(VALUE iseqval, VALUE* result)
{
volatile int state;
volatile VALUE retval;
rb_thread_t *th = GET_THREAD();
if (!iseqval) return 0;
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
SAVE_ROOT_JMPBUF(th, {
th->base_block = 0;
retval = rb_iseq_eval_main(iseqval);
});
}
POP_TAG();
*result = state ? th->errinfo : retval;
return state;
}
/*! Runs the given compiled source and exits this process.
* @retval 0 if successfully run thhe source
* @retval non-zero if an error occurred.
*/
int
ruby_run_node(void *n)
ruby_run_node(ruby_opaque_t n)
{
int status;
if (!ruby_executable_node(n, &status)) {
@ -290,10 +304,27 @@ ruby_run_node(void *n)
/*! Runs the given compiled source */
int
ruby_exec_node(void *n)
ruby_exec_node(ruby_opaque_t n)
{
VALUE dummy;
ruby_init_stack((void *)&n);
return ruby_exec_internal(n);
return ruby_eval_main_internal((VALUE)n, &dummy);
}
/*!
* Evaluates the given iseq in the main (toplevel) context.
*
* @param iseqval a VALUE that wraps an iseq.
* @param result Stores the evaluated value if succeeded,
* or an exception if failed.
* @retval 0 if succeeded
* @retval non-zero if failed.
*/
int
ruby_eval_main(ruby_opaque_t n, VALUE *result)
{
return !!ruby_eval_main_internal((VALUE)n, result);
}
/*

View File

@ -386,9 +386,6 @@ VALUE rb_protect(VALUE (*)(VALUE), VALUE, int*);
void rb_set_end_proc(void (*)(VALUE), VALUE);
void rb_mark_end_proc(void);
void rb_exec_end_proc(void);
void ruby_finalize(void);
NORETURN(void ruby_stop(int));
int ruby_cleanup(volatile int);
DEPRECATED(void rb_gc_mark_threads(void));
void rb_thread_schedule(void);
void rb_thread_wait_fd(int);
@ -432,10 +429,7 @@ VALUE rb_file_directory_p(VALUE,VALUE);
VALUE rb_str_encode_ospath(VALUE);
int rb_is_absolute_path(const char *);
/* gc.c */
void ruby_set_stack_size(size_t);
NORETURN(void rb_memerror(void));
int ruby_stack_check(void);
size_t ruby_stack_length(VALUE**);
int rb_during_gc(void);
void rb_gc_mark_locations(VALUE*, VALUE*);
void rb_mark_tbl(struct st_table*);
@ -451,7 +445,6 @@ void rb_gc_call_finalizer_at_exit(void);
VALUE rb_gc_enable(void);
VALUE rb_gc_disable(void);
VALUE rb_gc_start(void);
#define Init_stack(addr) ruby_init_stack(addr)
void rb_gc_set_params(void);
/* hash.c */
void st_foreach_safe(struct st_table *, int (*)(ANYARGS), st_data_t);
@ -662,12 +655,6 @@ int rb_reg_options(VALUE);
RUBY_EXTERN VALUE rb_argv0;
VALUE rb_get_argv(void);
void *rb_load_file(const char*);
void ruby_script(const char*);
void ruby_prog_init(void);
void ruby_set_argv(int, char**);
void *ruby_process_options(int, char**);
void ruby_init_loadpath(void);
void ruby_incpush(const char*);
/* signal.c */
VALUE rb_f_kill(int, VALUE*);
void rb_gc_mark_trap_list(void);
@ -675,7 +662,6 @@ void rb_gc_mark_trap_list(void);
#define posix_signal ruby_posix_signal
RETSIGTYPE (*posix_signal(int, RETSIGTYPE (*)(int)))(int);
#endif
void ruby_sig_finalize(void);
void rb_trap_exit(void);
void rb_trap_exec(void);
const char *ruby_signal_name(int);
@ -929,9 +915,6 @@ VALUE rb_cv_get(VALUE, const char*);
void rb_define_class_variable(VALUE, const char*, VALUE);
VALUE rb_mod_class_variables(VALUE);
VALUE rb_mod_remove_cvar(VALUE, VALUE);
/* version.c */
void ruby_show_version(void);
void ruby_show_copyright(void);
ID rb_frame_callee(void);
VALUE rb_str_succ(VALUE);

View File

@ -1229,21 +1229,6 @@ NORETURN(void rb_throw_obj(VALUE,VALUE));
VALUE rb_require(const char*);
#ifdef __ia64
void ruby_init_stack(volatile VALUE*, void*);
#define ruby_init_stack(addr) ruby_init_stack((addr), rb_ia64_bsp())
#else
void ruby_init_stack(volatile VALUE*);
#endif
#define RUBY_INIT_STACK \
VALUE variable_in_this_stack_frame; \
ruby_init_stack(&variable_in_this_stack_frame);
void ruby_init(void);
void *ruby_options(int, char**);
int ruby_run_node(void *);
int ruby_exec_node(void *);
int ruby_executable_node(void *n, int *status);
RUBY_EXTERN VALUE rb_mKernel;
RUBY_EXTERN VALUE rb_mComparable;
RUBY_EXTERN VALUE rb_mEnumerable;
@ -1400,14 +1385,6 @@ rb_special_const_p(VALUE obj)
static char *dln_libs_to_be_linked[] = { EXTLIB, 0 };
#endif
#if (defined(__APPLE__) || defined(__NeXT__)) && defined(__MACH__)
#define RUBY_GLOBAL_SETUP /* use linker option to link startup code with ObjC support */
#else
#define RUBY_GLOBAL_SETUP
#endif
void ruby_sysinit(int *, char ***);
#define RUBY_VM 1 /* YARV */
#define HAVE_NATIVETHREAD
int ruby_native_thread_p(void);
@ -1495,6 +1472,89 @@ int ruby_vsnprintf(char *str, size_t n, char const *fmt, va_list ap);
#include "ruby/subst.h"
#endif
/**
* @defgroup embed CRuby Embedding APIs
* CRuby interpreter APIs. These are APIs to embed MRI interpreter into your
* program.
* These functions are not a part of Ruby extention library API.
* Extension libraries of Ruby should not depend on these functions.
* @{
*/
/*! Opaque pointer to an inner data structure.
*
* You do not have to know what the actual data type this pointer points.
* It often changes for internal improvements.
*/
typedef void *ruby_opaque_t;
/*! @deprecated You no longer need to use this macro. */
#if (defined(__APPLE__) || defined(__NeXT__)) && defined(__MACH__)
#define RUBY_GLOBAL_SETUP /* use linker option to link startup code with ObjC support */
#else
#define RUBY_GLOBAL_SETUP
#endif
/** @defgroup ruby1 ruby(1) implementation
* A part of the implementation of ruby(1) command.
* Other programs that embed Ruby interpreter do not always need to use these
* functions.
* @{
*/
void ruby_sysinit(int *argc, char ***argv);
void ruby_init(void);
ruby_opaque_t ruby_options(int argc, char** argv);
int ruby_executable_node(ruby_opaque_t n, int *status);
int ruby_run_node(ruby_opaque_t n);
/* version.c */
void ruby_show_version(void);
void ruby_show_copyright(void);
/*! A convenience macro to call ruby_init_stack(). Must be placed just after
* variable declarations */
#define RUBY_INIT_STACK \
VALUE variable_in_this_stack_frame; \
ruby_init_stack(&variable_in_this_stack_frame);
/*! @} */
#ifdef __ia64
void ruby_init_stack(volatile VALUE*, void*);
#define ruby_init_stack(addr) ruby_init_stack((addr), rb_ia64_bsp())
#else
void ruby_init_stack(volatile VALUE*);
#endif
#define Init_stack(addr) ruby_init_stack(addr)
int ruby_setup(void);
int ruby_cleanup(volatile int);
void ruby_finalize(void);
NORETURN(void ruby_stop(int));
void ruby_set_stack_size(size_t);
int ruby_stack_check(void);
size_t ruby_stack_length(VALUE**);
ruby_opaque_t ruby_compile_main_from_file(VALUE fname, const char* path, VALUE* error);
ruby_opaque_t ruby_compile_main_from_string(VALUE fname, VALUE string, VALUE* error);
int ruby_exec_node(ruby_opaque_t n);
int ruby_eval_main(ruby_opaque_t n, VALUE *result);
void ruby_script(const char* name);
void ruby_set_script_name(VALUE name);
void ruby_prog_init(void);
void ruby_set_argv(int, char**);
void *ruby_process_options(int, char**);
void ruby_init_loadpath(void);
void ruby_incpush(const char*);
void ruby_sig_finalize(void);
/*! @} */
#if defined(__cplusplus)
#if 0
{ /* satisfy cc-mode */

164
ruby.c
View File

@ -496,6 +496,26 @@ require_libraries(VALUE *req_list)
th->base_block = prev_base_block;
}
static rb_env_t*
toplevel_context(void)
{
rb_env_t *env;
VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
rb_binding_t *bind;
GetBindingPtr(toplevel_binding, bind);
GetEnvPtr(bind->env, env);
return env;
}
#define PREPARE_PARSE_MAIN(th, env, expr) do { \
(th)->parse_in_eval--; \
(th)->base_block = &(env)->block; \
expr; \
(th)->parse_in_eval++; \
(th)->base_block = 0; \
} while (0)
static void
process_sflag(int *sflag)
{
@ -1365,22 +1385,7 @@ process_options(int argc, char **argv, struct cmdline_options *opt)
ruby_set_argv(argc, argv);
process_sflag(&opt->sflag);
{
/* set eval context */
VALUE toplevel_binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING"));
rb_binding_t *bind;
GetBindingPtr(toplevel_binding, bind);
GetEnvPtr(bind->env, env);
}
#define PREPARE_PARSE_MAIN(expr) do { \
th->parse_in_eval--; \
th->base_block = &env->block; \
expr; \
th->parse_in_eval++; \
th->base_block = 0; \
} while (0)
env = toplevel_context();
if (opt->e_script) {
VALUE progname = rb_progname;
@ -1392,11 +1397,11 @@ process_options(int argc, char **argv, struct cmdline_options *opt)
eenc = lenc;
}
rb_enc_associate(opt->e_script, eenc);
rb_vm_set_progname(rb_progname = opt->script_name);
ruby_set_script_name(opt->script_name);
require_libraries(&opt->req_list);
rb_vm_set_progname(rb_progname = progname);
ruby_set_script_name(progname);
PREPARE_PARSE_MAIN({
PREPARE_PARSE_MAIN(th, env, {
tree = rb_parser_compile_string(parser, opt->script, opt->e_script, 1);
});
}
@ -1405,12 +1410,11 @@ process_options(int argc, char **argv, struct cmdline_options *opt)
forbid_setid("program input from stdin");
}
PREPARE_PARSE_MAIN({
PREPARE_PARSE_MAIN(th, env, {
tree = load_file(parser, opt->script_name, 1, opt);
});
}
rb_progname = opt->script_name;
rb_vm_set_progname(rb_progname);
ruby_set_script_name(opt->script_name);
if (opt->dump & DUMP_BIT(yydebug)) return Qtrue;
if (opt->ext.enc.index >= 0) {
@ -1446,12 +1450,12 @@ process_options(int argc, char **argv, struct cmdline_options *opt)
}
if (opt->do_print) {
PREPARE_PARSE_MAIN({
PREPARE_PARSE_MAIN(th, env, {
tree = rb_parser_append_print(parser, tree);
});
}
if (opt->do_loop) {
PREPARE_PARSE_MAIN({
PREPARE_PARSE_MAIN(th, env, {
tree = rb_parser_while_loop(parser, tree, opt->do_line, opt->do_split);
});
rb_define_global_function("sub", rb_f_sub, -1);
@ -1466,7 +1470,7 @@ process_options(int argc, char **argv, struct cmdline_options *opt)
return Qtrue;
}
PREPARE_PARSE_MAIN({
PREPARE_PARSE_MAIN(th, env, {
VALUE path = Qnil;
if (!opt->e_script && strcmp(opt->script, "-"))
path = rb_realpath_internal(Qnil, opt->script_name, 1);
@ -1622,7 +1626,7 @@ load_file_internal(VALUE arg)
if (f != rb_stdin) rb_io_close(f);
f = Qnil;
}
rb_vm_set_progname(rb_progname = opt->script_name);
ruby_set_script_name(opt->script_name);
require_libraries(&opt->req_list); /* Why here? unnatural */
}
if (opt->src.enc.index >= 0) {
@ -1689,6 +1693,103 @@ rb_load_file(const char *fname)
return load_file(rb_parser_new(), fname_v, 0, cmdline_options_init(&opt));
}
struct ruby_compile_main_arg {
int is_string;
union {
VALUE path;
VALUE string;
} source;
};
static ruby_opaque_t
parse_and_compile_main(VALUE fname, const struct ruby_compile_main_arg* arg, VALUE* error)
{
rb_env_t *const env = toplevel_context();
rb_thread_t *const th = GET_THREAD();
NODE* tree;
VALUE iseq;
VALUE path;
int state;
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
PREPARE_PARSE_MAIN(th, env, {
VALUE parser = rb_parser_new();
th->mild_compile_error++;
if (arg->is_string) {
FilePathValue(fname);
path = fname;
tree = rb_parser_compile_string(parser, RSTRING_PTR(fname), arg->source.string, 1);
}
else {
struct cmdline_options opt;
path = arg->source.path;
tree = load_file(parser, path, 0, cmdline_options_init(&opt));
}
th->mild_compile_error--;
});
if (!tree) rb_exc_raise(th->errinfo);
ruby_set_script_name(fname);
PREPARE_PARSE_MAIN(th, env, {
iseq = rb_iseq_new_main(tree, fname, path);
});
}
POP_TAG();
if (state) {
*error = th->errinfo;
return NULL;
} else {
*error = Qnil;
return iseq;
}
}
/**
* Compiles a main Ruby script file into the internal a data structure.
*
* This function:
* @li loads the file specified by path.
* @li parses the source and compiles it
*
* @param fname <code>$0</code> is set to this value.
* If nil,
* uses the given path instead.
* @param path path to the source
* @param error where to store the exception if an error occured.
* @return The compiled source code. Or NULL if an error occured.
*/
ruby_opaque_t
ruby_compile_main_from_file(VALUE fname, const char* path, VALUE* error)
{
struct ruby_compile_main_arg arg;
arg.is_string = FALSE;
arg.source.path = rb_str_new_cstr(path);
if (NIL_P(fname)) fname = arg.source.path;
return parse_and_compile_main(fname, &arg, error);
}
/**
* Compiles a main Ruby script in a string into the internal a data structure.
*
* This function parses the given source and compiles it
*
* @param fname <code>$0</code> is set to this value.
* @param source Ruby source string
* @param error where to store the exception if an error occured.
* @return The compiled source code. Or NULL if an error occured.
*/
ruby_opaque_t
ruby_compile_main_from_string(VALUE fname, VALUE source, VALUE* error)
{
struct ruby_compile_main_arg arg;
arg.is_string = TRUE;
arg.source.string = source;
return parse_and_compile_main(fname, &arg, error);
}
static void
set_arg0(VALUE val, ID id)
{
@ -1720,6 +1821,17 @@ ruby_script(const char *name)
}
}
/*! Sets the current script name to this value.
*
* Same as ruby_script() but accepts a VALUE.
*/
void
ruby_set_script_name(VALUE name)
{
rb_progname = rb_str_dup(name);
rb_vm_set_progname(rb_progname);
}
static void
init_ids(struct cmdline_options *opt)
{