1 /*
2 ** mirb - Embeddable Interactive Ruby Shell
3 **
4 ** This program takes code from the user in
5 ** an interactive way and executes it
6 ** immediately. It's a REPL...
7 */
8 
9 #include <mruby.h>
10 
11 #ifdef MRB_DISABLE_STDIO
12 # error mruby-bin-mirb conflicts 'MRB_DISABLE_STDIO' configuration in your 'build_config.rb'
13 #endif
14 
15 #include <mruby/array.h>
16 #include <mruby/proc.h>
17 #include <mruby/compile.h>
18 #include <mruby/dump.h>
19 #include <mruby/string.h>
20 #include <mruby/variable.h>
21 #include <mruby/throw.h>
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26 
27 #include <signal.h>
28 #include <setjmp.h>
29 
30 #ifdef ENABLE_READLINE
31 #include <readline/readline.h>
32 #include <readline/history.h>
33 #define MIRB_ADD_HISTORY(line) add_history(line)
34 #define MIRB_READLINE(ch) readline(ch)
35 #if !defined(RL_READLINE_VERSION) || RL_READLINE_VERSION < 0x600
36 /* libedit & older readline do not have rl_free() */
37 #define MIRB_LINE_FREE(line) free(line)
38 #else
39 #define MIRB_LINE_FREE(line) rl_free(line)
40 #endif
41 #define MIRB_WRITE_HISTORY(path) write_history(path)
42 #define MIRB_READ_HISTORY(path) read_history(path)
43 #define MIRB_USING_HISTORY() using_history()
44 #elif defined(ENABLE_LINENOISE)
45 #define ENABLE_READLINE
46 #include <linenoise.h>
47 #define MIRB_ADD_HISTORY(line) linenoiseHistoryAdd(line)
48 #define MIRB_READLINE(ch) linenoise(ch)
49 #define MIRB_LINE_FREE(line) linenoiseFree(line)
50 #define MIRB_WRITE_HISTORY(path) linenoiseHistorySave(path)
51 #define MIRB_READ_HISTORY(path) linenoiseHistoryLoad(history_path)
52 #define MIRB_USING_HISTORY()
53 #endif
54 
55 #ifndef _WIN32
56 #define MIRB_SIGSETJMP(env) sigsetjmp(env, 1)
57 #define MIRB_SIGLONGJMP(env, val) siglongjmp(env, val)
58 #define SIGJMP_BUF sigjmp_buf
59 #else
60 #define MIRB_SIGSETJMP(env) setjmp(env)
61 #define MIRB_SIGLONGJMP(env, val) longjmp(env, val)
62 #define SIGJMP_BUF jmp_buf
63 #endif
64 
65 #ifdef ENABLE_READLINE
66 
67 static const char history_file_name[] = ".mirb_history";
68 
69 static char *
get_history_path(mrb_state * mrb)70 get_history_path(mrb_state *mrb)
71 {
72   char *path = NULL;
73   const char *home = getenv("HOME");
74 
75 #ifdef _WIN32
76   if (home != NULL) {
77     home = getenv("USERPROFILE");
78   }
79 #endif
80 
81   if (home != NULL) {
82     int len = snprintf(NULL, 0, "%s/%s", home, history_file_name);
83     if (len >= 0) {
84       size_t size = len + 1;
85       path = (char *)mrb_malloc_simple(mrb, size);
86       if (path != NULL) {
87         int n = snprintf(path, size, "%s/%s", home, history_file_name);
88         if (n != len) {
89           mrb_free(mrb, path);
90           path = NULL;
91         }
92       }
93     }
94   }
95 
96   return path;
97 }
98 
99 #endif
100 
101 static void
p(mrb_state * mrb,mrb_value obj,int prompt)102 p(mrb_state *mrb, mrb_value obj, int prompt)
103 {
104   mrb_value val;
105   char* msg;
106 
107   val = mrb_funcall(mrb, obj, "inspect", 0);
108   if (prompt) {
109     if (!mrb->exc) {
110       fputs(" => ", stdout);
111     }
112     else {
113       val = mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0);
114     }
115   }
116   if (!mrb_string_p(val)) {
117     val = mrb_obj_as_string(mrb, obj);
118   }
119   msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val));
120   fwrite(msg, strlen(msg), 1, stdout);
121   mrb_locale_free(msg);
122   putc('\n', stdout);
123 }
124 
125 /* Guess if the user might want to enter more
126  * or if he wants an evaluation of his code now */
127 static mrb_bool
is_code_block_open(struct mrb_parser_state * parser)128 is_code_block_open(struct mrb_parser_state *parser)
129 {
130   mrb_bool code_block_open = FALSE;
131 
132   /* check for heredoc */
133   if (parser->parsing_heredoc != NULL) return TRUE;
134 
135   /* check for unterminated string */
136   if (parser->lex_strterm) return TRUE;
137 
138   /* check if parser error are available */
139   if (0 < parser->nerr) {
140     const char unexpected_end[] = "syntax error, unexpected $end";
141     const char *message = parser->error_buffer[0].message;
142 
143     /* a parser error occur, we have to check if */
144     /* we need to read one more line or if there is */
145     /* a different issue which we have to show to */
146     /* the user */
147 
148     if (strncmp(message, unexpected_end, sizeof(unexpected_end) - 1) == 0) {
149       code_block_open = TRUE;
150     }
151     else if (strcmp(message, "syntax error, unexpected keyword_end") == 0) {
152       code_block_open = FALSE;
153     }
154     else if (strcmp(message, "syntax error, unexpected tREGEXP_BEG") == 0) {
155       code_block_open = FALSE;
156     }
157     return code_block_open;
158   }
159 
160   switch (parser->lstate) {
161 
162   /* all states which need more code */
163 
164   case EXPR_BEG:
165     /* beginning of a statement, */
166     /* that means previous line ended */
167     code_block_open = FALSE;
168     break;
169   case EXPR_DOT:
170     /* a message dot was the last token, */
171     /* there has to come more */
172     code_block_open = TRUE;
173     break;
174   case EXPR_CLASS:
175     /* a class keyword is not enough! */
176     /* we need also a name of the class */
177     code_block_open = TRUE;
178     break;
179   case EXPR_FNAME:
180     /* a method name is necessary */
181     code_block_open = TRUE;
182     break;
183   case EXPR_VALUE:
184     /* if, elsif, etc. without condition */
185     code_block_open = TRUE;
186     break;
187 
188   /* now all the states which are closed */
189 
190   case EXPR_ARG:
191     /* an argument is the last token */
192     code_block_open = FALSE;
193     break;
194 
195   /* all states which are unsure */
196 
197   case EXPR_CMDARG:
198     break;
199   case EXPR_END:
200     /* an expression was ended */
201     break;
202   case EXPR_ENDARG:
203     /* closing parenthese */
204     break;
205   case EXPR_ENDFN:
206     /* definition end */
207     break;
208   case EXPR_MID:
209     /* jump keyword like break, return, ... */
210     break;
211   case EXPR_MAX_STATE:
212     /* don't know what to do with this token */
213     break;
214   default:
215     /* this state is unexpected! */
216     break;
217   }
218 
219   return code_block_open;
220 }
221 
222 struct _args {
223   FILE *rfp;
224   mrb_bool verbose      : 1;
225   mrb_bool debug        : 1;
226   int argc;
227   char** argv;
228   int libc;
229   char **libv;
230 };
231 
232 static void
usage(const char * name)233 usage(const char *name)
234 {
235   static const char *const usage_msg[] = {
236   "switches:",
237   "-d           set $DEBUG to true (same as `mruby -d`)",
238   "-r library   same as `mruby -r`",
239   "-v           print version number, then run in verbose mode",
240   "--verbose    run in verbose mode",
241   "--version    print the version",
242   "--copyright  print the copyright",
243   NULL
244   };
245   const char *const *p = usage_msg;
246 
247   printf("Usage: %s [switches] [programfile] [arguments]\n", name);
248   while (*p)
249     printf("  %s\n", *p++);
250 }
251 
252 static char *
dup_arg_item(mrb_state * mrb,const char * item)253 dup_arg_item(mrb_state *mrb, const char *item)
254 {
255   size_t buflen = strlen(item) + 1;
256   char *buf = (char*)mrb_malloc(mrb, buflen);
257   memcpy(buf, item, buflen);
258   return buf;
259 }
260 
261 static int
parse_args(mrb_state * mrb,int argc,char ** argv,struct _args * args)262 parse_args(mrb_state *mrb, int argc, char **argv, struct _args *args)
263 {
264   char **origargv = argv;
265   static const struct _args args_zero = { 0 };
266 
267   *args = args_zero;
268 
269   for (argc--,argv++; argc > 0; argc--,argv++) {
270     char *item;
271     if (argv[0][0] != '-') break;
272 
273     item = argv[0] + 1;
274     switch (*item++) {
275     case 'd':
276       args->debug = TRUE;
277       break;
278     case 'r':
279       if (!item[0]) {
280         if (argc <= 1) {
281           printf("%s: No library specified for -r\n", *origargv);
282           return EXIT_FAILURE;
283         }
284         argc--; argv++;
285         item = argv[0];
286       }
287       if (args->libc == 0) {
288         args->libv = (char**)mrb_malloc(mrb, sizeof(char*));
289       }
290       else {
291         args->libv = (char**)mrb_realloc(mrb, args->libv, sizeof(char*) * (args->libc + 1));
292       }
293       args->libv[args->libc++] = dup_arg_item(mrb, item);
294       break;
295     case 'v':
296       if (!args->verbose) mrb_show_version(mrb);
297       args->verbose = TRUE;
298       break;
299     case '-':
300       if (strcmp((*argv) + 2, "version") == 0) {
301         mrb_show_version(mrb);
302         exit(EXIT_SUCCESS);
303       }
304       else if (strcmp((*argv) + 2, "verbose") == 0) {
305         args->verbose = TRUE;
306         break;
307       }
308       else if (strcmp((*argv) + 2, "copyright") == 0) {
309         mrb_show_copyright(mrb);
310         exit(EXIT_SUCCESS);
311       }
312     default:
313       return EXIT_FAILURE;
314     }
315   }
316 
317   if (args->rfp == NULL) {
318     if (*argv != NULL) {
319       args->rfp = fopen(argv[0], "r");
320       if (args->rfp == NULL) {
321         printf("Cannot open program file. (%s)\n", *argv);
322         return EXIT_FAILURE;
323       }
324       argc--; argv++;
325     }
326   }
327   args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1));
328   memcpy(args->argv, argv, (argc+1) * sizeof(char*));
329   args->argc = argc;
330 
331   return EXIT_SUCCESS;
332 }
333 
334 static void
cleanup(mrb_state * mrb,struct _args * args)335 cleanup(mrb_state *mrb, struct _args *args)
336 {
337   if (args->rfp)
338     fclose(args->rfp);
339   mrb_free(mrb, args->argv);
340   if (args->libc) {
341     while (args->libc--) {
342       mrb_free(mrb, args->libv[args->libc]);
343     }
344     mrb_free(mrb, args->libv);
345   }
346   mrb_close(mrb);
347 }
348 
349 /* Print a short remark for the user */
350 static void
print_hint(void)351 print_hint(void)
352 {
353   printf("mirb - Embeddable Interactive Ruby Shell\n\n");
354 }
355 
356 #ifndef ENABLE_READLINE
357 /* Print the command line prompt of the REPL */
358 static void
print_cmdline(int code_block_open)359 print_cmdline(int code_block_open)
360 {
361   if (code_block_open) {
362     printf("* ");
363   }
364   else {
365     printf("> ");
366   }
367   fflush(stdout);
368 }
369 #endif
370 
371 void mrb_codedump_all(mrb_state*, struct RProc*);
372 
373 static int
check_keyword(const char * buf,const char * word)374 check_keyword(const char *buf, const char *word)
375 {
376   const char *p = buf;
377   size_t len = strlen(word);
378 
379   /* skip preceding spaces */
380   while (*p && ISSPACE(*p)) {
381     p++;
382   }
383   /* check keyword */
384   if (strncmp(p, word, len) != 0) {
385     return 0;
386   }
387   p += len;
388   /* skip trailing spaces */
389   while (*p) {
390     if (!ISSPACE(*p)) return 0;
391     p++;
392   }
393   return 1;
394 }
395 
396 
397 #ifndef ENABLE_READLINE
398 volatile sig_atomic_t input_canceled = 0;
399 void
ctrl_c_handler(int signo)400 ctrl_c_handler(int signo)
401 {
402   input_canceled = 1;
403 }
404 #else
405 SIGJMP_BUF ctrl_c_buf;
406 void
ctrl_c_handler(int signo)407 ctrl_c_handler(int signo)
408 {
409   MIRB_SIGLONGJMP(ctrl_c_buf, 1);
410 }
411 #endif
412 
413 #ifndef DISABLE_MIRB_UNDERSCORE
decl_lv_underscore(mrb_state * mrb,mrbc_context * cxt)414 void decl_lv_underscore(mrb_state *mrb, mrbc_context *cxt)
415 {
416   struct RProc *proc;
417   struct mrb_parser_state *parser;
418 
419   parser = mrb_parse_string(mrb, "_=nil", cxt);
420   if (parser == NULL) {
421     fputs("create parser state error\n", stderr);
422     mrb_close(mrb);
423     exit(EXIT_FAILURE);
424   }
425 
426   proc = mrb_generate_code(mrb, parser);
427   mrb_vm_run(mrb, proc, mrb_top_self(mrb), 0);
428 
429   mrb_parser_free(parser);
430 }
431 #endif
432 
433 int
main(int argc,char ** argv)434 main(int argc, char **argv)
435 {
436   char ruby_code[4096] = { 0 };
437   char last_code_line[1024] = { 0 };
438 #ifndef ENABLE_READLINE
439   int last_char;
440   size_t char_index;
441 #else
442   char *history_path;
443   char* line;
444 #endif
445   mrbc_context *cxt;
446   struct mrb_parser_state *parser;
447   mrb_state *mrb;
448   mrb_value result;
449   struct _args args;
450   mrb_value ARGV;
451   int n;
452   int i;
453   mrb_bool code_block_open = FALSE;
454   int ai;
455   unsigned int stack_keep = 0;
456 
457   /* new interpreter instance */
458   mrb = mrb_open();
459   if (mrb == NULL) {
460     fputs("Invalid mrb interpreter, exiting mirb\n", stderr);
461     return EXIT_FAILURE;
462   }
463 
464   n = parse_args(mrb, argc, argv, &args);
465   if (n == EXIT_FAILURE) {
466     cleanup(mrb, &args);
467     usage(argv[0]);
468     return n;
469   }
470 
471   ARGV = mrb_ary_new_capa(mrb, args.argc);
472   for (i = 0; i < args.argc; i++) {
473     char* utf8 = mrb_utf8_from_locale(args.argv[i], -1);
474     if (utf8) {
475       mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, utf8));
476       mrb_utf8_free(utf8);
477     }
478   }
479   mrb_define_global_const(mrb, "ARGV", ARGV);
480   mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug));
481 
482 #ifdef ENABLE_READLINE
483   history_path = get_history_path(mrb);
484   if (history_path == NULL) {
485     fputs("failed to get history path\n", stderr);
486     mrb_close(mrb);
487     return EXIT_FAILURE;
488   }
489 
490   MIRB_USING_HISTORY();
491   MIRB_READ_HISTORY(history_path);
492 #endif
493 
494   print_hint();
495 
496   cxt = mrbc_context_new(mrb);
497 
498 #ifndef DISABLE_MIRB_UNDERSCORE
499   decl_lv_underscore(mrb, cxt);
500 #endif
501 
502   /* Load libraries */
503   for (i = 0; i < args.libc; i++) {
504     FILE *lfp = fopen(args.libv[i], "r");
505     if (lfp == NULL) {
506       printf("Cannot open library file. (%s)\n", args.libv[i]);
507       cleanup(mrb, &args);
508       return EXIT_FAILURE;
509     }
510     mrb_load_file_cxt(mrb, lfp, cxt);
511     fclose(lfp);
512   }
513 
514   cxt->capture_errors = TRUE;
515   cxt->lineno = 1;
516   mrbc_filename(mrb, cxt, "(mirb)");
517   if (args.verbose) cxt->dump_result = TRUE;
518 
519   ai = mrb_gc_arena_save(mrb);
520 
521   while (TRUE) {
522     char *utf8;
523     struct mrb_jmpbuf c_jmp;
524 
525     MRB_TRY(&c_jmp);
526     mrb->jmp = &c_jmp;
527     if (args.rfp) {
528       if (fgets(last_code_line, sizeof(last_code_line)-1, args.rfp) != NULL)
529         goto done;
530       break;
531     }
532 
533 #ifndef ENABLE_READLINE
534     print_cmdline(code_block_open);
535 
536     signal(SIGINT, ctrl_c_handler);
537     char_index = 0;
538     while ((last_char = getchar()) != '\n') {
539       if (last_char == EOF) break;
540       if (char_index >= sizeof(last_code_line)-2) {
541         fputs("input string too long\n", stderr);
542         continue;
543       }
544       last_code_line[char_index++] = last_char;
545     }
546     signal(SIGINT, SIG_DFL);
547     if (input_canceled) {
548       ruby_code[0] = '\0';
549       last_code_line[0] = '\0';
550       code_block_open = FALSE;
551       puts("^C");
552       input_canceled = 0;
553       continue;
554     }
555     if (last_char == EOF) {
556       fputs("\n", stdout);
557       break;
558     }
559 
560     last_code_line[char_index++] = '\n';
561     last_code_line[char_index] = '\0';
562 #else
563     if (MIRB_SIGSETJMP(ctrl_c_buf) == 0) {
564       ;
565     }
566     else {
567       ruby_code[0] = '\0';
568       last_code_line[0] = '\0';
569       code_block_open = FALSE;
570       puts("^C");
571     }
572     signal(SIGINT, ctrl_c_handler);
573     line = MIRB_READLINE(code_block_open ? "* " : "> ");
574     signal(SIGINT, SIG_DFL);
575 
576     if (line == NULL) {
577       printf("\n");
578       break;
579     }
580     if (strlen(line) > sizeof(last_code_line)-2) {
581       fputs("input string too long\n", stderr);
582       continue;
583     }
584     strcpy(last_code_line, line);
585     strcat(last_code_line, "\n");
586     MIRB_ADD_HISTORY(line);
587     MIRB_LINE_FREE(line);
588 #endif
589 
590   done:
591     if (code_block_open) {
592       if (strlen(ruby_code)+strlen(last_code_line) > sizeof(ruby_code)-1) {
593         fputs("concatenated input string too long\n", stderr);
594         continue;
595       }
596       strcat(ruby_code, last_code_line);
597     }
598     else {
599       if (check_keyword(last_code_line, "quit") || check_keyword(last_code_line, "exit")) {
600         break;
601       }
602       strcpy(ruby_code, last_code_line);
603     }
604 
605     utf8 = mrb_utf8_from_locale(ruby_code, -1);
606     if (!utf8) abort();
607 
608     /* parse code */
609     parser = mrb_parser_new(mrb);
610     if (parser == NULL) {
611       fputs("create parser state error\n", stderr);
612       break;
613     }
614     parser->s = utf8;
615     parser->send = utf8 + strlen(utf8);
616     parser->lineno = cxt->lineno;
617     mrb_parser_parse(parser, cxt);
618     code_block_open = is_code_block_open(parser);
619     mrb_utf8_free(utf8);
620 
621     if (code_block_open) {
622       /* no evaluation of code */
623     }
624     else {
625       if (0 < parser->nwarn) {
626         /* warning */
627         char* msg = mrb_locale_from_utf8(parser->warn_buffer[0].message, -1);
628         printf("line %d: %s\n", parser->warn_buffer[0].lineno, msg);
629         mrb_locale_free(msg);
630       }
631       if (0 < parser->nerr) {
632         /* syntax error */
633         char* msg = mrb_locale_from_utf8(parser->error_buffer[0].message, -1);
634         printf("line %d: %s\n", parser->error_buffer[0].lineno, msg);
635         mrb_locale_free(msg);
636       }
637       else {
638         /* generate bytecode */
639         struct RProc *proc = mrb_generate_code(mrb, parser);
640         if (proc == NULL) {
641           fputs("codegen error\n", stderr);
642           mrb_parser_free(parser);
643           break;
644         }
645 
646         if (args.verbose) {
647           mrb_codedump_all(mrb, proc);
648         }
649         /* adjust stack length of toplevel environment */
650         if (mrb->c->cibase->env) {
651           struct REnv *e = mrb->c->cibase->env;
652           if (e && MRB_ENV_LEN(e) < proc->body.irep->nlocals) {
653             MRB_ENV_SET_LEN(e, proc->body.irep->nlocals);
654           }
655         }
656         /* pass a proc for evaluation */
657         /* evaluate the bytecode */
658         result = mrb_vm_run(mrb,
659             proc,
660             mrb_top_self(mrb),
661             stack_keep);
662         stack_keep = proc->body.irep->nlocals;
663         /* did an exception occur? */
664         if (mrb->exc) {
665           p(mrb, mrb_obj_value(mrb->exc), 0);
666           mrb->exc = 0;
667         }
668         else {
669           /* no */
670           if (!mrb_respond_to(mrb, result, mrb_intern_lit(mrb, "inspect"))){
671             result = mrb_any_to_s(mrb, result);
672           }
673           p(mrb, result, 1);
674 #ifndef DISABLE_MIRB_UNDERSCORE
675           *(mrb->c->stack + 1) = result;
676 #endif
677         }
678       }
679       ruby_code[0] = '\0';
680       last_code_line[0] = '\0';
681       mrb_gc_arena_restore(mrb, ai);
682     }
683     mrb_parser_free(parser);
684     cxt->lineno++;
685     MRB_CATCH(&c_jmp) {
686       p(mrb, mrb_obj_value(mrb->exc), 0);
687       mrb->exc = 0;
688     }
689     MRB_END_EXC(&c_jmp);
690   }
691 
692 #ifdef ENABLE_READLINE
693   MIRB_WRITE_HISTORY(history_path);
694   mrb_free(mrb, history_path);
695 #endif
696 
697   if (args.rfp) fclose(args.rfp);
698   mrb_free(mrb, args.argv);
699   if (args.libv) {
700     for (i = 0; i < args.libc; ++i) {
701       mrb_free(mrb, args.libv[i]);
702     }
703     mrb_free(mrb, args.libv);
704   }
705   mrbc_context_free(mrb, cxt);
706   mrb_close(mrb);
707 
708   return 0;
709 }
710