Source

v8erl / src / driver.cc

#include <string>
#include "js2erl.h"

#define DRV_RUN_SCRIPT 'R'

// driver symbols
static ErlDrvData start(ErlDrvPort, char *);
static void stop(ErlDrvData);
static void outputv(ErlDrvData, ErlIOVec *);
static void ready_async(ErlDrvData, ErlDrvThreadData);

// local
static void run_script(void *);
static void async_free(void *);

static char OK[] = "ok";
static char ERROR[] = "error";
static char COMPILER_ERROR[] = "compilation failed";
static char DRIVER_NAME[] = "v8erl";

struct PortContext {
  ErlDrvPort port;
  Persistent<Context> context;

  void init(ErlDrvPort);
  void dispose();
  ErlDrvData castOut();
  static PortContext *castIn(void *);
};

void PortContext::init(ErlDrvPort p) {
  port = p;

  HandleScope handle_scope;
  Handle<ObjectTemplate> global = ObjectTemplate::New();
  context = Context::New(NULL, global);
};

void PortContext::dispose() {
  context.Dispose();
};

ErlDrvData PortContext::castOut() { 
  return reinterpret_cast<ErlDrvData>(this); 
};

PortContext *PortContext::castIn(void *data) {
  return reinterpret_cast<PortContext *>(data);
};

struct AsyncData {
  // input
  ErlDrvPort port;
  Persistent<Context> context;
  char *buf;
  int len;
  // output
  ErlDrvTermData *terms;
  int tlen;

  void init(ErlDrvPort, Persistent<Context>, char *, int);
  void setResult(Handle<Value>);
  void send();
  void setError(char *);
  void dispose();
  void *castOut();
  static AsyncData *castIn(void *);
};

void AsyncData::init(ErlDrvPort p, Persistent<Context> c, char *b, int l) {
  port = p;
  context = c;
  buf = b;
  len = l;
};

void AsyncData::dispose() {
  driver_free(terms);
};

void AsyncData::setResult(Handle<Value> result) {
  tlen = terms_length(result) + 4;
  terms = reinterpret_cast<ErlDrvTermData *>(driver_alloc(sizeof(ErlDrvTermData) * tlen));
  terms[0] = ERL_DRV_ATOM;
  terms[1] = driver_mk_atom(OK);
  value_to_terms(result, terms + 2);
  terms[tlen - 2] = ERL_DRV_TUPLE;
  terms[tlen - 1] = 2;
};

void AsyncData::send() {
  driver_output_term(port, terms, tlen);
};

void AsyncData::setError(char *msg) {
  tlen = 7;
  terms = reinterpret_cast<ErlDrvTermData *>(driver_alloc(sizeof(ErlDrvTermData) * tlen));
  terms[0] = ERL_DRV_ATOM;
  terms[1] = driver_mk_atom(ERROR);
  terms[2] = ERL_DRV_STRING;
  terms[3] = reinterpret_cast<ErlDrvTermData>(msg);
  terms[4] = strlen(msg);
  terms[5] = ERL_DRV_TUPLE;
  terms[6] = 2;
};

void *AsyncData::castOut() {
  return reinterpret_cast<void *>(this); 
};

AsyncData *AsyncData::castIn(void *data) {
  return reinterpret_cast<AsyncData *>(data);
};

static ErlDrvEntry driver_entry = {
  NULL, // init driver
  start, // open_port
  stop, // close_port
  NULL, // output		
  NULL, // ready_input
  NULL, // ready_output
  DRIVER_NAME, // driver name
  NULL, // unload dynamic driver
  NULL, // handle: EMULATOR USE ONLY
  NULL, // control
  NULL, // timeout
  outputv, // outputv
  ready_async, // async event done
  NULL, // flush
  NULL, // call
  NULL // event
};

extern "C" DRIVER_INIT(v8erl) {
  return &driver_entry;
}

static ErlDrvData start(ErlDrvPort port, char *cmd) {
  PortContext *data = PortContext::castIn(driver_alloc(sizeof(PortContext)));
  data->init(port);
  return data->castOut(); 
}

static void stop(ErlDrvData edata) {
  PortContext *data = PortContext::castIn(edata);
  data->dispose();
  driver_free(edata);
}

static void outputv(ErlDrvData edata, ErlIOVec *ev) {
  printf("outputv\r\n");
  ErlDrvBinary* input = ev->binv[1];

  PortContext *data = PortContext::castIn(edata);
  AsyncData *adata = AsyncData::castIn(driver_alloc(sizeof(AsyncData)));
  adata->init(data->port, data->context, input->orig_bytes, input->orig_size);
  driver_async(data->port, NULL, run_script, adata, async_free);
}

static void ready_async(ErlDrvData edata, ErlDrvThreadData tdata) {
  printf("ready_async\r\n");
  AsyncData *adata = AsyncData::castIn(tdata);
  adata->send();
  adata->dispose();
  driver_free(adata);
}

static void run_script(void *tdata) {
  printf("run_script\r\n");
  AsyncData *adata = AsyncData::castIn(tdata);
 
  Context::Scope context_scope(adata->context);
  HandleScope handle_scope;
  TryCatch try_catch;

  Handle<String> script = String::New(adata->buf, adata->len);
  Handle<Script> compiled = Script::Compile(script);
  if (compiled.IsEmpty()) {
    adata->setError(COMPILER_ERROR); 
  } else {
    Handle<Value> value = compiled->Run();
    if (value.IsEmpty()) {
      Handle<Value> exception = try_catch.Exception();
      String::AsciiValue exception_str(exception);
      adata->setError(*exception_str);
    } else {
      adata->setResult(value); 
    }
  }
}

static void async_free(void *tdata) {
  AsyncData *adata = AsyncData::castIn(tdata);
  adata->dispose();
  driver_free(adata);
}