Skip to content

Commit 6a65f0d

Browse files
committed
add "Executing Ruby code with mruby" article
Based upon @bovi's blog post: http://blog.mruby.sh/201207020720.html
1 parent 7f19725 commit 6a65f0d

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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

Comments
 (0)