2

I'm getting started with mruby. I'm also pretty new to programming in C, so I may not be familiar with many of the basics. I was able to compile the example mruby program which loads the ruby code from a string. Now I would like to load it from an external file. Here's the example code that I'm using:

#include <stdlib.h>
#include <stdio.h>
#include <mruby.h>

static mrb_value my_c_method(mrb_state *mrb, mrb_value value)
{
  puts("Called my C method");
  return value;
}

int main(void)
{
  mrb_state *mrb = mrb_open();

  struct RClass *mymodule = mrb_define_module(mrb, "MyModule");
  mrb_define_class_method(mrb, mymodule, "my_c_method", my_c_method, MRB_ARGS_NONE());

  mrb_load_string(mrb, "MyModule.my_c_method"); // move this to an external file
  return 0;
}

As you can see in the example above, I would like to move the ruby code to an external file. I'm a little confused as to whether or not I can simply "include" the ruby file, or if I need to precompile it into an .mrb file. Even then I'm not sure how to include that .mrb file when compiling.

What do I need to change to be able to load the ruby code from an external file?

Community
  • 1
  • 1
Andrew
  • 227,796
  • 193
  • 515
  • 708

3 Answers3

1

Assuming your code is stored in file foo, all you need is to open the file and read it. To open the file, you need fopen(), defined in stdio.h. You can then read it line by line using fgets(). I'm not familiar with mruby, so I'm not exactly sure if mrb_load_string expects that every mruby code is in a single line. I will assume so. Here's how you can do it:

#define MAX_CODE_SIZE 128
FILE *code;
char code_buf[128]
code = fopen("foo", "r");
if (code == NULL) {
    /* File couldn't be opened, handle error case... */
}
fgets(code_buf, MAX_CODE_SIZE, code);
/* Do some work ... */

fclose(code); /* Don't forget to close the file */

This piece of code reads the first line of file foo, up to a limit of 127 characters (including newline), and stores it in code_buf. You can then call mrb_load_string:

mrb_load_string(mrb, code);

I'm not sure if this is what you wanted, I never touched mruby, but from what I saw, mrb_load_string expects a char * with the code, and you want that to come from a file. That's how you do it.

If you want to read a file with code in multiple lines, you have no choice but to allocate a large enough buffer and read it using fread():

#include <stdio.h>
#define MAX_LINE_LENGTH 128
#define MAX_LINES 256
#define MAX_FILE_SIZE MAX_LINE_LENGTH*MAX_LINES

char code[MAX_FILE_SIZE];

int read_code(char *filepath) {
    FILE *fp = fopen(filepath, "r");
    if (fp == NULL)
        return 0;
    fread(code, 1, MAX_FILE_SIZE, fp);
    fclose(fp);
    return 1;
}

