lua-users home
lua-l archive

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


With Lua 5.3, the `string` library embeds 3 minilanguages:
  - a text formatting minilanguage in `format` function
  - a regexp minilanguage in `find`, `gmatch`, `gsub` and `match` functions
  - a binary pack/unpacking minilanguage in `pack`, `packsize` and `unpack` functions

These minilanguages are embedded in Lua strings which are interpreted only at runtime.
Before runtime, there are neither syntax check, neither argument type check.
They are outside the Lua grammar.
They are not friendly with JIT optimization.

The text formatting minilanguage is based on the one of C `sprintf`.
This proposal is based on its C++ replacement, the iostream library.
The strict replacement is the output stream, but with this model, it is easy to add the input stream counterpart.
And the binary pack/unpack could be unified in this model.
Two minilanguages are replaced by method chaining on a new userdata representing a string buffer.

    printf("x = %d  y = %d", 10, 20);                           -- C
    string.format("x = %d  y = %d", 10, 20)                     -- Lua 5.0
    ("x = %d  y = %d"):format(10, 20)                           -- Lua 5.1
    cout << "x = " << 10 << "  y = " << 20;                     -- C++
    string.buffer():put'x = ':put(10):put'  y = ':put(20)       -- proposal

    string.format("x = %#x", 200)                               --> "x = 0xc8"
    string.buffer():hex():showbase(true):put'x = ':put(200)

    string.format("pi = %.4f", math.pi)                         --> "pi = 3.1416"
    string.buffer():put'pi = ':fixed(true):precision(4):put(math.pi)

    d = 5; m = 11; y = 1990
    string.format("%02d/%02d/%04d", d, m, y)                    --> "05/11/1990"
    string.buffer():fill'0':width(2):put(d):put'/':width(2):put(m):put'/':width(4):put(y)

The implementation defines a new userdata based on `luaL_Buffer` from the Lua/C API. `string.buffer` is the constructor.
The name of methods comes from C++: `put`, `precision`, `width`, `fill`, `left`, `right`, `internal`, `dec`, `oct`, `hex`, `fixed`, `scientific`, `showbase`, `showpoint`, `showpos`, `uppercase`, `endl`, `ends`.
And the methods `__tostring`, `len` & `add` come from Lua.

As the conversion `int` to `char` makes sense only in C, the format "%c" must be rewrite with an explicit call of `string.char`
    string.format("%c", 0x41)   --> 'A'
    string.buffer():put(string.char(0x41))

And the feature of format "%q" is supplied by a new function `string.repl` (named like in Python)
    string.format("%q", 'a string with "quotes"')               --> "a string with \"quotes\""
    string.repl('a string with "quotes"')

This userdata supplies some input methods: `get`, `getline`, `pos`.
The interface of `get` looks like the Lua `io.read`.
    local sb = string.buffer'05/11/1990'
    print(sb:get'i')            --> 5
    assert(sb:get(1) == '/')
    print(sb:get'i')            --> 11
    assert(sb:get(1) == '/')
    print(sb:get'i')            --> 1990

In order to replace `string.pack` & `string.unpack`, this userdata supplies these methods:
`pack`, `packsize`, `unpack`, `little`, `align`, `int`, `num`, `str`.

    string.pack("iii", 3, -27, 450)
    string.buffer():int'int':pack(3):pack(-27):pack(450)

    string.pack("i7", 1 << 54)
    string.buffer():int(7):pack(1 << 54)

    string.pack("c1", "hello")
    string.buffer():str('prefix', 1):pack'hello'

    string.pack("<i2 i2", 500, 24)
    string.buffer():little(true):int(2):pack(500):pack(24)

pack/unpack are introduced only since Lua 5.3, so, I think they could be deprecated in Lua 5.4.
For historical reasons, `format` can not be deprecated.
`format` is good for small things, and the new way is good for serious things.
In the same way, the regex minilanguage was not deprecated/replaced by LPeg.

Find in attachment, a patch of lstrlib.c against Lua 5.3.4.

François

