text: /*
   * lua-periphery by vsergeev
   * https://github.com/vsergeev/lua-periphery
   * License: MIT
   */
  
  #include <lua.h>
  #include <lualib.h>
  #include <lauxlib.h>
  
  #include <stdlib.h>
  #include <string.h>
  #include <stdint.h>
  #include <errno.h>
  
  #include <c-periphery/src/spi.h>
  #include "lua_periphery.h"
  #include "lua_compat.h"
  
  /*
  local periphery = require('periphery')
  local SPI = periphery.SPI
  
  -- Constructor
  spi = SPI(device <path string>, mode <number>, max_speed <number>)
  spi = SPI{device=<path string>, mode=<number>, max_speed=<number>, bit_order="msb", bits_per_word=8, extra_flags=0}
  
  -- Methods
  spi:transfer(data <table>) --> <table>
  spi:close()
  
  -- Properties
  spi.fd              immutable <number>
  spi.mode            mutable <number>
  spi.max_speed       mutable <number>
  spi.bit_order       mutable <number>
  spi.bits_per_word   mutable <number>
  spi.extra_flags     mutable <number>
  */
  
  /* Define a new error for malloc() required in read/write */
  #define SPI_ERROR_ALLOC    (SPI_ERROR_UNSUPPORTED-1)
  
  static const char *spi_error_code_strings[] = {
      [-SPI_ERROR_ARG]            = "SPI_ERROR_ARG",
      [-SPI_ERROR_OPEN]           = "SPI_ERROR_OPEN",
      [-SPI_ERROR_QUERY]          = "SPI_ERROR_QUERY",
      [-SPI_ERROR_CONFIGURE]      = "SPI_ERROR_CONFIGURE",
      [-SPI_ERROR_TRANSFER]       = "SPI_ERROR_TRANSFER",
      [-SPI_ERROR_CLOSE]          = "SPI_ERROR_CLOSE",
      [-SPI_ERROR_UNSUPPORTED]    = "SPI_ERROR_UNSUPPORTED",
      [-SPI_ERROR_ALLOC]          = "SPI_ERROR_ALLOC",
  };
  
  static int lua_spi_error(lua_State *L, enum spi_error_code code, int c_errno, const char *fmt, ...) {
      char message[128];
      va_list ap;
  
      va_start(ap, fmt);
  
      /* Create error table */
      lua_newtable(L);
      /* .code string */
      lua_pushstring(L, spi_error_code_strings[-code]);
      lua_setfield(L, -2, "code");
      /* .c_errno number */
      lua_pushinteger(L, c_errno);
      lua_setfield(L, -2, "c_errno");
      /* .message string */
      vsnprintf(message, sizeof(message), fmt, ap);
      lua_pushstring(L, message);
      lua_setfield(L, -2, "message");
  
      va_end(ap);
  
      /* Set error metatable on it */
      luaL_getmetatable(L, "periphery.error");
      lua_setmetatable(L, -2);
  
      return lua_error(L);
  }
  
  static void lua_spi_checktype(lua_State *L, int index, int type) {
      if (lua_type(L, index) != type)
          lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid argument #%d (%s expected, got %s)", index, lua_typename(L, type), lua_typename(L, lua_type(L, index)));
  }
  
  static int lua_spi_open(lua_State *L) {
      spi_t *spi;
      const char *device;
      unsigned int mode;
      uint32_t max_speed;
      spi_bit_order_t bit_order;
      uint8_t bits_per_word;
      uint32_t extra_flags;
      int ret;
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
  
      /* Default settings of optional arguments */
      bit_order = MSB_FIRST;
      bits_per_word = 8;
      extra_flags = 0;
  
      /* Arguments passed in table form */
      if (lua_istable(L, 2)) {
          lua_getfield(L, 2, "device");
          if (!lua_isstring(L, -1))
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid type on table argument 'device', should be string");
          lua_getfield(L, 2, "mode");
          if (!lua_isnumber(L, -1))
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid type on table argument 'mode', should be number");
          lua_getfield(L, 2, "max_speed");
          if (!lua_isnumber(L, -1))
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid type on table argument 'max_speed', should be number");
  
          device = lua_tostring(L, -3);
          mode = lua_tounsigned(L, -2);
          max_speed = lua_tounsigned(L, -1);
  
          /* Optional bit_order */
          lua_getfield(L, 2, "bit_order");
          if (lua_isstring(L, -1)) {
              const char *s;
              s = lua_tostring(L, -1);
              if (strcmp(s, "msb") == 0)
                  bit_order = MSB_FIRST;
              else if (strcmp(s, "lsb") == 0)
                  bit_order = LSB_FIRST;
              else
                  return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid table argument 'bit_order', should be 'msb' or 'lsb'");
          } else if (!lua_isnil(L, -1)) {
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid type on table argument 'bit_order', should be string");
          }
  
          /* Optional bits_per_word */
          lua_getfield(L, 2, "bits_per_word");
          if (lua_isnumber(L, -1))
              bits_per_word = lua_tounsigned(L, -1);
          else if (!lua_isnil(L, -1))
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid type on table argument 'bits_per_word', should be number");
  
          /* Optional extra_flags */
          lua_getfield(L, 2, "extra_flags");
          if (lua_isnumber(L, -1))
              extra_flags = lua_tounsigned(L, -1);
          else if (!lua_isnil(L, -1))
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid type on table argument 'extra_flags', should be number");
  
      /* Arguments passed normally */
      } else {
          lua_spi_checktype(L, 2, LUA_TSTRING);
          lua_spi_checktype(L, 3, LUA_TNUMBER);
          lua_spi_checktype(L, 4, LUA_TNUMBER);
  
          device = lua_tostring(L, 2);
          mode = lua_tounsigned(L, 3);
          max_speed = lua_tounsigned(L, 4);
      }
  
      if ((ret = spi_open_advanced2(spi, device, mode, max_speed, bit_order, bits_per_word, extra_flags)) < 0)
          return lua_spi_error(L, ret, spi_errno(spi), spi_errmsg(spi));
  
      return 0;
  }
  
  static int lua_spi_new(lua_State *L) {
      /* Remove self table object */
      lua_remove(L, 1);
  
      /* Create handle userdata */
      spi_t **spi = lua_newuserdata(L, sizeof(spi_t *));
      *spi = spi_new();
      /* Set SPI metatable on it */
      luaL_getmetatable(L, "periphery.SPI");
      lua_setmetatable(L, -2);
      /* Move userdata to the beginning of the stack */
      lua_insert(L, 1);
  
      /* Call open */
      lua_spi_open(L);
  
      /* Leave only userdata on the stack */
      lua_settop(L, 1);
  
      return 1;
  }
  
  static int lua_spi_transfer(lua_State *L) {
      spi_t *spi;
      uint8_t *buf;
      unsigned int i, len;
      int ret;
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
      lua_spi_checktype(L, 2, LUA_TTABLE);
  
      len = luaL_len(L, 2);
  
      if ((buf = malloc(len)) == NULL)
          return lua_spi_error(L, SPI_ERROR_ALLOC, errno, "Error: allocating memory");
  
      /* Convert byte table to byte buffer */
      for (i = 0; i < len; i++) {
          lua_pushunsigned(L, i+1);
          lua_gettable(L, -2);
          if (!lua_isnumber(L, -1)) {
              free(buf);
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid element index %d in bytes table.", i+1);
          }
  
          buf[i] = lua_tounsigned(L, -1);
          lua_pop(L, 1);
      }
  
      if ((ret = spi_transfer(spi, buf, buf, len)) < 0) {
          free(buf);
          return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
      }
  
      /* Convert byte buffer back to bytes table */
      for (i = 0; i < len; i++) {
          lua_pushunsigned(L, i+1);
          lua_pushunsigned(L, buf[i]);
          lua_settable(L, -3);
      }
  
      free(buf);
  
      return 1;
  }
  
  static int lua_spi_close(lua_State *L) {
      spi_t *spi;
      int ret;
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
  
      if ((ret = spi_close(spi)) < 0)
          return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
      return 0;
  }
  
  static int lua_spi_gc(lua_State *L) {
      spi_t *spi;
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
  
      spi_close(spi);
  
      spi_free(spi);
  
      return 0;
  }
  
  static int lua_spi_tostring(lua_State *L) {
      spi_t *spi;
      char spi_str[128];
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
  
      spi_tostring(spi, spi_str, sizeof(spi_str));
  
      lua_pushstring(L, spi_str);
  
      return 1;
  }
  
  static int lua_spi_index(lua_State *L) {
      spi_t *spi;
      const char *field;
  
      if (!lua_isstring(L, 2))
          return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: unknown method or property");
  
      field = lua_tostring(L, 2);
  
      /* Look up method in metatable */
      lua_getmetatable(L, 1);
      lua_getfield(L, -1, field);
      if (!lua_isnil(L, -1))
          return 1;
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
  
      if (strcmp(field, "fd") == 0) {
          lua_pushinteger(L, spi_fd(spi));
          return 1;
      } else if (strcmp(field, "mode") == 0) {
          unsigned int mode;
          int ret;
  
          if ((ret = spi_get_mode(spi, &mode)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          lua_pushunsigned(L, mode);
          return 1;
      } else if (strcmp(field, "max_speed") == 0) {
          uint32_t speed;
          int ret;
  
          if ((ret = spi_get_max_speed(spi, &speed)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          lua_pushunsigned(L, speed);
          return 1;
      } else if (strcmp(field, "bit_order") == 0) {
          spi_bit_order_t bit_order;
          int ret;
  
          if ((ret = spi_get_bit_order(spi, &bit_order)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          switch (bit_order) {
              case MSB_FIRST: lua_pushstring(L, "msb"); break;
              case LSB_FIRST: lua_pushstring(L, "lsb"); break;
              default: lua_pushstring(L, "unknown"); break;
          }
          return 1;
      } else if (strcmp(field, "bits_per_word") == 0) {
          uint8_t bits_per_word;
          int ret;
  
          if ((ret = spi_get_bits_per_word(spi, &bits_per_word)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          lua_pushunsigned(L, bits_per_word);
          return 1;
      } else if (strcmp(field, "extra_flags") == 0) {
          uint32_t extra_flags32;
          int ret;
  
          /* Attempt getting 32-bit extra flags */
          if ((ret = spi_get_extra_flags32(spi, &extra_flags32)) < 0) {
              /* Fallback to getting 8-bit extra flags */
              uint8_t extra_flags8;
  
              if ((ret = spi_get_extra_flags(spi, &extra_flags8)) < 0)
                  return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
              lua_pushunsigned(L, extra_flags8);
              return 1;
          }
  
          lua_pushunsigned(L, extra_flags32);
          return 1;
      }
  
      return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: unknown property");
  }
  
  static int lua_spi_newindex(lua_State *L) {
      spi_t *spi;
      const char *field;
  
      spi = *((spi_t **)luaL_checkudata(L, 1, "periphery.SPI"));
  
      if (!lua_isstring(L, 2))
          return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: unknown property");
  
      field = lua_tostring(L, 2);
  
      if (strcmp(field, "fd") == 0)
          return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: immutable property");
      else if (strcmp(field, "mode") == 0) {
          unsigned int mode;
          int ret;
  
          lua_spi_checktype(L, 3, LUA_TNUMBER);
          mode = lua_tounsigned(L, 3);
  
          if ((ret = spi_set_mode(spi, mode)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          return 0;
      } else if (strcmp(field, "max_speed") == 0) {
          uint32_t max_speed;
          int ret;
  
          lua_spi_checktype(L, 3, LUA_TNUMBER);
          max_speed = lua_tounsigned(L, 3);
  
          if ((ret = spi_set_max_speed(spi, max_speed)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          return 0;
      } else if (strcmp(field, "bit_order") == 0) {
          const char *s;
          spi_bit_order_t bit_order;
          int ret;
  
          lua_spi_checktype(L, 3, LUA_TSTRING);
  
          s = lua_tostring(L, 3);
          if (strcmp(s, "msb") == 0)
              bit_order = MSB_FIRST;
          else if (strcmp(s, "lsb") == 0)
              bit_order = LSB_FIRST;
          else
              return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: invalid bit_order, should be 'msb' or 'lsb'");
  
          if ((ret = spi_set_bit_order(spi, bit_order)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          return 0;
      } else if (strcmp(field, "bits_per_word") == 0) {
          uint8_t bits_per_word;
          int ret;
  
          lua_spi_checktype(L, 3, LUA_TNUMBER);
          bits_per_word = lua_tounsigned(L, 3);
  
          if ((ret = spi_set_bits_per_word(spi, bits_per_word)) < 0)
              return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
  
          return 0;
      } else if (strcmp(field, "extra_flags") == 0) {
          uint32_t extra_flags;
          int ret;
  
          lua_spi_checktype(L, 3, LUA_TNUMBER);
          extra_flags = lua_tounsigned(L, 3);
  
          if (extra_flags > 0xff) {
              if ((ret = spi_set_extra_flags32(spi, extra_flags)) < 0)
                  return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
          } else {
              if ((ret = spi_set_extra_flags(spi, (uint8_t)extra_flags)) < 0)
                  return lua_spi_error(L, ret, spi_errno(spi), "Error: %s", spi_errmsg(spi));
          }
  
          return 0;
      }
  
      return lua_spi_error(L, SPI_ERROR_ARG, 0, "Error: unknown property");
  }
  
  static const struct luaL_Reg periphery_spi_m[] = {
      {"close", lua_spi_close},
      {"transfer", lua_spi_transfer},
      {"__gc", lua_spi_gc},
      {"__tostring", lua_spi_tostring},
      {"__index", lua_spi_index},
      {"__newindex", lua_spi_newindex},
      {NULL, NULL}
  };
  
  LUALIB_API int luaopen_periphery_spi(lua_State *L) {
      /* Create periphery.SPI metatable */
      luaL_newmetatable(L, "periphery.SPI");
      /* Set metatable functions */
      const struct luaL_Reg *funcs = (const struct luaL_Reg *)periphery_spi_m;
      for (; funcs->name != NULL; funcs++) {
          lua_pushcclosure(L, funcs->func, 0);
          lua_setfield(L, -2, funcs->name);
      }
      /* Set metatable properties */
      lua_pushstring(L, "protected metatable");
      lua_setfield(L, -2, "__metatable");
  
      /* Create {__call = lua_spi_new, __metatable = "protected metatable"} table */
      lua_newtable(L);
      lua_pushcclosure(L, lua_spi_new, 0);
      lua_setfield(L, -2, "__call");
      lua_pushstring(L, "protected metatable");
      lua_setfield(L, -2, "__metatable");
      /* Set it as the metatable for the periphery.SPI metatable */
      lua_setmetatable(L, -2);
  
      return 1;
  }
  
  