From 4161f6756dadef6cd5a5c2e1e75804122e892949 Mon Sep 17 00:00:00 2001
From: Benoit Giannangeli <benoit.giannangeli@boursorama.fr>
Date: Thu, 16 Mar 2017 09:45:50 +0100
Subject: string.format

---
 README.md        | 122 +----------------------
 package.json     |   3 +-
 src/lauxlib.js   |   5 +
 src/llex.js      |   8 +-
 src/lobject.js   |   2 +-
 src/lstrlib.js   | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/ltablib.js   |   2 +-
 src/luaconf.js   |  21 +++-
 src/lutf8lib.js  |   2 +-
 tests/lstrlib.js |  92 ++++++++++++++++++
 10 files changed, 418 insertions(+), 129 deletions(-)

diff --git a/README.md b/README.md
index 617fd40..84195cf 100644
--- a/README.md
+++ b/README.md
@@ -25,91 +25,10 @@
     - [ ] userdata
 - [x] Tag Methods
 - [ ] C API
-    - [x] lua_absindex
-    - [x] lua_atpanic
-    - [x] lua_call
-    - [x] lua_callk
-    - [x] lua_checkstack
-    - [x] lua_compare
-    - [x] lua_concat
-    - [x] lua_copy
-    - [x] lua_createtable
-    - [x] lua_error
-    - [x] lua_getfield
-    - [x] lua_getglobal
-    - [x] lua_getmetatable
-    - [x] lua_gettable
-    - [x] lua_gettop
-    - [x] lua_insert
-    - [x] lua_isinteger
-    - [x] lua_isnil
-    - [x] lua_isnone
-    - [x] lua_isnoneornil
-    - [x] lua_isnumber
-    - [x] lua_isstring
-    - [x] lua_istable
-    - [x] lua_isyieldable
-    - [x] lua_len
-    - [x] lua_load
-    - [x] lua_newstate
-    - [x] lua_newtable
-    - [x] lua_newthread
-    - [x] lua_next
-    - [x] lua_numbertointeger
-    - [x] lua_pcall
-    - [x] lua_pop
-    - [x] lua_pushboolean
-    - [x] lua_pushglobaltable
-    - [x] lua_pushinteger
-    - [x] lua_pushjsclosure (lua_pushcclosure)
-    - [x] lua_pushjsfunction (lua_pushcfunction)
-    - [x] lua_pushliteral
-    - [x] lua_pushlstring
-    - [x] lua_pushnil
-    - [x] lua_pushnumber
-    - [x] lua_pushstring
-    - [x] lua_pushthread
-    - [x] lua_pushvalue
-    - [x] lua_rawequal
-    - [x] lua_rawget
-    - [x] lua_rawgeti
-    - [x] lua_rawlen
-    - [x] lua_rawset
-    - [x] lua_remove
-    - [x] lua_replace
-    - [x] lua_resume
-    - [x] lua_rotate
-    - [x] lua_setfield
-    - [x] lua_setglobal
-    - [x] lua_seti
-    - [x] lua_setmetatable
-    - [x] lua_settable
-    - [x] lua_settop
-    - [x] lua_setupvalue
-    - [x] lua_status
-    - [x] lua_stringtonumber
-    - [x] lua_toboolean
-    - [x] lua_tointeger
-    - [x] lua_tointegerx
-    - [x] lua_tolstring
-    - [x] lua_tonumber
-    - [x] lua_tonumberx
-    - [x] lua_topointer
-    - [x] lua_tostring
-    - [x] lua_tothread
-    - [x] lua_type
-    - [x] lua_typename
-    - [x] lua_upvalueindex
-    - [x] lua_version
-    - [x] lua_xmove
-    - [x] lua_yield
-    - [x] lua_yieldk
+    - [x] ...
     - [ ] lua_arith
     - [ ] lua_close
     - [ ] lua_dump
-    - [x] lua_gc (unavailable)
-    - [x] lua_getallocf (unavailable)
-    - [x] lua_getextraspace (unavailable)
     - [ ] lua_gethook
     - [ ] lua_gethookcount
     - [ ] lua_gethookmask
