3

I'm currently trying to learn Nim (it's going slowly - can't devote much time to it). On the other hand, in the interests of getting some working code, I'd like to prototype out sections of a Nim app I'm working on in ruby.

Since mruby allows embedding a ruby subset in a C app, and since nim allows compiling arbitrary C code into functions, it feels like this should be relatively straightforward. Has anybody done this?

I'm particularly looking for ways of using Nim's funky macro features to break out into inline ruby code. I'm going to try myself, but I figure someone is bound to have tried it and /or come up with more elegant solutions than I can in my current state of learning :)

user208769
  • 2,216
  • 1
  • 18
  • 27

2 Answers2

3

https://github.com/micklat/NimBorg

This is a project with a somewhat similar goal. It targets python and lua at the moment, but using the same techniques to interface with Ruby shouldn't be too hard.

There are several features in Nim that help in interfacing with a foreign language in a fluent way:

1) Calling Ruby from Nim using Nim's dot operators

These are a bit like method_missing in Ruby. You can define a type like RubyValue in Nim, which will have dot operators that will translate any expression like foo.bar or foo.bar(baz) to the appropriate Ruby method call. The arguments can be passed to a generic function like toRubyValue that can be overloaded for various Nim and C types to automatically convert them to the right Ruby type.

2) Calling Nim from Ruby

In most scripting languages, there is a way to register a foreign type, often described in a particular data structure that has to be populated once per exported type. You can use a bit of generic programming and Nim's .global. vars to automatically create and cache the required data structure for each type that was passed to Ruby through the dot operators. There will be a generic proc like getRubyTypeDesc(T: typedesc) that may rely on typeinfo, typetraits or some overloaded procs supplied by user, defining what has to be exported for the type.

Now, if you really want to rely on mruby (because you have experience with it for example), you can look into using the .emit. pragma to directly output pieces of mruby code. You can then ask the Nim compiler to generate only source code, which you will compile in a second step or you can just change the compiler executable, which Nim will call when compiling the project (this is explained in the same section linked above).

zah
  • 5,314
  • 1
  • 34
  • 31
0

Here's what I've discovered so far.

Fetching the return value from an mruby execution is not as easy as I thought. That said, after much trial and error, this is the simplest way I've found to get some mruby code to execute:

const mrb_cc_flags = "-v -I/mruby_1.2.0_path/include/ -L/mruby_1.2.0_path/build/host/lib/"
const mrb_linker_flags = "-v"
const mrb_obj = "/mruby_1.2.0_path/build/host/lib/libmruby.a"
{. passC: mrb_cc_flags, passL: mrb_linker_flags, link: mrb_obj .}
{.emit: """
  #include <mruby.h>
  #include <mruby/string.h>
""".}

proc ruby_raw(str:cstring):cstring =
  {.emit: """
    mrb_state *mrb = mrb_open();
    if (!mrb) { printf("ERROR: couldn't init mruby\n"); exit(0); }
    mrb_load_string(mrb, `str`);
    `result` = mrb_str_to_cstr(mrb, mrb_funcall(mrb, mrb_top_self(mrb), "test_func", 0));
    mrb_close(mrb);
  """.}

proc ruby*(str:string):string =
  echo ruby_raw("def test_func\n" & str & "\nend")
  "done"

let resp = ruby """
  puts 'this was a puts from within ruby'
  "this is the response"
"""

echo(resp)

I'm pretty sure that you should be able to omit some of the compiler flags at the start of the file in a well configured environment, e.g. by setting LD_LIBRARY_PATH correctly (not least because that would make the code more portable)

Some of the issues I've encountered so far:

  • I'm forced to use mrb_funcall because, for some reason, clang seems to think that the mrb_load_string function returns an int, despite all the c code I can find and the documentation and several people online saying otherwise:

    error: initializing 'mrb_value' (aka 'struct mrb_value') with an expression of incompatible type 'int'
        mrb_value mrb_out = mrb_load_string(mrb, str);
                  ^         ~~~~~~~~~~~~~~~~~~~~~~~~~
    
  • The mruby/string.h header is needed for mrb_str_to_cstr, otherwise you get a segfault. RSTRING_PTR seems to work fine also (which at least gives a sensible error without string.h), but if you write it as a one-liner as above, it will execute the function twice.

I'm going to keep going, write some slightly more idiomatic nim, but this has done what I needed for now.

user208769
  • 2,216
  • 1
  • 18
  • 27