This function reads the whole file (assuming it doesn't exceed our buffer limits). code is global because you can easily reach the stack capacity if you allocate large local variables (another alternative is to use dynamic allocation). When you call read_code(), you should make sure to check its return value, to check for possible errors upon opening the file. Also, you can play with fread()'s return value to know if the buffer size wasn't enough to read everything.

Just make sure you don't forget to close the file when you're done.

EDIT: For the fgets() version, please note that in case the line holds less than 128 characters, newline will be retained in code_buf. You may want to set code_buf[strlen(code_buf)-1] to '\0' if that's the case.

UPDATE:

From our discussion on the comments below, I am updating my answer with a rudimentary parser to enable you to read the ruby file at compile time. Basically, the parser will read your ruby file and generate an output file with valid C code that inserts the file's content in a char array. Special characters are accordingly escaped. Here it is:

#include <stdio.h>

void inline insert(int, FILE *);

int main(int argc, char *argv[]) {
    FILE *out, *in;
    int c;
    if (argc != 3) {
        printf("Usage: %s <input_file> <output_file>\n", argv[0]);
        return 1;
    }

    in = fopen(argv[1], "r");
    out = fopen(argv[2], "w");

    if (out == NULL) {
        printf("Unable to create or write to %s\n", argv[1]);
        return 1;
    }

    if (in == NULL) {
        printf("Unable to read %s\n", argv[1]);
        return 1;
    }

    fputs("#ifndef MRUBY_CODE_FILE_GUARD\n", out);
    fputs("#define MRUBY_CODE_FILE_GUARD\n", out);
    fputs("char mruby_code[] = \"", out);
    while ((c = getc(in)) != EOF)
        insert(c, out);
    fputs("\";\n", out);
    fputs("#endif\n", out);
    fclose(in);
    fclose(out);
    return 0;
}

void inline insert(int c, FILE *fp) {
    switch (c) {
        case '\a':
            fputs("\\a", fp);
            break;
        case '\b':
            fputs("\\b", fp);
            break;
        case '\f':
            fputs("\\f", fp);
            break;
        case '\n':
            fputs("\\n", fp);
            break;
        case '\r':
            fputs("\\r", fp);
            break;
        case '\t':
            fputs("\\t", fp);
            break;
        case '\v':
            fputs("\\v", fp);
            break;
        case '\\':
            fputs("\\\\", fp);
            break;
        case '\'':
            fputs("\\'", fp);
            break;
        case '"':
            fputs("\\\"", fp);
            break;
        default:
            fputc(c, fp);
    }
}

Now, go back to your original program and add the following include directive in the beginning:

#include mruby_code.h

You have to do the following steps to compile a runnable program, assuming that this parser was compiled into a file named fileparser.c:

  • Run ./fileparser /path/to/mruby_code_file /path/to/program/mruby_code.h.
  • Compile your original program (it will include mruby_code.h)

The mruby code is provided in a variable called mruby_code. This is a char array, so you can pass it to mrb_load_string. And voila, you got the mruby file read once at compile time.

Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • Thanks! That's definitely a start and helpful to know. I came across an Objective-C example that made use of `mrb_read_irep_file`. I'm wondering if mruby has a method that simplifies this answer a little bit. I'm not sure how to navigate the mruby source code to find methods like this that may help. – Andrew Oct 13 '13 at 19:06
  • Well, as I said, I never looked at mruby, so I can't help you any further. This is the pure C way to do it. Maybe someone with further ruby knowledge will answer this question. – Filipe Gonçalves Oct 13 '13 at 19:08
  • One more question: does your example read the file at runtime? Is there a way to include the file at compile time to, sort of, package it up? – Andrew Oct 13 '13 at 19:13
  • It reads the file at runtime. If you're not comfortable with the overhead of doing that every time you run the program, the only alternative I can see is to code a C program that opens an mruby code file, generates a C file with proper code that shoves the file's content in a char *, and then include that file when calling the compiler. That would be really cool, I might try to code such a program just for fun. If I do it, I'll update my answer. Apart from that, every other possible approach (even mruby library functions) will most likely read the file in run time. – Filipe Gonçalves Oct 13 '13 at 19:17
  • Actually, it would be really cool, because you wouldn't have to worry about buffer size. I'm going to code this up, that was a very nice idea! The only catch is that if you change your ruby code, you have to recompile your C program. – Filipe Gonçalves Oct 13 '13 at 19:22
  • @Andrew updated my answer with my implementation to the approach I described. Hope it works for you, I tested it here and it worked like a charm. – Filipe Gonçalves Oct 13 '13 at 19:58
  • That's pretty cool. I learned quite a bit from that example. Thanks! – Andrew Oct 13 '13 at 20:19
1

In the mruby/compile.h there is a method called mrb_load_file which allows you to load a ruby file and execute it with mruby:

#include <stdlib.h>
#include <stdio.h>

#include <mruby.h>
#include <mruby/compile.h>

static mrb_value my_c_method(mrb_state *mrb, mrb_value value)
{
  puts("Called my C method");
  return value;
}

int main(void)
{
  mrb_state *mrb = mrb_open();

  struct RClass *mymodule = mrb_define_module(mrb, "MyModule");
  mrb_define_class_method(mrb, mymodule, "my_c_method", my_c_method, MRB_ARGS_NONE());

  FILE *f = fopen("example.rb", "r");
  mrb_load_file(mrb, f);

  return 0;
}
Andrew
  • 227,796
  • 193
  • 515
  • 708
  • Thanks for sharing this example. If I have a method which takes some parameter (in my ruby file) Ex: ef helloworld(var1, var2), how do I load the file and call this method from C? – programmer Jun 06 '15 at 17:03
  • Also do you know how to extract information from mrb_value structure? For example, how to read a string, integer, double etc? – programmer Jun 06 '15 at 17:05
0

Apart from using mrb_load_file:

FILE *f = fopen("the_file.rb", "r");
mrb_load_file(mrb, f);
fclose(f) ;

The manual option is to read the filename:

int load_ruby_file_to_state(char *filename, mrb_state *state) {
    FILE *file = fopen(filename, "r") ;
    if (!file) return -1 ;

    fseek(file, 0, SEEK_END);
    unsigned long length = ftell(file) ;
    fseek(file, 0, SEEK_SET);
    char *code = malloc(length + 1) ;

    if (code) {
        fread(code, 1, length, file) ;
        code[length + 1] = '\0' ;
    } else {
        return -1 ;
    }

    fclose(file) ;

    mrb_load_string(state, code) ;
    free(code) ;

    return 0 ;
}

And load file: load_ruby_file_to_state("the_file.rb", mrb) ;

Do note that mrb_load_file() will give a segfault if the file doesn't exist, so it's better to check for the file existence or if it's readable.

15 Volts
  • 1,946
  • 15
  • 37