The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
** mirb - Embeddable Interactive Ruby Shell
**
** This program takes code from the user in
** an interactive way and executes it
** immediately. It's a REPL...
*/
 
#include <string.h>

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

/* Guess if the user might want to enter more
 * or if he wants an evaluation of his code now */
int
is_code_block_open(struct mrb_parser_state *parser)
{
  int code_block_open = FALSE;

  /* check for unterminated string */
  if (parser->sterm) return TRUE;

  /* check if parser error are available */
  if (0 < parser->nerr) {
    const char *unexpected_end = "syntax error, unexpected $end";
    const char *message = parser->error_buffer[0].message;

    /* a parser error occur, we have to check if */
    /* we need to read one more line or if there is */
    /* a different issue which we have to show to */
    /* the user */

    if (strncmp(message, unexpected_end, strlen(unexpected_end)) == 0) {
      code_block_open = TRUE;
    }
    else if (strcmp(message, "syntax error, unexpected keyword_end") == 0) {
      code_block_open = TRUE;
    }
    else if (strcmp(message, "syntax error, unexpected tREGEXP_BEG") == 0) {
      code_block_open = TRUE;
    }
    return code_block_open;
  }

  switch (parser->lstate) {

  /* all states which need more code */

  case EXPR_BEG:
    /* an expression was just started, */
    /* we can't end it like this */
    code_block_open = TRUE;
    break;
  case EXPR_DOT:
    /* a message dot was the last token, */
    /* there has to come more */
    code_block_open = TRUE;
    break;
  case EXPR_CLASS:
    /* a class keyword is not enough! */
    /* we need also a name of the class */
    code_block_open = TRUE;
    break;
  case EXPR_FNAME:
    /* a method name is necessary */
    code_block_open = TRUE;
    break;
  case EXPR_VALUE:
    /* if, elsif, etc. without condition */
    code_block_open = TRUE;
    break;

  /* now all the states which are closed */

  case EXPR_ARG:
    /* an argument is the last token */
    code_block_open = FALSE;
    break;

  /* all states which are unsure */

  case EXPR_CMDARG:
    break;
  case EXPR_END:
    /* an expression was ended */
    break;
  case EXPR_ENDARG:
    /* closing parenthese */
    break;
  case EXPR_ENDFN:
    /* definition end */
    break;
  case EXPR_MID:
    /* jump keyword like break, return, ... */
    break;
  case EXPR_MAX_STATE:
    /* don't know what to do with this token */
    break;
  default:
    /* this state is unexpected! */
    break;
  }

  return code_block_open;
}

/* Print a short remark for the user */
void print_hint(void)
{
  printf("mirb - Embeddable Interactive Ruby Shell\n");
  printf("\nThis is a very early version, please test and report errors.\n");
  printf("Thanks :)\n\n");
}

/* Print the command line prompt of the REPL */
void
print_cmdline(int code_block_open)
{
  if (code_block_open) {
    printf("* ");
  }
  else {
    printf("> ");
  }
}

int
main(void)
{
  char last_char, ruby_code[1024], last_code_line[1024];
  int char_index;
  struct mrb_parser_state *parser;
  mrb_state *mrb_interpreter;
  mrb_value mrb_return_value;
  int byte_code;
  int code_block_open = FALSE;

  print_hint();

  /* new interpreter instance */ 
  mrb_interpreter = mrb_open();
  if (mrb_interpreter == NULL) {
    fprintf(stderr, "Invalid mrb_interpreter, exiting mirb");
    return EXIT_FAILURE;
  }

  /* new parser instance */
  parser = mrb_parser_new(mrb_interpreter);
  memset(ruby_code, 0, sizeof(*ruby_code));
  memset(last_code_line, 0, sizeof(*last_code_line));

  while (TRUE) {
    print_cmdline(code_block_open);

    char_index = 0;
    while ((last_char = getchar()) != '\n') {
      if (last_char == EOF) break;
      last_code_line[char_index++] = last_char;
    }
    if (last_char == EOF) {
      printf("\n");
      break;
    }

    last_code_line[char_index] = '\0';

    if ((strcmp(last_code_line, "quit") == 0) ||
        (strcmp(last_code_line, "exit") == 0)) {
      if (code_block_open) {
        /* cancel the current block and reset */
        code_block_open = FALSE;
        memset(ruby_code, 0, sizeof(*ruby_code));
        memset(last_code_line, 0, sizeof(*last_code_line));
        continue;
      }
      else {
        /* quit the program */
        break;
      }
    }
    else {
      if (code_block_open) {
	strcat(ruby_code, "\n");
        strcat(ruby_code, last_code_line);
      }
      else {
        memset(ruby_code, 0, sizeof(*ruby_code));
        strcat(ruby_code, last_code_line);
      }

      /* parse code */
      parser->s = ruby_code;
      parser->send = ruby_code + strlen(ruby_code);
      parser->capture_errors = 1;
      parser->lineno = 1;
      mrb_parser_parse(parser);
      code_block_open = is_code_block_open(parser); 

      if (code_block_open) {
        /* no evaluation of code */
      }
      else {
        if (0 < parser->nerr) {
          /* syntax error */
          printf("line %d: %s\n", parser->error_buffer[0].lineno, parser->error_buffer[0].message);
        }
	else {
          /* generate bytecode */
          byte_code = mrb_generate_code(mrb_interpreter, parser->tree);

          /* evaluate the bytecode */
          mrb_return_value = mrb_run(mrb_interpreter,
            /* pass a proc for evaulation */
            mrb_proc_new(mrb_interpreter, mrb_interpreter->irep[byte_code]),
            mrb_top_self(mrb_interpreter));
          /* did an exception occur? */
          if (mrb_interpreter->exc) {
            mrb_p(mrb_interpreter, mrb_obj_value(mrb_interpreter->exc));
            mrb_interpreter->exc = 0;
          }
	  else {
            /* no */
            printf(" => ");
            mrb_p(mrb_interpreter, mrb_return_value);
          }
        }

        memset(ruby_code, 0, sizeof(*ruby_code));
        memset(ruby_code, 0, sizeof(*last_code_line));
      }
    }
  }
  mrb_close(mrb_interpreter);

  return 0;
}