1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <mruby.h>
5 #include <mruby/compile.h>
6 #include <mruby/dump.h>
7 #include <mruby/proc.h>
8 
9 #define RITEBIN_EXT ".mrb"
10 #define C_EXT       ".c"
11 
12 struct mrbc_args {
13   int argc;
14   char **argv;
15   int idx;
16   const char *prog;
17   const char *outfile;
18   const char *initname;
19   mrb_bool check_syntax : 1;
20   mrb_bool verbose      : 1;
21   mrb_bool remove_lv    : 1;
22   unsigned int flags    : 4;
23 };
24 
25 static void
usage(const char * name)26 usage(const char *name)
27 {
28   static const char *const usage_msg[] = {
29   "switches:",
30   "-c           check syntax only",
31   "-o<outfile>  place the output into <outfile>",
32   "-v           print version number, then turn on verbose mode",
33   "-g           produce debugging information",
34   "-B<symbol>   binary <symbol> output in C language format",
35   "-e           generate little endian iseq data",
36   "-E           generate big endian iseq data",
37   "--remove-lv  remove local variables",
38   "--verbose    run at verbose mode",
39   "--version    print the version",
40   "--copyright  print the copyright",
41   NULL
42   };
43   const char *const *p = usage_msg;
44 
45   printf("Usage: %s [switches] programfile\n", name);
46   while (*p)
47     printf("  %s\n", *p++);
48 }
49 
50 static char *
get_outfilename(mrb_state * mrb,char * infile,const char * ext)51 get_outfilename(mrb_state *mrb, char *infile, const char *ext)
52 {
53   size_t infilelen;
54   size_t extlen;
55   char *outfile;
56   char *p;
57 
58   infilelen = strlen(infile);
59   extlen = strlen(ext);
60   outfile = (char*)mrb_malloc(mrb, infilelen + extlen + 1);
61   memcpy(outfile, infile, infilelen + 1);
62   if (*ext) {
63     if ((p = strrchr(outfile, '.')) == NULL)
64       p = outfile + infilelen;
65     memcpy(p, ext, extlen + 1);
66   }
67 
68   return outfile;
69 }
70 
71 static int
parse_args(mrb_state * mrb,int argc,char ** argv,struct mrbc_args * args)72 parse_args(mrb_state *mrb, int argc, char **argv, struct mrbc_args *args)
73 {
74   char *outfile = NULL;
75   static const struct mrbc_args args_zero = { 0 };
76   int i;
77 
78   *args = args_zero;
79   args->argc = argc;
80   args->argv = argv;
81   args->prog = argv[0];
82 
83   for (i=1; i<argc; i++) {
84     if (argv[i][0] == '-') {
85       switch ((argv[i])[1]) {
86       case 'o':
87         if (args->outfile) {
88           fprintf(stderr, "%s: an output file is already specified. (%s)\n",
89                   args->prog, outfile);
90           return -1;
91         }
92         if (argv[i][2] == '\0' && argv[i+1]) {
93           i++;
94           args->outfile = get_outfilename(mrb, argv[i], "");
95         }
96         else {
97           args->outfile = get_outfilename(mrb, argv[i] + 2, "");
98         }
99         break;
100       case 'B':
101         if (argv[i][2] == '\0' && argv[i+1]) {
102           i++;
103           args->initname = argv[i];
104         }
105         else {
106           args->initname = argv[i]+2;
107         }
108         if (*args->initname == '\0') {
109           fprintf(stderr, "%s: function name is not specified.\n", args->prog);
110           return -1;
111         }
112         break;
113       case 'c':
114         args->check_syntax = TRUE;
115         break;
116       case 'v':
117         if (!args->verbose) mrb_show_version(mrb);
118         args->verbose = TRUE;
119         break;
120       case 'g':
121         args->flags |= DUMP_DEBUG_INFO;
122         break;
123       case 'E':
124         args->flags = DUMP_ENDIAN_BIG | (args->flags & ~DUMP_ENDIAN_MASK);
125         break;
126       case 'e':
127         args->flags = DUMP_ENDIAN_LIL | (args->flags & ~DUMP_ENDIAN_MASK);
128         break;
129       case 'h':
130         return -1;
131       case '-':
132         if (argv[i][1] == '\n') {
133           return i;
134         }
135         if (strcmp(argv[i] + 2, "version") == 0) {
136           mrb_show_version(mrb);
137           exit(EXIT_SUCCESS);
138         }
139         else if (strcmp(argv[i] + 2, "verbose") == 0) {
140           args->verbose = TRUE;
141           break;
142         }
143         else if (strcmp(argv[i] + 2, "copyright") == 0) {
144           mrb_show_copyright(mrb);
145           exit(EXIT_SUCCESS);
146         }
147         else if (strcmp(argv[i] + 2, "remove-lv") == 0) {
148           args->remove_lv = TRUE;
149           break;
150         }
151         return -1;
152       default:
153         return i;
154       }
155     }
156     else {
157       break;
158     }
159   }
160   if (args->verbose && args->initname && (args->flags & DUMP_ENDIAN_MASK) == 0) {
161     fprintf(stderr, "%s: generating %s endian C file. specify -e/-E for cross compiling.\n",
162             args->prog, bigendian_p() ? "big" : "little");
163   }
164   return i;
165 }
166 
167 static void
cleanup(mrb_state * mrb,struct mrbc_args * args)168 cleanup(mrb_state *mrb, struct mrbc_args *args)
169 {
170   mrb_free(mrb, (void*)args->outfile);
171   mrb_close(mrb);
172 }
173 
174 static int
partial_hook(struct mrb_parser_state * p)175 partial_hook(struct mrb_parser_state *p)
176 {
177   mrbc_context *c = p->cxt;
178   struct mrbc_args *args = (struct mrbc_args *)c->partial_data;
179   const char *fn;
180 
181   if (p->f) fclose(p->f);
182   if (args->idx >= args->argc) {
183     p->f = NULL;
184     return -1;
185   }
186   fn = args->argv[args->idx++];
187   p->f = fopen(fn, "r");
188   if (p->f == NULL) {
189     fprintf(stderr, "%s: cannot open program file. (%s)\n", args->prog, fn);
190     return -1;
191   }
192   mrb_parser_set_filename(p, fn);
193   return 0;
194 }
195 
196 static mrb_value
load_file(mrb_state * mrb,struct mrbc_args * args)197 load_file(mrb_state *mrb, struct mrbc_args *args)
198 {
199   mrbc_context *c;
200   mrb_value result;
201   char *input = args->argv[args->idx];
202   FILE *infile;
203   mrb_bool need_close = FALSE;
204 
205   c = mrbc_context_new(mrb);
206   if (args->verbose)
207     c->dump_result = TRUE;
208   c->no_exec = TRUE;
209   if (input[0] == '-' && input[1] == '\0') {
210     infile = stdin;
211   }
212   else {
213     need_close = TRUE;
214     if ((infile = fopen(input, "r")) == NULL) {
215       fprintf(stderr, "%s: cannot open program file. (%s)\n", args->prog, input);
216       return mrb_nil_value();
217     }
218   }
219   mrbc_filename(mrb, c, input);
220   args->idx++;
221   if (args->idx < args->argc) {
222     need_close = FALSE;
223     mrbc_partial_hook(mrb, c, partial_hook, (void*)args);
224   }
225 
226   result = mrb_load_file_cxt(mrb, infile, c);
227   if (need_close) fclose(infile);
228   mrbc_context_free(mrb, c);
229   if (mrb_undef_p(result)) {
230     return mrb_nil_value();
231   }
232   return result;
233 }
234 
235 static int
dump_file(mrb_state * mrb,FILE * wfp,const char * outfile,struct RProc * proc,struct mrbc_args * args)236 dump_file(mrb_state *mrb, FILE *wfp, const char *outfile, struct RProc *proc, struct mrbc_args *args)
237 {
238   int n = MRB_DUMP_OK;
239   mrb_irep *irep = proc->body.irep;
240 
241   if (args->remove_lv) {
242     mrb_irep_remove_lv(mrb, irep);
243   }
244   if (args->initname) {
245     n = mrb_dump_irep_cfunc(mrb, irep, args->flags, wfp, args->initname);
246     if (n == MRB_DUMP_INVALID_ARGUMENT) {
247       fprintf(stderr, "%s: invalid C language symbol name\n", args->initname);
248     }
249   }
250   else {
251     n = mrb_dump_irep_binary(mrb, irep, args->flags, wfp);
252   }
253   if (n != MRB_DUMP_OK) {
254     fprintf(stderr, "%s: error in mrb dump (%s) %d\n", args->prog, outfile, n);
255   }
256   return n;
257 }
258 
259 int
main(int argc,char ** argv)260 main(int argc, char **argv)
261 {
262   mrb_state *mrb = mrb_open();
263   int n, result;
264   struct mrbc_args args;
265   FILE *wfp;
266   mrb_value load;
267 
268   if (mrb == NULL) {
269     fputs("Invalid mrb_state, exiting mrbc\n", stderr);
270     return EXIT_FAILURE;
271   }
272 
273   n = parse_args(mrb, argc, argv, &args);
274   if (n < 0) {
275     cleanup(mrb, &args);
276     usage(argv[0]);
277     return EXIT_FAILURE;
278   }
279   if (n == argc) {
280     fprintf(stderr, "%s: no program file given\n", args.prog);
281     return EXIT_FAILURE;
282   }
283   if (args.outfile == NULL && !args.check_syntax) {
284     if (n + 1 == argc) {
285       args.outfile = get_outfilename(mrb, argv[n], args.initname ? C_EXT : RITEBIN_EXT);
286     }
287     else {
288       fprintf(stderr, "%s: output file should be specified to compile multiple files\n", args.prog);
289       return EXIT_FAILURE;
290     }
291   }
292 
293   args.idx = n;
294   load = load_file(mrb, &args);
295   if (mrb_nil_p(load)) {
296     cleanup(mrb, &args);
297     return EXIT_FAILURE;
298   }
299   if (args.check_syntax) {
300     printf("%s:%s:Syntax OK\n", args.prog, argv[n]);
301   }
302 
303   if (args.check_syntax) {
304     cleanup(mrb, &args);
305     return EXIT_SUCCESS;
306   }
307 
308   if (args.outfile) {
309     if (strcmp("-", args.outfile) == 0) {
310       wfp = stdout;
311     }
312     else if ((wfp = fopen(args.outfile, "wb")) == NULL) {
313       fprintf(stderr, "%s: cannot open output file:(%s)\n", args.prog, args.outfile);
314       return EXIT_FAILURE;
315     }
316   }
317   else {
318     fprintf(stderr, "Output file is required\n");
319     return EXIT_FAILURE;
320   }
321   result = dump_file(mrb, wfp, args.outfile, mrb_proc_ptr(load), &args);
322   fclose(wfp);
323   cleanup(mrb, &args);
324   if (result != MRB_DUMP_OK) {
325     return EXIT_FAILURE;
326   }
327   return EXIT_SUCCESS;
328 }
329 
330 void
mrb_init_mrblib(mrb_state * mrb)331 mrb_init_mrblib(mrb_state *mrb)
332 {
333 }
334 
335 #ifndef DISABLE_GEMS
336 void
mrb_init_mrbgems(mrb_state * mrb)337 mrb_init_mrbgems(mrb_state *mrb)
338 {
339 }
340 
341 void
mrb_final_mrbgems(mrb_state * mrb)342 mrb_final_mrbgems(mrb_state *mrb)
343 {
344 }
345 #endif
346