@@ -143,42 +62,7 @@
     - [ ] lua_upvalueid
     - [ ] lua_upvaluejoin
 - [ ] Auxiliary library
-    - [x] luaL_addlstring
-    - [x] luaL_addstring
-    - [x] luaL_addvalue
-    - [x] luaL_argcheck
-    - [x] luaL_argerror
-    - [x] luaL_buffinit
-    - [x] luaL_buffinitsize
-    - [x] luaL_callmeta
-    - [x] luaL_checkany
-    - [x] luaL_checkinteger
-    - [x] luaL_checklstring
-    - [x] luaL_checknumber
-    - [x] luaL_checkstack
-    - [x] luaL_checkstring
-    - [x] luaL_checktype
-    - [x] luaL_error
-    - [x] luaL_getmetafield
-    - [x] luaL_getsubtable
-    - [x] luaL_len
-    - [x] luaL_loadbuffer
-    - [x] luaL_loadbufferx
-    - [x] luaL_loadstring
-    - [x] luaL_newlib
-    - [x] luaL_newstate
-    - [x] luaL_openlibs
-    - [x] luaL_opt
-    - [x] luaL_optinteger
-    - [x] luaL_optlstring
-    - [x] luaL_prepbuffsize
-    - [x] luaL_pushresult
-    - [x] luaL_requiref
-    - [x] luaL_setfuncs
-    - [x] luaL_tolstring
-    - [x] luaL_typename
-    - [x] luaL_where
-    - [ ] luaL_addchar
+    - [x] ...
     - [ ] luaL_addsize
     - [ ] luaL_checkoption
     - [ ] luaL_checkudata
@@ -215,6 +99,7 @@
     - [ ] String
         - [x] string.byte
         - [x] string.char
+        - [x] string.format
         - [x] string.len
         - [x] string.lower
         - [x] string.rep
@@ -222,7 +107,6 @@
         - [x] string.upper
         - [ ] string.dump
         - [ ] string.find
-        - [ ] string.format
         - [ ] string.gmatch
         - [ ] string.gsub
         - [ ] string.match
diff --git a/package.json b/package.json
index d567a62..0167a15 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "tmp": "0.0.31"
   },
   "dependencies": {
-    "seedrandom": "^2.4.2"
+    "seedrandom": "^2.4.2",
+    "sprintf-js": "^1.0.3"
   }
 }
diff --git a/src/lauxlib.js b/src/lauxlib.js
index e3afa60..f77fe5e 100644
--- a/src/lauxlib.js
+++ b/src/lauxlib.js
@@ -226,6 +226,10 @@ const luaL_pushresult = function(B) {
     lapi.lua_pushstring(L, B.b);
 };
 
