|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Executing Ruby code with mruby |
| 4 | +categories: articles |
| 5 | +--- |
| 6 | + |
| 7 | +# Executing Ruby code with mruby |
| 8 | + |
| 9 | +**originally written by [Daniel Bovensiepen](http://bovensiepen.net/)** |
| 10 | + |
| 11 | +The traditional way to execute Ruby code is distributing the plain source code and requiring a Ruby interpreter with lots of files to be installed. mruby can be used like that too. So if you have experience with another Ruby implementation you may know some of the examples listed here. However with mruby you can also build standalone programs and compileto bytecode which can be dumped and loaded. |
| 12 | + |
| 13 | +A file called `test_program.rb` is used in some of the examples. Its content is: |
| 14 | + |
| 15 | +~~~ruby |
| 16 | +puts 'hello world' |
| 17 | +~~~ |
| 18 | + |
| 19 | +## REPL (mirb) |
| 20 | + |
| 21 | +Not exactly directly used for program execution but still, Ruby code can be evaluated by using the `mirb` program: |
| 22 | + |
| 23 | +~~~ |
| 24 | +$ mruby/bin/mirb |
| 25 | +mirb - Embeddable Interactive Ruby Shell |
| 26 | +
|
| 27 | +> puts 'hello world' |
| 28 | +hello world |
| 29 | + => nil |
| 30 | +~~~ |
| 31 | + |
| 32 | +### Pros & Cons |
| 33 | + |
| 34 | +✔ direct feedback without any indirection like files |
| 35 | + |
| 36 | +✘ not usable for productive execution |
| 37 | + |
| 38 | +✘ the input needs to be parsed twice: first `mirb` checks if the code is complete and afterwards `mirb` compiles it into bytecode and executes it |
| 39 | + |
| 40 | +## Source code (.rb) |
| 41 | + |
| 42 | +The probably most common way to run Ruby code is by passing a filename as argument to an interpreter. In mruby that's the `mruby` program: |
| 43 | + |
| 44 | +~~~ |
| 45 | +$ mruby/bin/mruby test_program.rb |
| 46 | +hello world |
| 47 | +~~~ |
| 48 | + |
| 49 | +### Pros & Cons |
| 50 | + |
| 51 | +✔ very simple development cycle: programming → testing → programming |
| 52 | + |
| 53 | +✘ Ruby code has to be provided to users |
| 54 | + |
| 55 | +✘ the `mruby` program and a file system is required |
| 56 | + |
| 57 | +✘ Ruby code has to be parsed and compiled to bytecode before its execution |
| 58 | + |
| 59 | +## Source code (.c) |
| 60 | + |
| 61 | +Ruby code can also be written as a C string. This is similar to the `-e` switch of the `mruby` program. |
| 62 | + |
| 63 | +~~~c |
| 64 | +#include "mruby.h" |
| 65 | +#include "mruby/compile.h" |
| 66 | + |
| 67 | +int |
| 68 | +main(void) |
| 69 | +{ |
| 70 | + mrb_state *mrb = mrb_open(); |
| 71 | + if (!mrb) { /* handle error */ } |
| 72 | + // mrb_load_nstring() for strings without null terminator or known length |
| 73 | + mrb_load_string(mrb, "puts 'hello world'"); |
| 74 | + mrb_close(mrb); |
| 75 | +} |
| 76 | +~~~ |
| 77 | +
|
| 78 | +To compile and link: |
| 79 | +
|
| 80 | +~~~ |
| 81 | +$ gcc -std=c99 -Imruby/include test_program.c -o test_program test_program.o mruby/build/host/lib/libmruby.a |
| 82 | +~~~ |
| 83 | +
|
| 84 | +To execute: |
| 85 | +
|
| 86 | +~~~ |
| 87 | +$ ./test_program |
| 88 | +hello world |
| 89 | +~~~ |
| 90 | +
|
| 91 | +### Pros & Cons |
| 92 | +
|
| 93 | +✔ simple development cycle: programming → compiling (`gcc`) → testing → programming |
| 94 | +
|
| 95 | +✔ the program is fully standalone |
| 96 | +
|
| 97 | +✘ additional boilerplate is needed to get the program up and running |
| 98 | +
|
| 99 | +✘ Ruby code has to be parsed and compiled to bytecode before its execution |
| 100 | +
|
| 101 | +✘ updating the Ruby code might require a recompilation of the C code or an advanced updating mechanism |
| 102 | +
|
| 103 | +## Bytecode (.mrb) |
| 104 | +
|
| 105 | +mruby provides a Java-like execution style by compiling to an intermediate representation form which then will be executed. |
| 106 | +
|
| 107 | +The first step is to compile source code to bytecode with the `mrbc` program: |
| 108 | +
|
| 109 | +~~~ |
| 110 | +$ mruby/bin/mrbc test_program.rb |
| 111 | +~~~ |
| 112 | +
|
| 113 | +This will produce a file called `test_program.mrb` which contains the intermediate representation of the previously given Ruby code: |
| 114 | +
|
| 115 | +~~~ |
| 116 | +$ hexdump -C test_program.mrb |
| 117 | +00000000 52 49 54 45 30 30 30 33 e1 c0 00 00 00 65 4d 41 |RITE0003.....eMA| |
| 118 | +00000010 54 5a 30 30 30 30 49 52 45 50 00 00 00 47 30 30 |TZ0000IREP...G00| |
| 119 | +00000020 30 30 00 00 00 3f 00 01 00 04 00 00 00 00 00 04 |00...?..........| |
| 120 | +00000030 00 80 00 06 01 00 00 3d 00 80 00 a0 00 00 00 4a |.......=.......J| |
| 121 | +00000040 00 00 00 01 00 00 0b 68 65 6c 6c 6f 20 77 6f 72 |.......hello wor| |
| 122 | +00000050 6c 64 00 00 00 01 00 04 70 75 74 73 00 45 4e 44 |ld......puts.END| |
| 123 | +00000060 00 00 00 00 08 |.....| |
| 124 | +00000065 |
| 125 | +~~~ |
| 126 | +
|
| 127 | +This file can be executed by the `mruby` program or the `mrb_load_irep_file()` function. The `-b` switch tells the program that the file is binary rather than plain Ruby code: |
| 128 | +
|
| 129 | +~~~ |
| 130 | +$ mruby/bin/mruby -b test_program.mrb |
| 131 | +hello world |
| 132 | +~~~ |
| 133 | +
|
| 134 | +### Pros & Cons |
| 135 | +
|
| 136 | +✔ Ruby code doesn't have to be provided to users |
| 137 | +
|
| 138 | +✔ no Ruby code has to be parsed |
| 139 | +
|
| 140 | +✔ bytecode can easily be updated by replacing the file |
| 141 | +
|
| 142 | +✘ complex development cycle: programming → compiling (`mrbc`) → testing (`mruby`) → programming |
| 143 | +
|
| 144 | +✘ the `mruby` program and a file system is required |
| 145 | +
|
| 146 | +## Bytecode (.c) |
| 147 | +
|
| 148 | +This variant is interesting for those who want to integrate Ruby code directly into their C code. It will create a C array containg the bytecode which you then have to execute by yourself. |
| 149 | +
|
| 150 | +The first step is to compile the Ruby program. This is done by using `mrbc` and its `-B` switch. An identifier for the array also has to be given: |
| 151 | +
|
| 152 | +~~~ |
| 153 | +$ mruby/bin/mrbc -Btest_symbol test_program.rb |
| 154 | +~~~ |
| 155 | +
|
| 156 | +This will create a C file called `test_program.c` containing the `test_symbol` array: |
| 157 | +
|
| 158 | +~~~c |
| 159 | +/* dumped in little endian order. |
| 160 | + use `mrbc -E` option for big endian CPU. */ |
| 161 | +#include <stdint.h> |
| 162 | +const uint8_t |
| 163 | +#if defined __GNUC__ |
| 164 | +__attribute__((aligned(4))) |
| 165 | +#elif defined _MSC_VER |
| 166 | +__declspec(align(4)) |
| 167 | +#endif |
| 168 | +test_symbol[] = { |
| 169 | +0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x33,0x73,0x0d,0x00,0x00,0x00,0x65,0x4d,0x41, |
| 170 | +0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x47,0x30,0x30, |
| 171 | +0x30,0x30,0x00,0x00,0x00,0x3f,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x04, |
| 172 | +0x06,0x00,0x80,0x00,0x3d,0x00,0x00,0x01,0xa0,0x00,0x80,0x00,0x4a,0x00,0x00,0x00, |
| 173 | +0x00,0x00,0x00,0x01,0x00,0x00,0x0b,0x68,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72, |
| 174 | +0x6c,0x64,0x00,0x00,0x00,0x01,0x00,0x04,0x70,0x75,0x74,0x73,0x00,0x45,0x4e,0x44, |
| 175 | +0x00,0x00,0x00,0x00,0x08, |
| 176 | +}; |
| 177 | +~~~ |
| 178 | + |
| 179 | +To execute this bytecode the following boilerplate has to be written. It reads the array and executes the bytecode immediately: |
| 180 | + |
| 181 | +~~~c |
| 182 | +#include "mruby.h" |
| 183 | +#include "mruby/irep.h" |
| 184 | + |
| 185 | +int |
| 186 | +main(void) |
| 187 | +{ |
| 188 | + mrb_state *mrb = mrb_open(); |
| 189 | + if (!mrb) { /* handle error */ } |
| 190 | + mrb_load_irep(mrb, test_symbol); |
| 191 | + mrb_close(mrb); |
| 192 | +} |
| 193 | +~~~ |
| 194 | +
|
| 195 | +To compile and link: |
| 196 | +
|
| 197 | +~~~ |
| 198 | +$ gcc -std=c99 -Imruby/include test_program.c -o test_program test_program.o mruby/build/host/lib/libmruby.a |
| 199 | +~~~ |
| 200 | +
|
| 201 | +To execute: |
| 202 | +
|
| 203 | +~~~ |
| 204 | +$ ./test_program |
| 205 | +hello world |
| 206 | +~~~ |
| 207 | +
|
| 208 | +### Pros & Cons |
| 209 | +
|
| 210 | +✔ Ruby code doesn't have to be provided to users |
| 211 | +
|
| 212 | +✔ no Ruby code has to be parsed |
| 213 | +
|
| 214 | +✔ the program is fully standalone |
| 215 | +
|
| 216 | +✘ even more complex development cycle: programming → compiling (`mrbc`) → integrating C code → compiling (`gcc`) → testing → programming |
| 217 | +
|
| 218 | +✘ additional boilerplate is needed to get the program up and running |
| 219 | +
|
| 220 | +✘ updating the bytecode requires a recompilation of the C code or an advanced updating mechanism |
| 221 | +
|
0 commit comments