lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I think this is easier than you think it is :)

See below

On 13-Feb-05, at 7:57 AM, Michael Abbott wrote:

This doesn't appear possible by what I can tell because the yield() would need to know what coroutine this script is in relation to (a script is not a coroutine right?). Please tell me I'm wrong because I'm not proud of the next solution I started implementing. So I ended up doing something like this:

   function player_idle()
       while true do
           coroutine.yield(1)
       end
   end

   return coroutine.wrap(player_idle)

Note that player_idle does not need to be a global. It's value is only used to pass to coroutine.wrap. You could call coroutine.wrap just given the value of player_idle, and you wouldn't need to do it from the script file.

By the way, a script file is not a coroutine, but it is a function, indistinguishable from other functions.

Now, there is nothing stopping you from constructing a character string and compiling it. So, for example, consider the following Lua code:

function loadCoroutine(filename)
  -- slurp the whole script
  local script = assert(io.open(filename)):read"*a"
  -- create a coroutine factory out of it
  local chunk = assert(loadstring(string.format(
                                    "return function() %s end",
                                    script),
                                  filename))
  -- and get the coroutine
  local worked, func = pcall(chunk)
  if worked then return coroutine.wrap(func)
            else error(func)
  end
end

-- The error handling could be better but that gives you some idea of where the checks should go.

Of course, you could do the same thing from C. The easiest way to do it is to just brute force turn the above code into C, but a cleaner solution is to write your own chunk reader and use lua_load. There is a slight difference between the Lua coroutine library and the C coroutine API, though: read the reference manual carefully.

The following code is not guaranteed to do anything, not even compile. But it might give you some ideas. I didn't do anything other than type it into the message :( I have to catch a plane in a few hours. If it actually works, let me know.

typedef enum LoaderState {
  LS_PREFIX, LS_FILE, LS_SUFFIX, LS_DONE, LS_ERROR
} LoaderState;

typedef struct LoaderContext {
  FILE *file;
  const char *prefix;
  const char *suffix;
  LoaderState state;
  char inbuf[BUFSIZ];
} LoaderContext;

/* Simple state machine to feed chunks to Lua */
const char *loader (lua_State *L, void *uctxt, size_t *siz) {
  LoaderContext *ctxt = uctxt;
  switch (ctxt->state) {
    case LS_PREFIX: {
      *size = strlen(ctxt->prefix);
      ctxt->state = LS_FILE;
      return ctxt->prefix;
    }
    case LS_FILE: {
      size_t nr = fread(ctxt->inbuf, 1, BUFSIZ, ctxt->file);
      if (nr != 0) {
        *size = nr;
        return ctxt->inbuf;
      } else if (ferror(ctxt->file)) {
          ctxt->state = LS_ERROR;
          strerror_r(errno, ctxt->inbuf, BUFSIZ);
          *size = 0;
          return NULL;
      }
      ctxt->state = LS_SUFFIX;
    }
    /* FALL THROUGH ON EOF */
    case LS_SUFFIX: {
      *size = strlen(ctxt->suffix);
      ctxt->state = LS_DONE;
      return ctxt->suffix;
    }
    case LS_ERROR:
    case LS_DONE: {
      *size = 0;
      return NULL;
    }
    default: {
      /* Oops. */
      assert(0, "Internal error");
    }
  }
}

LoaderContext *contextNew (lua_State *L, FILE *file,
                           const char *prefix, const char *suffix) {
  LoaderContext *self = lua_pushuserdata(L, sizeof(LoaderContext);
  /* TODO: add a __gc metamethod which closes the file */
  self->file = file;
  self->prefix = prefix;
  self->suffix = suffix;
  self->state = LC_PREFIX;
  return self;
}

static const char pfx = "return function () ";
static const char sfx = " end";

void loadCoroutine (lua_State *L, const char *filename) {
  LoaderContext *ctxt;
  int status;
  FILE *f;
  lua_State *thread;
  /* Create the thread. After this, everything will happen
     in the thread
   */
  thread = lua_newthread(L);
  /* Now read the file */
  f = fopen(filename, "r");
  if (f == NULL) { /* TODO: handle error */ }
  /* Make the loader */
  ctxt = contextNew(thread, f, "return function() ", " end");
  /* Load it */
  status = lua_load(thread, loader, ctxt, filename);
  /*
     If there was a read error, then we should report that
     rather than the syntax error
   */
  /* TODO: Make this an object method */
  if (ctxt->state == LC_ERROR) {
    lua_pop(thread, 1);
    lua_pushstring(thread, ctxt->inbuf);
    status = LUA_ERRRUN;
  }
  /* Ditch the context */
  fclose(f);
  lua_replace(thread, 1); /* overwrite the udata with the chunk */
  /* call the factory */
  if (status == 0) status = lua_pcall(thread, 0, 0, 0);
  /*
     At this point, the only thing on the thread's stack is
     either the chunk or an error message. If it's an error,
     we'll signal the error in the calling state. If the
     top of the thread stack is the chunk, we can just return,
     because the thread is already on top of the main stack.
   */
  if (status != 0) {
    lua_xmove(thread, L, 1);
    lua_error(L);
  }
}