+const luaL_addchar = function(B, c) {
+    B.b += c;
+};
+
 const luaL_addvalue = function(B) {
     let L = B.L;
     let s = lapi.lua_tostring(L, -1);
@@ -395,6 +399,7 @@ const luaL_newlib = function(L, l) {
 
 module.exports.LUA_LOADED_TABLE  = LUA_LOADED_TABLE;
 module.exports.luaL_Buffer       = luaL_Buffer;
+module.exports.luaL_addchar      = luaL_addchar;
 module.exports.luaL_addlstring   = luaL_addlstring;
 module.exports.luaL_addstring    = luaL_addstring;
 module.exports.luaL_addvalue     = luaL_addvalue;
diff --git a/src/llex.js b/src/llex.js
index e747fdd..4f5b97e 100644
--- a/src/llex.js
+++ b/src/llex.js
@@ -467,11 +467,13 @@ const read_string = function(ls, del, seminfo) {
 
                 if (will === 'read_save')
                     next(ls);
-                else if (will === 'only_save') {
+                
+                if (will === 'read_save' || will === 'only_save') {
                     ls.buff.n -= 1;  /* remove '\\' */
                     save(ls, c);
-                } else if (will === 'no_save')
-                    break;
+                }
+
+                break;
             }
             default:
                 save_and_next(ls);
diff --git a/src/lobject.js b/src/lobject.js
index 556da08..719d7a6 100644
--- a/src/lobject.js
+++ b/src/lobject.js
@@ -360,7 +360,7 @@ const l_str2int = function(s) {
         s = s.slice(2);  /* skip '0x' */
 
         for (; ljstype.lisxdigit(s[0]); s = s.slice(1)) {
-            a = a * 16 + luaO_hexavalue(s);
+            a = a * 16 + luaO_hexavalue(s[0]);
             empty = false;
         }
     } else {  /* decimal */
diff --git a/src/lstrlib.js b/src/lstrlib.js
index 376d379..b3d5aea 100644
--- a/src/lstrlib.js
+++ b/src/lstrlib.js
@@ -1,10 +1,15 @@
 "use strict";
 
 const assert  = require('assert');
+const sprintf = require('sprintf');
 
 const lua     = require('./lua.js');
 const lapi    = require('./lapi.js');
 const lauxlib = require('./lauxlib.js');
+const luaconf = require('./luaconf.js');
+const CT      = lua.constant_types;
+
+const L_ESC   = '%'.charCodeAt(0);
 
 /* translate a relative string position: negative means back from end */
 const posrelat = function(pos, len) {
@@ -30,6 +35,290 @@ const str_char = function(L) {
     return 1;
 };
 
+const SIZELENMOD = luaconf.LUA_NUMBER_FRMLEN.length;
+
+const L_NBFD = 16; // TODO: arbitrary
+
+// See: http://croquetweak.blogspot.fr/2014/08/deconstructing-floats-frexp-and-ldexp.html
+const frexp = function(value) {
+    if (value === 0) return [value, 0];
+    var data = new DataView(new ArrayBuffer(8));
+    data.setFloat64(0, value);
+    var bits = (data.getUint32(0) >>> 20) & 0x7FF;
+    if (bits === 0) { // denormal
+        data.setFloat64(0, value * Math.pow(2, 64));  // exp + 64
+        bits = ((data.getUint32(0) >>> 20) & 0x7FF) - 64;
+    }
+    var exponent = bits - 1022;
+    var mantissa = ldexp(value, -exponent);
+    return [mantissa, exponent];
+};
+
+const ldexp = function(mantissa, exponent) {
+    var steps = Math.min(3, Math.ceil(Math.abs(exponent) / 1023));
+    var result = mantissa;
+    for (var i = 0; i < steps; i++)
+        result *= Math.pow(2, Math.floor((exponent + i) / steps));
+    return result;
+};
+
+/*
+** Add integer part of 'x' to buffer and return new 'x'
+*/
+const adddigit = function(buff, n, x) {
+    let d = Math.floor(x)|0;  /* get integer part from 'x' */
+    buff[n] = d < 10 ? d + '0'.charCodeAt(0) : d - 10 + 'a'.charCodeAt(0);  /* add to buffer */
+    return x - d;  /* return what is left */
+};
+
+const num2straux = function(buff, x) {
+    /* if 'inf' or 'NaN', format it like '%g' */
+    if (x === Infinity || isNaN(x))
+        return sprintf(luaconf.LUA_NUMBER_FMT, x).split('').map(e => e.charCodeAt(0));
+    else if (x === 0) {  /* can be -0... */
+        /* create "0" or "-0" followed by exponent */
+        return sprintf(luaconf.LUA_NUMBER_FMT + "x0p+0", x).split('').map(e => e.charCodeAt(0));
+    } else {
+        let fe = frexp(x);  /* 'x' fraction and exponent */
+        let m = fe[0];
+        let e = fe[1];
+        let n = 0;  /* character count */
+        if (m < 0) {  /* is number negative? */
+            buff[n++] = '-'.charCodeAt(0);  /* add signal */
+            m = -m;  /* make it positive */
+        }
+        buff[n++] = '0'.charCodeAt(0);
+        buff[n++] = 'x'.charCodeAt(0);  /* add "0x" */
+        m = adddigit(buff, n++, m * (1 << L_NBFD));  /* add first digit */
+        e -= L_NBFD;  /* this digit goes before the radix point */
+        if (m > 0) {  /* more digits? */
+            buff[n++] = luaconf.lua_getlocaledecpoint().charCodeAt(0);   /* add radix point */
+            do {  /* add as many digits as needed */
+                m = adddigit(buff, n++, m * 16);
+            } while (m > 0);
+        }
+        let exp = sprintf("p%+d", e).split('').map(e => e.charCodeAt(0));
+        return buff.slice(0, n + 1).concat(exp).concat(buff.slice(n));
+    }
+};
+
+const lua_number2strx = function(L, buff, fmt, x) {
+    let n = num2straux(buff, x);
+    if (fmt[SIZELENMOD] === 'A'.charCodeAt(0)) {
+        for (let i = 0; i < n; i++)
+            buff[i] = String.fromCharCode(buff[i]).toUpperCase().charCodeAt(0);
+    } else if (fmt[SIZELENMOD] !== 'a'.charCodeAt(0))
+        lauxlib.luaL_error(L, "modifiers for format '%a'/'%A' not implemented");
+    return n;
+};
+
+/*
+** Maximum size of each formatted item. This maximum size is produced
+** by format('%.99f', -maxfloat), and is equal to 99 + 3 ('-', '.',
+** and '\0') + number of decimal digits to represent maxfloat (which
+** is maximum exponent + 1). (99+3+1 then rounded to 120 for "extra
+** expenses", such as locale-dependent stuff)
+*/
+const MAX_ITEM   = 120;// TODO: + l_mathlim(MAX_10_EXP);
+
+
+/* valid flags in a format specification */
+const FLAGS      = ["-", "+", " ", "#", "0"].map(e => e.charCodeAt(0));
+
+/*
+** maximum size of each format specification (such as "%-099.99d")
+*/
+const MAX_FORMAT = 32;
+
+const isdigit = e => "0".charCodeAt(0) <= e && e <= "9".charCodeAt(0);
+
+const iscntrl = e => (0x00 <= e && e <= 0x1f) || e === 0x7f;
+
+const addquoted = function(b, s) {
+    lauxlib.luaL_addchar(b, '"');
+    let len = s.length;
+    while (len--) {
+        if (s[0] === '"'.charCodeAt(0) || s[0] === '\\'.charCodeAt(0) || s[0] === '\n'.charCodeAt(0)) {
+            lauxlib.luaL_addchar(b, '\\');
+            lauxlib.luaL_addchar(b, String.fromCharCode(s[0]));
+        } else if (iscntrl(s[0])) {
+            let buff = [];
+            if (!isdigit(s[1]))
+                buff = sprintf("\\%d", s[0]).split('').map(e => e.charCodeAt(0));
+            else
+                buff = sprintf("\\%03d", s[0]).split('').map(e => e.charCodeAt(0));
+            lauxlib.luaL_addstring(b, buff);
+        } else
+            lauxlib.luaL_addchar(b, String.fromCharCode(s[0]));
+        s = s.slice(1);
+    }
+    lauxlib.luaL_addchar(b, '"');
+};
+
+/*
+** Ensures the 'buff' string uses a dot as the radix character.
+*/
+const checkdp = function(buff) {
+    if (buff.indexOf('.') < 0) {  /* no dot? */
+        let point = luaconf.lua_getlocaledecpoint().charCodeAt(0);  /* try locale point */
+        let ppoint = buff.indexOf(point);
+        if (ppoint) point[ppoint] = '.';  /* change it to a dot */
+    }
+};
+
+const addliteral = function(L, b, arg) {
+    switch(lapi.lua_type(L, arg)) {
+        case CT.LUA_TSTRING: {
+            let s = L.stack[lapi.index2addr_(L, arg)].value;
+            addquoted(b, s, s.length);
+            break;
+        }
+        case CT.LUA_TNUMBER: {
+            let buff = lauxlib.luaL_prepbuffsize(b, MAX_ITEM);
+            if (!lapi.lua_isinteger(L, arg)) {  /* float? */
+                let n = lapi.lua_tonumber(L, arg);  /* write as hexa ('%a') */
+                buff.b += lapi.lua_number2strx(L, buff.b, `%${luaconf.LUA_INTEGER_FRMLEN}a`, n);
+                checkdp(buff.b);  /* ensure it uses a dot */
+            } else {  /* integers */
+                let n = lapi.lua_tointeger(L, arg);
+                let format = (n === Number.MIN_SAFE_INTEGER) ? `0x%${luaconf.LUA_INTEGER_FRMLEN}x` : luaconf.LUA_INTEGER_FMT;
+                buff.b += sprintf(format, n|0);
+            }
+            break;
+        }
+        case CT.LUA_TNIL: case CT.LUA_TBOOLEAN: {
+            lauxlib.luaL_tolstring(L, arg);
+            lauxlib.luaL_addvalue(b);
+            break;
+        }
+        default: {
+            lauxlib.luaL_argerror(L, arg, "value has no literal form");
+        }
+    }
+};
+
+const scanformat = function(L, strfrmt, form) {
+    let p = strfrmt;
+    while (p[0] !== 0 && FLAGS.indexOf(p[0]) >= 0) p = p.slice(1);  /* skip flags */
+    if (strfrmt.length - p.length >= FLAGS.length)
+        lauxlib.luaL_error(L, "invalid format (repeated flags)");
+    if (isdigit(p[0])) p = p.slice(1);  /* skip width */
+    if (isdigit(p[0])) p = p.slice(1);  /* (2 digits at most) */
+    if (p[0] === '.'.charCodeAt(0)) {
+        p = p.slice(1);
+        if (isdigit(p[0])) p = p.slice(1);  /* skip precision */
+        if (isdigit(p[0])) p = p.slice(1);  /* (2 digits at most) */
+    }
+    if (isdigit(p[0]))
+        lauxlib.luaL_error(L, "invalid format (width or precision too long)");
+    form[0] = "%".charCodeAt(0);
+    for (let i = 0; i < strfrmt.length - p.length + 1; i++)
+        form[i + 1] = strfrmt[i];
+    // form[strfrmt.length - p.length + 2] = 0;
+    return {
+        form: form,
+        p: p
+    };
+};
+
+/*
+** add length modifier into formats
+*/
+const addlenmod = function(form, lenmod) {
+    let l = form.length;
+    let lm = lenmod.length;
+    let spec = form[l - 1];
+    for (let i = 0; i < lenmod.length; i++)
+        form[i + l - 1] = lenmod[i];
+    form[l + lm - 1] = spec;
+    // form[l + lm] = 0;
+    return form;
+};
+
+const str_format = function(L) {
+    let top = lapi.lua_gettop(L);
+    let arg = 1;
+    let strfrmt = lauxlib.luaL_checkstring(L, arg);
+    strfrmt = L.stack[lapi.index2addr_(L, 1)].value;
+    let b = new lauxlib.luaL_Buffer(L);
+
+    while (strfrmt.length > 0) {
+        if (strfrmt[0] !== L_ESC) {
+            lauxlib.luaL_addchar(b, String.fromCharCode(strfrmt[0]));
+            strfrmt = strfrmt.slice(1);
+        } else if ((strfrmt = strfrmt.slice(1))[0] === L_ESC) {
+            lauxlib.luaL_addchar(b, String.fromCharCode(strfrmt[0]));
+            strfrmt = strfrmt.slice(1);
+        } else { /* format item */
+            let form = [];  /* to store the format ('%...') */
+            let buff = lauxlib.luaL_prepbuffsize(b, MAX_ITEM);  /* to put formatted item */
+            if (++arg > top)
+                lauxlib.luaL_argerror(L, arg, "no value");
+            let f = scanformat(L, strfrmt, form);
+            strfrmt = f.p;
+            form = f.form;
+            switch (String.fromCharCode(strfrmt[0])) {
+                case 'c': {
+                    strfrmt = strfrmt.slice(1);
+                    buff.b += sprintf(String.fromCharCode(...form), lauxlib.luaL_checkinteger(L, arg));
+                    break;
+                }
+                case 'd': case 'i':
+                case 'o': case 'u': case 'x': case 'X': {
+                    strfrmt = strfrmt.slice(1);
+                    let n = lauxlib.luaL_checkinteger(L, arg);
+                    form = addlenmod(form, luaconf.LUA_INTEGER_FRMLEN.split('').map(e => e.charCodeAt(0)));
+                    buff.b += sprintf(String.fromCharCode(...form), n|0);
+                    break;
+                }
+                case 'a': case 'A': {
+                    strfrmt = strfrmt.slice(1);
+                    form = addlenmod(form, luaconf.LUA_INTEGER_FRMLEN.split('').map(e => e.charCodeAt(0)));
+                    buff.b += lua_number2strx(L, buff.b, form, lauxlib.luaL_checknumber(L, arg));
+                    break;
+                }
+                case 'e': case 'E': case 'f':
+                case 'g': case 'G': {
+                    strfrmt = strfrmt.slice(1);
+                    let n = lauxlib.luaL_checknumber(L, arg);
+                    form = addlenmod(form, luaconf.LUA_INTEGER_FRMLEN.split('').map(e => e.charCodeAt(0)));
+                    buff.b += sprintf(String.fromCharCode(...form), n);
+                    break;
+                }
+                case 'q': {
+                    strfrmt = strfrmt.slice(1);
+                    addliteral(L, b, arg);
+                    break;
+                }
+                case 's': {
+                    strfrmt = strfrmt.slice(1);
+                    let str = lauxlib.luaL_tolstring(L, arg);
+                    let  s = L.stack[lapi.index2addr_(L, arg)].value;
+                    if (form[2] === '\0')  /* no modifiers? */
+                        lauxlib.luaL_addvalue(b);  /* keep entire string */
+                    else {
+                        lauxlib.luaL_argcheck(L, s.length === str.length, arg, "string contains zeros");
+                        if (form.indexOf('.') < 0 && s.length >= 100) {
+                            /* no precision and string is too long to be formatted */
+                            lauxlib.luaL_addvalue(b);  /* keep entire string */
+                        } else {  /* format the string into 'buff' */
+                            buff.b += sprintf(String.fromCharCode(...form), s);
+                            lapi.lua_pop(L, 1);  /* remove result from 'luaL_tolstring' */
+                        }
+                    }
+                    break;
+                }
+                default: {  /* also treat cases 'pnLlh' */
+                    return lauxlib.luaL_error(L, `invalid option '%${strfrmt[0]}'`);
+                }
+            }
+        }
+    }
+
+    lauxlib.luaL_pushresult(b);
+    return 1;
+};
+
 const str_reverse = function(L) {
     lapi.lua_pushstring(L, lauxlib.luaL_checkstring(L, 1).split("").reverse().join(""));
     return 1;
@@ -77,6 +366,7 @@ const str_byte = function(L) {
 const strlib = {
     "byte":    str_byte,
     "char":    str_char,
+    "format":  str_format,
     "len":     str_len,
     "lower":   str_lower,
     "rep":     str_rep,
diff --git a/src/ltablib.js b/src/ltablib.js
index a7963d3..10d66eb 100644
--- a/src/ltablib.js
+++ b/src/ltablib.js
@@ -139,7 +139,7 @@ const tconcat = function(L) {
     let i = lauxlib.luaL_optinteger(L, 3, 1);
     last = lauxlib.luaL_optinteger(L, 4, last);
 
-    let b = new lauxlib.luaL_Buffer();
+    let b = new lauxlib.luaL_Buffer(L);
     lauxlib.luaL_buffinit(L, b);
 
     for (; i < last; i++) {
diff --git a/src/luaconf.js b/src/luaconf.js
index f0066de..fc96224 100644
--- a/src/luaconf.js
+++ b/src/luaconf.js
@@ -20,6 +20,21 @@ const lua_numbertointeger = function(n) {
     return n|0;
 };
 
-module.exports.LUAI_MAXSTACK       = LUAI_MAXSTACK;
-module.exports.LUA_IDSIZE          = LUA_IDSIZE;
-module.exports.lua_numbertointeger = lua_numbertointeger;
\ No newline at end of file
+const LUA_INTEGER_FRMLEN = "";
+const LUA_NUMBER_FRMLEN = "";
+
+const LUA_INTEGER_FMT = `%${LUA_INTEGER_FRMLEN}d`;
+const LUA_NUMBER_FMT  = "%.7g";
+
+const lua_getlocaledecpoint = function() {
+    return (1.1).toLocaleString().substring(1, 2);
+};
+
+module.exports.LUAI_MAXSTACK         = LUAI_MAXSTACK;
+module.exports.LUA_IDSIZE            = LUA_IDSIZE;
+module.exports.LUA_INTEGER_FMT       = LUA_INTEGER_FMT;
+module.exports.LUA_INTEGER_FRMLEN    = LUA_INTEGER_FRMLEN;
+module.exports.LUA_NUMBER_FMT        = LUA_NUMBER_FMT;
+module.exports.LUA_NUMBER_FRMLEN     = LUA_NUMBER_FRMLEN;
+module.exports.lua_getlocaledecpoint = lua_getlocaledecpoint;
+module.exports.lua_numbertointeger   = lua_numbertointeger;
\ No newline at end of file
diff --git a/src/lutf8lib.js b/src/lutf8lib.js
index a1ce6bb..053fb56 100644
--- a/src/lutf8lib.js
+++ b/src/lutf8lib.js
@@ -112,7 +112,7 @@ const utfchar = function(L) {
     if (n === 1)  /* optimize common case of single char */
         pushutfchar(L, 1);
     else {
-        let b = new lauxlib.luaL_Buffer();
+        let b = new lauxlib.luaL_Buffer(L);
         lauxlib.luaL_buffinit(L, b);
         for (let i = 1; i <= n; i++) {
             pushutfchar(L, i);
diff --git a/tests/lstrlib.js b/tests/lstrlib.js
index b15eecf..0475a42 100644
--- a/tests/lstrlib.js
+++ b/tests/lstrlib.js
@@ -220,4 +220,96 @@ test('string.byte', function (t) {
         108,
         "Correct element(s) on the stack"
     );
+});
+
+
+test('string.format', function (t) {
+    let luaCode = `
+        return string.format("%%%d %010d", 10, 23)
+    `, L;
+    
+    t.plan(3);
+
+    t.doesNotThrow(function () {
+
+        L = lauxlib.luaL_newstate();
+
+        linit.luaL_openlibs(L);
+
+        lauxlib.luaL_loadstring(L, luaCode);
+
+    }, "Lua program loaded without error");
+
+    t.doesNotThrow(function () {
+
+        lapi.lua_call(L, 0, -1);
+
+    }, "Lua program ran without error");
+
+    t.strictEqual(
+        lapi.lua_tostring(L, -1),
+        "%10 0000000023",
+        "Correct element(s) on the stack"
+    );
+});
+
+
+test('string.format', function (t) {
+    let luaCode = `
+        return string.format("%07X", 0xFFFFFFF)
+    `, L;
+    
+    t.plan(3);
+
+    t.doesNotThrow(function () {
+
+        L = lauxlib.luaL_newstate();
+
+        linit.luaL_openlibs(L);
+
+        lauxlib.luaL_loadstring(L, luaCode);
+
+    }, "Lua program loaded without error");
+
+    t.doesNotThrow(function () {
+
+        lapi.lua_call(L, 0, -1);
+
+    }, "Lua program ran without error");
+
+    t.strictEqual(
+        lapi.lua_tostring(L, -1),
+        "FFFFFFF",
+        "Correct element(s) on the stack"
+    );
+});
+
+test('string.format', function (t) {
+    let luaCode = `
+        return string.format("%q", 'a string with "quotes" and \\n new line')
+    `, L;
+    
+    t.plan(3);
+
+    t.doesNotThrow(function () {
+
+        L = lauxlib.luaL_newstate();
+
+        linit.luaL_openlibs(L);
+
+        lauxlib.luaL_loadstring(L, luaCode);
+
+    }, "Lua program loaded without error");
+
+    t.doesNotThrow(function () {
+
+        lapi.lua_call(L, 0, -1);
+
+    }, "Lua program ran without error");
+
+    t.strictEqual(
+        lapi.lua_tostring(L, -1),
+        '"a string with \\"quotes\\" and \\\n new line"',
+        "Correct element(s) on the stack"
+    );
 });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2