diff --git a/lstrlib.c b/lstrlib.c
index 934b7db..f2377bf 100644
--- a/lstrlib.c
+++ b/lstrlib.c
@@ -11,6 +11,7 @@
 
 
 #include <ctype.h>
+#include <errno.h>
 #include <float.h>
 #include <limits.h>
 #include <locale.h>
@@ -1092,6 +1093,16 @@ static int str_format (lua_State *L) {
   return 1;
 }
 
+
+static int str_repl (lua_State *L) {
+  luaL_Buffer b;
+  luaL_checkany(L, 1);
+  luaL_buffinit(L, &b);
+  addliteral(L, &b, 1);
+  luaL_pushresult(&b);
+  return 1;
+}
+
 /* }====================================================== */
 
 
@@ -1539,7 +1550,742 @@ static int str_unpack (lua_State *L) {
 /* }====================================================== */
 
 
+/*
+** {======================================================
+** STRING BUFFER
+** =======================================================
+*/
+
+#define LUA_STRINGBUFFER "SB"
+
+enum {
+  // boolalpha   = 0x0001,
+  dec         = 0x0002,
+  fixed       = 0x0004,
+  hex         = 0x0008,
+  internal    = 0x0010, // ???
+  left        = 0x0020,
+  oct         = 0x0040,
+  right       = 0x0080,
+  scientific  = 0x0100,
+  showbase    = 0x0200,
+  showpoint   = 0x0400,
+  showpos     = 0x0800,
+  skipws      = 0x1000, // unused
+  //unitbuf     = 0x2000,
+  uppercase   = 0x4000,
+  adjustfield = left | right | internal,
+  basefield   = dec | oct | hex,
+  floatfield  = scientific | fixed,
+};
+
+typedef struct {
+  luaL_Buffer B;
+
+  unsigned int fmtflags;
+  int width;
+  int precision;
+  char fill;
+  size_t pos;
+
+  int islittle;
+  int maxalign;
+  int signed_;
+  int szint;
+  int sznum;
+  int szstr;
+  int szslen;
+} SB;
+
+#define tosb(L) (SB *)luaL_checkudata(L, 1, LUA_STRINGBUFFER)
+
+static int str_buffer (lua_State *L) {
+  size_t slen;
+  const char *s = luaL_optlstring(L, 1, "", &slen);
+  SB *sb = (SB *)lua_newuserdata(L, sizeof(SB));
+  luaL_buffinit(L, &sb->B);
+  if (slen > 0)
+    luaL_addlstring(&sb->B, s, slen);
+
+  sb->fmtflags = skipws | dec;
+  sb->width = 0;
+  sb->precision = -1;
+  sb->fill = ' ';
+  sb->pos = 0;
+
+  sb->islittle = nativeendian.little;
+  sb->maxalign = 1;
+  sb->signed_ = 1;
+  sb->szint = sizeof(lua_Integer);
+  sb->sznum = sizeof(lua_Number);
+  sb->szstr = 0;
+  sb->szslen = 0;
+  luaL_setmetatable(L, LUA_STRINGBUFFER);
+  return 1;
+}
+
+static int sb_add (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  luaL_addvalue(&sb->B);
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_put (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  switch (lua_type(L, 2)) {
+    case LUA_TSTRING: {
+      char form[MAX_FORMAT] = "%";
+      char *buff = luaL_prepbuffsize(&sb->B, MAX_ITEM);
+      int nb;
+      const char *s = lua_tostring(L, 2);
+      if ((sb->fmtflags & adjustfield) == left)
+        strcat(form, "-");
+      if (sb->width > 0) {
+        if (sb->precision > 0)
+          nb = snprintf(buff, MAX_ITEM, strcat(form, "*.*s"), sb->width, sb->precision, s);
+        else
+          nb = snprintf(buff, MAX_ITEM, strcat(form, "*s"), sb->width, s);
+      }
+      else {
+        if (sb->precision > 0)
+          nb = snprintf(buff, MAX_ITEM, strcat(form, ".*s"), sb->precision, s);
+        else
+          nb = snprintf(buff, MAX_ITEM, strcat(form, "s"), s);
+      }
+      luaL_addsize(&sb->B, nb);
+      break;
+    }
+    case LUA_TNUMBER: {
+      char form[MAX_FORMAT] = "%";
+      char *buff = luaL_prepbuffsize(&sb->B, MAX_ITEM);
+      int nb;
+      if ((sb->fmtflags & adjustfield) == left)
+        strcat(form, "-");
+      if ((sb->fmtflags & showpos) == showpos)
+        strcat(form, "+");
+      if (sb->fill == '0')
+        strcat(form, "0");
+      if (!lua_isinteger(L, 2)) {  /* float? */
+        lua_Number n = lua_tonumber(L, 2);
+        unsigned int flags = sb->fmtflags & floatfield;
+        if (flags == (fixed | scientific)) {
+          strcat(form, ((sb->fmtflags & uppercase) == uppercase) ? "A" : "a");
+          nb = snprintf(buff, MAX_ITEM, form, n);
+        }
+        else {
+          ptrdiff_t prec = (sb->precision < 0) ? 6 : sb->precision;
+          if ((sb->fmtflags & showpoint) == showpoint)
+            strcat(form, "#");
+          if (sb->width > 0)
+            strcat(form, "*");
+          strcat(form, ".*");
+          if (flags == fixed)
+            strcat(form, ((sb->fmtflags & uppercase) == uppercase) ? "F" : "f");
+          else if (flags == scientific)
+            strcat(form, ((sb->fmtflags & uppercase) == uppercase) ? "E" : "e");
+          else
+            strcat(form, ((sb->fmtflags & uppercase) == uppercase) ? "G" : "g");
+          if (sb->width > 0)
+            nb = snprintf(buff, MAX_ITEM, form, prec, sb->width, n);
+          else
+            nb = snprintf(buff, MAX_ITEM, form, prec, n);
+        }
+      }
+      else {  /* integers */
+        lua_Integer n = lua_tointeger(L, 2);
+        unsigned int flags = sb->fmtflags & basefield;
+        if ((sb->fmtflags & showbase) == showbase)
+          strcat(form, "#");
+        if (sb->width > 0)
+          strcat(form, "*");
+        if (flags == oct)
+          strcat(form, LUA_INTEGER_FRMLEN "o");
+        else if (flags == hex)
+          strcat(form, ((sb->fmtflags & uppercase) == uppercase)
+                       ? LUA_INTEGER_FRMLEN "X"
+                       : LUA_INTEGER_FRMLEN "x");
+        else
+          strcat(form, LUA_INTEGER_FRMLEN "d");
+        if (sb->width > 0)
+          nb = snprintf(buff, MAX_ITEM, form, sb->width, n);
+        else
+          nb = snprintf(buff, MAX_ITEM, form, n);
+      }
+      luaL_addsize(&sb->B, nb);
+      break;
+    }
+    default:
+      luaL_addvalue(&sb->B);
+      break;
+  }
+  sb->width = 0; /* C++ behaviour */
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_len (lua_State *L) {
+  SB *sb = tosb(L);
+  lua_pushinteger(L, sb->B.n);
+  return 1;
+}
+
+static int sb_precision (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->precision = luaL_checkinteger(L, 2);
+  if (sb->precision > 100)
+    sb->precision = 100;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_width (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->width = luaL_checkinteger(L, 2);
+  if (sb->width < 0)
+    sb->width = 0;
+  if (sb->width > 100)
+    sb->width = 100;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_fill (lua_State *L) {
+  SB *sb = tosb(L);
+  const char *s = luaL_checkstring(L, 2);
+  sb->fill = *s;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_left (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->fmtflags &= ~adjustfield;
+  sb->fmtflags |= left;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_right (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->fmtflags &= ~adjustfield;
+  sb->fmtflags |= right;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_internal (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->fmtflags &= ~adjustfield;
+  sb->fmtflags |= internal;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_dec (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->fmtflags &= ~basefield;
+  sb->fmtflags |= dec;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_oct (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->fmtflags &= ~basefield;
+  sb->fmtflags |= oct;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_hex (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->fmtflags &= ~basefield;
+  sb->fmtflags |= hex;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_fixed (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  if (lua_toboolean(L, 2))
+    sb->fmtflags |= fixed;
+  else
+    sb->fmtflags &= ~fixed;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_scientific (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  if (lua_toboolean(L, 2))
+    sb->fmtflags |= scientific;
+  else
+    sb->fmtflags &= ~scientific;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_showbase (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  if (lua_toboolean(L, 2))
+    sb->fmtflags |= showbase;
+  else
+    sb->fmtflags &= ~showbase;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_showpoint (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  if (lua_toboolean(L, 2))
+    sb->fmtflags |= showpoint;
+  else
+    sb->fmtflags &= ~showpoint;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_showpos (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  if (lua_toboolean(L, 2))
+    sb->fmtflags |= showpos;
+  else
+    sb->fmtflags &= ~showpos;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_uppercase (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  if (lua_toboolean(L, 2))
+    sb->fmtflags |= uppercase;
+  else
+    sb->fmtflags &= ~uppercase;
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_endl (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_addchar(&sb->B, '\n');
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_ends (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_addchar(&sb->B, '\0');
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_get (lua_State *L) {
+  SB *sb = tosb(L);
+  if (lua_type(L, 2) == LUA_TSTRING) {
+    static const char *const optnames[] = {"i", "n", NULL};
+    int opt = luaL_checkoption(L, 2, NULL, optnames);
+    if (sb->pos >= sb->B.n)
+      lua_pushnil(L);
+    else {
+      if (opt == 0) { /* integer */
+        char *endptr;
+        unsigned int flags = sb->fmtflags & basefield;
+        int base = (flags == oct) ? 8 : (flags == hex) ? 16 : 10;
+        lua_Integer n;
+        errno = 0;
+        n = strtol(&sb->B.b[sb->pos], &endptr, base);
+        if (endptr == &sb->B.b[sb->pos] || errno == ERANGE)
+          lua_pushnil(L);
+        else {
+          lua_pushinteger(L, n);
+          sb->pos = endptr - &sb->B.b[0];
+        }
+      }
+      else { /* number */
+        char *endptr;
+        lua_Number n;
+        errno = 0;
+        n = strtod(&sb->B.b[sb->pos], &endptr);
+        if (endptr == &sb->B.b[sb->pos] || errno == ERANGE)
+          lua_pushnil(L);
+        else {
+          lua_pushnumber(L, n);
+          sb->pos = endptr - &sb->B.b[0];
+        }
+      }
+    }
+  }
+  else { /* char / string */
+    lua_Integer nb = luaL_optinteger(L, 2, 1);
+    if (sb->pos >= sb->B.n)
+      lua_pushnil(L);
+    else {
+      if ((sb->pos + nb) > sb->B.n)
+        nb = sb->B.n - sb->pos;
+      lua_pushlstring(L, &sb->B.b[sb->pos], nb);
+      sb->pos += nb;
+    }
+  }
+  return 1;
+}
+
+static int sb_getline (lua_State *L) {
+  SB *sb = tosb(L);
+  const char *delim = luaL_optstring(L, 2, "\n");
+  if (sb->pos >= sb->B.n)
+    lua_pushnil(L);
+  else {
+    size_t i;
+    luaL_Buffer B;
+    luaL_buffinit(L, &B);
+    for (i = sb->pos; i < sb->B.n; i++) {
+      if (strchr(delim, sb->B.b[i])) {
+        sb->pos += 1;
+        break;
+      }
+      luaL_addchar(&B, sb->B.b[i]);
+      sb->pos += 1;
+    }
+    luaL_pushresult(&B);
+  }
+  return 1;
+}
+
+static int sb_pos (lua_State *L) {
+  SB *sb = tosb(L);
+  lua_pushinteger(L, sb->pos);
+  return 1;
+}
+
+static void pad(lua_State *L, SB *sb, int size) {
+  int align = size;
+  int ntoalign;
+  if (align <= 1)  /* need no alignment? */
+    ntoalign = 0;
+  else {
+    if (align > sb->maxalign)  /* enforce maximum alignment */
+      align = sb->maxalign;
+    if ((align & (align - 1)) != 0)  /* is 'align' not a power of 2? */
+      luaL_argerror(L, 2, "format asks for alignment not power of 2");
+    ntoalign = (align - (int)(sb->B.n & (align - 1))) & (align - 1);
+  }
+  while (ntoalign-- > 0)
+    luaL_addchar(&sb->B, LUAL_PACKPADBYTE);  /* fill alignment */
+}
+
+static int sb_pack (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  switch (lua_type(L, 2)) {
+    case LUA_TSTRING: {
+      size_t len;
+      const char *s = luaL_checklstring(L, 2, &len);
+      if (sb->szslen) { /* strings with length count */
+        luaL_argcheck(L, sb->szslen >= (int)sizeof(size_t) ||
+                         len < ((size_t)1 << (sb->szslen * NB)),
+                         2, "string length does not fit in given size");
+        packint(&sb->B, (lua_Unsigned)len, sb->islittle, sb->szslen, 0);  /* pack length */
+        luaL_addlstring(&sb->B, s, len);
+      }
+      else if (sb->szstr) { /* fixed-size string */
+        luaL_argcheck(L, len <= (size_t)sb->szstr, 2,
+                         "string longer than given size");
+        luaL_addlstring(&sb->B, s, len);  /* add string */
+        while (len++ < (size_t)sb->szstr)  /* pad extra space */
+          luaL_addchar(&sb->B, LUAL_PACKPADBYTE);
+      }
+      else { /* zero-terminated string */
+        luaL_argcheck(L, strlen(s) == len, 2, "string contains zeros");
+        luaL_addlstring(&sb->B, s, len);
+        luaL_addchar(&sb->B, '\0');  /* add zero at the end */
+      }
+      break;
+    }
+    case LUA_TNUMBER: {
+      if (!lua_isinteger(L, 2)) {  /* float? */
+        volatile Ftypes u;
+        char *buff;
+        lua_Number n = lua_tonumber(L, 2);
+        int sz = sb->sznum;
+        pad(L, sb, sz);
+        buff = luaL_prepbuffsize(&sb->B, sz);
+        if (sz == sizeof(u.f)) u.f = (float)n;  /* copy it into 'u' */
+        else if (sz == sizeof(u.d)) u.d = (double)n;
+        else u.n = n;
+        /* move 'u' to final result, correcting endianness if needed */
+        copywithendian(buff, u.buff, sz, sb->islittle);
+        luaL_addsize(&sb->B, sz);
+      }
+      else {  /* integers */
+        lua_Integer n = lua_tointeger(L, 2);
+        int sz = sb->szint;
+        pad(L, sb, sz);
+        if (sz < SZINT) {  /* need overflow check? */
+          if (sb->signed_) {
+            lua_Integer lim = (lua_Integer)1 << ((sz * NB) - 1);
+            luaL_argcheck(L, -lim <= n && n < lim, 2, "integer overflow");
+          }
+          else
+            luaL_argcheck(L, (lua_Unsigned)n < ((lua_Unsigned)1 << (sz * NB)),
+                              2, "unsigned overflow");
+        }
+        packint(&sb->B, (lua_Unsigned)n, sb->islittle, sz, (n < 0));
+      }
+      break;
+    }
+    default:
+      luaL_argerror(L, 2, "bad type");
+      break;
+  }
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_packsize (lua_State *L) {
+  SB *sb = tosb(L);
+  int sz = 0;
+  luaL_checkany(L, 2);
+  switch (lua_type(L, 2)) {
+    case LUA_TSTRING: {
+      if (sb->szstr)
+        sz = sb->szstr;
+      else
+        luaL_argerror(L, 2, "variable-length format");
+      break;
+    }
+    case LUA_TNUMBER:
+      sz = lua_isinteger(L, 2) ? sb->szint : sb->sznum;
+      break;
+    default:
+      luaL_argerror(L, 2, "bad type");
+    break;
+  }
+  lua_pushinteger(L, sz);
+  return 1;
+}
+
+static void skippad(lua_State *L, SB *sb, int size) {
+  int align = size;
+  int ntoalign;
+  if (align <= 1)  /* need no alignment? */
+    ntoalign = 0;
+  else {
+    if (align > sb->maxalign)  /* enforce maximum alignment */
+      align = sb->maxalign;
+    if ((align & (align - 1)) != 0)  /* is 'align' not a power of 2? */
+      luaL_argerror(L, 2, "format asks for alignment not power of 2");
+    ntoalign = (align - (int)(sb->B.n & (align - 1))) & (align - 1);
+  }
+  if (sb->pos + ntoalign + size > sb->B.n)
+    luaL_argerror(L, 2, "data string too short");
+  sb->pos += ntoalign;
+}
+
+static int sb_unpack (lua_State *L) {
+  SB *sb = tosb(L);
+  static const char *const optnames[] = {"i", "n", "s", NULL};
+  int opt = luaL_checkoption(L, 2, NULL, optnames);
+  switch (opt) {
+    case 0: { /* integer */
+      lua_Integer res;
+      skippad(L, sb, sb->szint);
+      res = unpackint(L, &sb->B.b[sb->pos], sb->islittle, sb->szint, sb->signed_);
+      lua_pushinteger(L, res);
+      sb->pos += sb->szint;
+      break;
+    }
+    case 1: { /* number */
+      volatile Ftypes u;
+      lua_Number num;
+      skippad(L, sb, sb->sznum);
+      copywithendian(u.buff, &sb->B.b[sb->pos], sb->sznum, sb->islittle);
+      if (sb->sznum == sizeof(u.f)) num = (lua_Number)u.f;
+      else if (sb->sznum == sizeof(u.d)) num = (lua_Number)u.d;
+      else num = u.n;
+      lua_pushnumber(L, num);
+      sb->pos += sb->sznum;
+      break;
+    }
+    case 2: { /* string */
+      if (sb->szslen) { /* strings with length count */
+        size_t len;
+        skippad(L, sb, sb->szslen);
+        len = (size_t)unpackint(L, &sb->B.b[sb->pos], sb->islittle, sb->szslen, 0);
+        luaL_argcheck(L, sb->pos + sb->szslen + len <= sb->B.n, 2, "data string too short");
+        lua_pushlstring(L, &sb->B.b[sb->pos + sb->szslen], len);
+        sb->pos += sb->szslen + len;
+      }
+      else if (sb->szstr) { /* fixed-size string */
+        luaL_argcheck(L, sb->pos + sb->szstr <= sb->B.n, 2, "data string too short");
+        lua_pushlstring(L, &sb->B.b[sb->pos], sb->szstr);
+        sb->pos += sb->szstr;
+      }
+      else { /* zero-terminated string */
+        if (sb->pos >= sb->B.n)
+          lua_pushnil(L);
+        else {
+          size_t len = (int)strlen(&sb->B.b[sb->pos]);
+          lua_pushlstring(L, &sb->B.b[sb->pos], len);
+          sb->pos += len + 1;  /* skip string plus final '\0' */
+        }
+      }
+      break;
+    }
+    default:
+      lua_assert(0);
+      break;
+  }
+  return 1;
+}
+
+static int sb_little (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_checkany(L, 2);
+  sb->islittle = lua_toboolean(L, 2);
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_align (lua_State *L) {
+  SB *sb = tosb(L);
+  sb->maxalign = luaL_checkinteger(L, 2);
+  if (sb->maxalign <= 0 || (sb->maxalign & (sb->maxalign - 1)) != 0)
+    luaL_argerror(L, 2, "alignment not power of 2");
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_int (lua_State *L) {
+  SB *sb = tosb(L);
+  if (lua_isnumber(L, 2)) {
+    lua_Integer sz = luaL_checkinteger(L, 2);
+    if (sz > MAXINTSIZE || sz <= 0)
+      luaL_error(L, "integral size (%d) out of limits [1,%d]",
+                       sz, MAXINTSIZE);
+    sb->szint = sz;
+  }
+  else {
+    static const int size[] = {0, 0,
+                               sizeof(lua_Integer), sizeof(char), sizeof(short),
+                               sizeof(int), sizeof(long), sizeof(size_t)};
+    static const char *const sizenames[] = {"unsigned", "signed",
+                                            "lua", "char", "short",
+                                            "int", "long", "size_t", NULL};
+    int opt = luaL_checkoption(L, 2, NULL, sizenames);
+    int sz = size[opt];
+    if (sz)
+      sb->szint = sz;
+    else
+      sb->signed_ = opt;
+  }
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_num (lua_State *L) {
+  SB *sb = tosb(L);
+  static const int size[] = {sizeof(lua_Number), sizeof(double), sizeof(float)};
+  static const char *const sizenames[] = {"lua", "double", "float", NULL};
+  sb->sznum = size[luaL_checkoption(L, 2, NULL, sizenames)];
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_str (lua_State *L) {
+  SB *sb = tosb(L);
+  static const char *const modenames[] = {"zero", "fixed", "prefix", NULL};
+  int mode = luaL_checkoption(L, 2, NULL, modenames);
+  switch (mode) {
+    case 0:
+      sb->szstr = 0;
+      sb->szslen = 0;
+      break;
+    case 1:
+      sb->szstr = luaL_checkinteger(L, 3);
+      sb->szslen = 0;
+      break;
+    case 2: {
+      lua_Integer sz = luaL_optinteger(L, 3, sizeof(size_t));
+      if (sz > MAXINTSIZE || sz <= 0)
+        luaL_error(L, "integral size (%d) out of limits [1,%d]",
+                         sz, MAXINTSIZE);
+      sb->szstr = 0;
+      sb->szslen = sz;
+      break;
+    }
+    default:
+      lua_assert(0);
+      break;
+  }
+  lua_pushvalue(L, 1);
+  return 1;
+}
+
+static int sb_tostring (lua_State *L) {
+  SB *sb = tosb(L);
+  luaL_pushresult(&sb->B);
+  return 1;
+}
+
+static const luaL_Reg sblib[] =
+{
+  {"add", sb_add},
+  {"put", sb_put},
+  {"len", sb_len},
+  {"precision", sb_precision},
+  {"width", sb_width},
+  {"fill", sb_fill},
+  {"left", sb_left},
+  {"right", sb_right},
+  {"internal", sb_internal},
+  {"dec", sb_dec},
+  {"oct", sb_oct},
+  {"hex", sb_hex},
+  {"fixed", sb_fixed},
+  {"scientific", sb_scientific},
+  {"showbase", sb_showbase},
+  {"showpoint", sb_showpoint},
+  {"showpos", sb_showpos},
+  {"uppercase", sb_uppercase},
+  {"endl", sb_endl},
+  {"ends", sb_ends},
+  {"get", sb_get},
+  {"getline", sb_getline},
+  {"pos", sb_pos},
+  {"pack", sb_pack},
+  {"packsize", sb_packsize},
+  {"unpack", sb_unpack},
+  {"little", sb_little},
+  {"align", sb_align},
+  {"int", sb_int},
+  {"num", sb_num},
+  {"str", sb_str},
+  {"__tostring", sb_tostring},
+  {NULL, NULL}
+};
+
+static void createmetatableb (lua_State *L) {
+  luaL_newmetatable(L, LUA_STRINGBUFFER);  /* create metatable */
+  lua_pushvalue(L, -1);  /* push metatable */
+  lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
+  luaL_setfuncs(L, sblib, 0);  /* add file methods to new metatable */
+  lua_pop(L, 1);  /* pop new metatable */
+}
+
+/* }====================================================== */
+
+
 static const luaL_Reg strlib[] = {
+  {"buffer", str_buffer},
   {"byte", str_byte},
   {"char", str_char},
   {"dump", str_dump},
@@ -1551,6 +2297,7 @@ static const luaL_Reg strlib[] = {
   {"lower", str_lower},
   {"match", str_match},
   {"rep", str_rep},
+  {"repl", str_repl},
   {"reverse", str_reverse},
   {"sub", str_sub},
   {"upper", str_upper},
@@ -1579,6 +2326,7 @@ static void createmetatable (lua_State *L) {
 LUAMOD_API int luaopen_string (lua_State *L) {
   luaL_newlib(L, strlib);
   createmetatable(L);
+  createmetatableb(L);
   return 1;
 }
 
-- 
2.7.4