Как загрузить код Mruby из внешнего файла в программе C?

Я начинаю с Мруби. Я также довольно новичок в программировании на C, поэтому, возможно, я не знаком со многими основами. Я смог скомпилировать пример программы mruby, которая загружает код ruby ​​из строки. Теперь я хотел бы загрузить его из внешнего файла. Вот пример кода, который я использую:

#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;
}

Как вы можете видеть в приведенном выше примере, я хотел бы переместить код ruby ​​во внешний файл. Я немного сбит с толку относительно того, могу ли я просто "включить" файл ruby ​​или мне нужно предварительно скомпилировать его в .mrb файл. Даже тогда я не уверен, как это включить .mrb файл при компиляции.

Что мне нужно изменить, чтобы иметь возможность загружать код ruby ​​из внешнего файла?

3 ответа

Решение

В mruby/compile.h есть метод, называемый mrb_load_file который позволяет загрузить файл ruby ​​и выполнить его с помощью 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;
}

Предполагая, что ваш код хранится в файле fooвсе, что вам нужно, это открыть файл и прочитать его. Чтобы открыть файл, вам нужно fopen(), определенный в stdio.h. Затем вы можете прочитать его построчно, используя fgets(), Я не знаком с mruby, поэтому не совсем уверен, ожидает ли mrb_load_string, что каждый код mruby находится в одной строке. Я приду так. Вот как вы можете это сделать:

#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 */

Этот кусок кода читает первую строку файла fooдо 127 символов (включая символ новой строки) и сохраняет его в code_buf, Вы можете позвонить mrb_load_string:

mrb_load_string(mrb, code);

Я не уверен, что это то, что вы хотели, я никогда не трогал Mruby, но из того, что я видел, mrb_load_string ожидает char * с кодом, и вы хотите, чтобы он пришел из файла. Вот как ты это делаешь.

Если вы хотите прочитать файл с кодом в несколько строк, у вас нет выбора, кроме как выделить достаточно большой буфер и прочитать его, используя 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;
}

Эта функция читает весь файл (при условии, что он не превышает наши пределы буфера). code является глобальным, потому что вы можете легко достичь емкости стека, если выделите большие локальные переменные (другой альтернативой является использование динамического выделения). Когда вы звоните read_code(), вы должны обязательно проверить его возвращаемое значение, чтобы проверить возможные ошибки при открытии файла. Также вы можете играть с fread()Возвращаемое значение, чтобы узнать, не достаточно ли размера буфера, чтобы прочитать все.

Просто убедитесь, что вы не забудете закрыть файл, когда закончите.

РЕДАКТИРОВАТЬ: Для fgets() версия, обратите внимание, что если строка содержит менее 128 символов, новая строка будет сохранена в code_buf, Вы можете установить code_buf[strlen(code_buf)-1] в '\0' если это так.

ОБНОВИТЬ:

Из нашего обсуждения комментариев ниже, я обновляю свой ответ с помощью элементарного парсера, чтобы вы могли читать файл ruby ​​во время компиляции. По сути, анализатор прочитает ваш файл ruby ​​и сгенерирует выходной файл с допустимым кодом C, который вставит содержимое файла в массив char. Специальные символы соответственно экранируются. Вот:

#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);
    }
}

Теперь вернитесь к исходной программе и добавьте следующую директиву include в начале:

#include mruby_code.h

Вы должны сделать следующие шаги, чтобы скомпилировать работающую программу, предполагая, что этот анализатор был скомпилирован в файл с именем fileparser.c:

  • Бежать ./fileparser /path/to/mruby_code_file /path/to/program/mruby_code.h,
  • Скомпилируйте вашу оригинальную программу (она будет включать mruby_code.h)

Код Mruby предоставляется в переменной с именем mruby_code, Это массив символов, так что вы можете передать его mrb_load_string, И вуаля, вы прочитали файл mruby один раз во время компиляции.

Помимо использования mrb_load_file:

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

Ручной вариант - прочитать имя файла:

      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 ;
}

И загрузить файл:load_ruby_file_to_state("the_file.rb", mrb) ;

Обратите внимание, чтоmrb_load_file()выдаст segfault, если файл не существует, поэтому лучше проверить существование файла или его доступность для чтения.

Другие вопросы по тегам