Files
mruby-mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c
T
Yukihiro "Matz" Matsumoto 05ffe0c441 mrb_open: return mrb_state with exc set on init failure
changed mrb_open() and mrb_open_core() to return mrb_state with mrb->exc
set (instead of NULL) when initialization fails. this allows callers to
programmatically inspect error details, which is essential for embedded
systems without stderr. return NULL only for true allocation failure.

added MRB_OPEN_SUCCESS(mrb) macro to check initialization success, since
mrb != NULL no longer guarantees success. updated all binary tools
(mruby, mirb, mrdb, mrbtest) to use new pattern: check MRB_OPEN_SUCCESS,
print exception details via mrb_print_error if available, then mrb_close.

mrb_core_init_protect now preserves exception in mrb->exc instead of
printing and clearing it, giving caller control over error handling.

breaking change: callers must use MRB_OPEN_SUCCESS(mrb) or check both
mrb != NULL && mrb->exc == NULL. old NULL-only checks will miss
initialization failures.

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-13 19:10:46 +09:00

387 lines
9.4 KiB
C

#include <mruby.h>
#ifdef MRB_NO_STDIO
# error mruby-bin-mruby conflicts 'MRB_NO_STDIO' in your build configuration
#endif
#include <stdlib.h>
#include <string.h>
#include <mruby/array.h>
#include <mruby/compile.h>
#include <mruby/dump.h>
#include <mruby/variable.h>
#include <mruby/proc.h>
#include <mruby/error.h>
#include <mruby/presym.h>
#if defined(_WIN32)
# include <io.h> /* for setmode */
# include <fcntl.h>
#endif
struct _args {
FILE *rfp;
char *cmdline;
mrb_bool fname : 1;
mrb_bool mrbfile : 1;
mrb_bool check_syntax : 1;
mrb_bool verbose : 1;
mrb_bool version : 1;
mrb_bool debug : 1;
int argc;
char **argv;
int libc;
char **libv;
};
struct options {
int argc;
char **argv;
char *program;
char *opt;
char short_opt[2];
};
static void
usage(const char *name)
{
static const char *const usage_msg[] = {
"switches:",
"-b load and execute RiteBinary (mrb) file",
"-c check syntax only",
"-d set debugging flags (set $DEBUG to true)",
"-e 'command' one line of script",
"-r library load the library before executing your script",
"-v print version number, then run in verbose mode",
"--verbose run in verbose mode",
"--version print the version",
"--copyright print the copyright",
NULL
};
const char *const *p = usage_msg;
printf("Usage: %s [switches] [programfile] [arguments]\n", name);
while (*p)
printf(" %s\n", *p++);
}
static mrb_bool
mrb_extension_p(const char *path)
{
const char *e = strrchr(path, '.');
if (e && e[1] == 'm' && e[2] == 'r' && e[3] == 'b' && e[4] == '\0') {
return TRUE;
}
return FALSE;
}
static void
options_init(struct options *opts, int argc, char **argv)
{
opts->argc = argc;
opts->argv = argv;
opts->program = *argv;
*opts->short_opt = 0;
}
static const char *
options_opt(struct options *opts)
{
/* concatenated short options (e.g. `-cv`) */
if (*opts->short_opt && *++opts->opt) {
short_opt:
opts->short_opt[0] = *opts->opt;
opts->short_opt[1] = 0;
return opts->short_opt;
}
while (++opts->argv, --opts->argc) {
opts->opt = *opts->argv;
/* not start with `-` || `-` */
if (opts->opt[0] != '-' || !opts->opt[1]) return NULL;
if (opts->opt[1] == '-') {
/* `--` */
if (!opts->opt[2]) {
opts->argv++, opts->argc--;
return NULL;
}
/* long option */
opts->opt += 2;
*opts->short_opt = 0;
return opts->opt;
}
else {
/* short option */
opts->opt++;
goto short_opt;
}
}
return NULL;
}
static const char *
options_arg(struct options *opts)
{
if (*opts->short_opt && opts->opt[1]) {
/* concatenated short option and option argument (e.g. `-rLIBRARY`) */
*opts->short_opt = 0;
return opts->opt + 1;
}
--opts->argc, ++opts->argv;
return opts->argc ? *opts->argv : NULL;
}
static char *
dup_arg_item(mrb_state *mrb, const char *item)
{
size_t buflen = strlen(item) + 1;
char *buf = (char*)mrb_malloc(mrb, buflen);
memcpy(buf, item, buflen);
return buf;
}
static int
parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args)
{
static const struct _args args_zero = { 0 };
struct options opts[1];
const char *opt, *item;
*args = args_zero;
options_init(opts, argc, argv);
while ((opt = options_opt(opts))) {
if (strcmp(opt, "b") == 0) {
args->mrbfile = TRUE;
}
else if (strcmp(opt, "c") == 0) {
args->check_syntax = TRUE;
}
else if (strcmp(opt, "d") == 0) {
args->debug = TRUE;
}
else if (strcmp(opt, "e") == 0) {
if ((item = options_arg(opts))) {
if (!args->cmdline) {
args->cmdline = dup_arg_item(mrb, item);
}
else {
size_t cmdlinelen;
size_t itemlen;
cmdlinelen = strlen(args->cmdline);
itemlen = strlen(item);
args->cmdline = (char*)mrb_realloc(mrb, args->cmdline, cmdlinelen + itemlen + 2);
args->cmdline[cmdlinelen] = '\n';
memcpy(args->cmdline + cmdlinelen + 1, item, itemlen + 1);
}
}
else {
fprintf(stderr, "%s: No code specified for -e\n", opts->program);
return EXIT_FAILURE;
}
}
else if (strcmp(opt, "h") == 0) {
usage(opts->program);
exit(EXIT_SUCCESS);
}
else if (strcmp(opt, "r") == 0) {
if ((item = options_arg(opts))) {
if (args->libc == 0) {
args->libv = (char**)mrb_malloc(mrb, sizeof(char*));
}
else {
args->libv = (char**)mrb_realloc(mrb, args->libv, sizeof(char*) * (args->libc + 1));
}
args->libv[args->libc++] = dup_arg_item(mrb, item);
}
else {
fprintf(stderr, "%s: No library specified for -r\n", opts->program);
return EXIT_FAILURE;
}
}
else if (strcmp(opt, "v") == 0) {
if (!args->verbose) {
mrb_show_version(mrb);
args->version = TRUE;
}
args->verbose = TRUE;
}
else if (strcmp(opt, "version") == 0) {
mrb_show_version(mrb);
exit(EXIT_SUCCESS);
}
else if (strcmp(opt, "verbose") == 0) {
args->verbose = TRUE;
}
else if (strcmp(opt, "copyright") == 0) {
mrb_show_copyright(mrb);
exit(EXIT_SUCCESS);
}
else {
fprintf(stderr, "%s: invalid option %s%s (-h will show valid options)\n",
opts->program, opt[1] ? "--" : "-", opt);
return EXIT_FAILURE;
}
}
argc = opts->argc; argv = opts->argv;
if (args->cmdline == NULL) {
if (*argv == NULL) {
if (args->version) exit(EXIT_SUCCESS);
args->rfp = stdin;
}
else {
args->rfp = strcmp(argv[0], "-") == 0 ?
stdin : fopen(argv[0], "rb");
if (args->rfp == NULL) {
fprintf(stderr, "%s: Cannot open program file: %s\n", opts->program, argv[0]);
return EXIT_FAILURE;
}
args->fname = TRUE;
args->cmdline = argv[0];
argc--; argv++;
}
}
#if defined(_WIN32)
if (args->rfp == stdin) {
_setmode(_fileno(stdin), O_BINARY);
}
#endif
args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1));
memcpy(args->argv, argv, (argc+1) * sizeof(char*));
args->argc = argc;
return EXIT_SUCCESS;
}
static void
cleanup(mrb_state *mrb, struct _args *args)
{
if (args->rfp && args->rfp != stdin)
fclose(args->rfp);
if (!args->fname)
mrb_free(mrb, args->cmdline);
mrb_free(mrb, args->argv);
if (args->libc) {
while (args->libc--) {
mrb_free(mrb, args->libv[args->libc]);
}
mrb_free(mrb, args->libv);
}
mrb_close(mrb);
}
int
main(int argc, char **argv)
{
mrb_state *mrb = mrb_open();
int n = -1;
struct _args args;
mrb_value ARGV;
mrb_value v;
if (!MRB_OPEN_SUCCESS(mrb)) {
if (mrb) {
/* Initialization failed - print exception details */
mrb_print_error(mrb);
mrb_close(mrb);
}
else {
/* Allocation failed */
fprintf(stderr, "%s: Failed to allocate mrb_state, exiting mruby\n", *argv);
}
return EXIT_FAILURE;
}
n = parse_args(mrb, argc, argv, &args);
if (n == EXIT_FAILURE || (args.cmdline == NULL && args.rfp == NULL)) {
cleanup(mrb, &args);
return n;
}
else {
int ai = mrb_gc_arena_save(mrb);
ARGV = mrb_ary_new_capa(mrb, args.argc);
for (int i = 0; i < args.argc; i++) {
char* utf8 = mrb_utf8_from_locale(args.argv[i], -1);
if (utf8) {
mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, utf8));
mrb_utf8_free(utf8);
}
}
mrb_define_global_const(mrb, "ARGV", ARGV);
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug));
mrb_ccontext *c = mrb_ccontext_new(mrb);
if (args.verbose)
c->dump_result = TRUE;
if (args.check_syntax)
c->no_exec = TRUE;
/* Set $0 */
const char *cmdline;
if (args.rfp) {
cmdline = args.cmdline ? args.cmdline : "-";
}
else {
cmdline = "-e";
}
mrb_gv_set(mrb, mrb_intern_lit(mrb, "$0"), mrb_str_new_cstr(mrb, cmdline));
/* Load libraries */
for (int i = 0; i < args.libc; i++) {
FILE *lfp = fopen(args.libv[i], "rb");
if (lfp == NULL) {
fprintf(stderr, "%s: Cannot open library file: %s\n", *argv, args.libv[i]);
mrb_ccontext_free(mrb, c);
cleanup(mrb, &args);
return EXIT_FAILURE;
}
mrb_ccontext_filename(mrb, c, args.libv[i]);
if (mrb_extension_p(args.libv[i])) {
mrb_load_irep_file_cxt(mrb, lfp, c);
}
else {
mrb_load_detect_file_cxt(mrb, lfp, c);
}
fclose(lfp);
mrb_vm_ci_env_clear(mrb, mrb->c->cibase);
mrb_ccontext_cleanup_local_variables(c);
}
/* set program filename */
mrb_ccontext_filename(mrb, c, cmdline);
/* Load program */
if (args.mrbfile || mrb_extension_p(cmdline)) {
v = mrb_load_irep_file_cxt(mrb, args.rfp, c);
}
else if (args.rfp) {
v = mrb_load_detect_file_cxt(mrb, args.rfp, c);
}
else {
char* utf8 = mrb_utf8_from_locale(args.cmdline, -1);
if (!utf8) abort();
v = mrb_load_string_cxt(mrb, utf8, c);
mrb_utf8_free(utf8);
}
mrb_gc_arena_restore(mrb, ai);
mrb_ccontext_free(mrb, c);
if (mrb->exc) {
MRB_EXC_CHECK_EXIT(mrb, mrb->exc);
if (!mrb_undef_p(v)) {
mrb_print_error(mrb);
}
n = EXIT_FAILURE;
}
else if (args.check_syntax) {
puts("Syntax OK");
}
}
cleanup(mrb, &args);
return n;
}