From 290ec54e13d1dff301465cbfa2fd7b4e1e52962f Mon Sep 17 00:00:00 2001
From: Benoit Giannangeli <benoit.giannangeli@boursorama.fr>
Date: Wed, 22 Feb 2017 13:35:01 +0100
Subject: lua_stringtonumber, tonumber

---
 README.md         |  4 ++--
 src/lapi.js       | 10 +++++++++-
 src/lbaselib.js   | 28 ++++++++++++++++++++++++++++
 tests/lbaselib.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 86 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 3174ecb..75768b7 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,7 @@
     - [x] lua_remove
     - [x] lua_rotate
     - [x] lua_insert
+    - [x] lua_stringtonumber
     - [ ] lua_arith
     - [ ] lua_close
     - [ ] lua_compare
@@ -129,7 +130,6 @@
     - [ ] lua_setupvalue
     - [ ] lua_setuservalue
     - [ ] lua_status
-    - [ ] lua_stringtonumber
     - [ ] lua_tocfunction
     - [ ] lua_tothread
     - [ ] lua_touserdata
@@ -214,11 +214,11 @@
         - [x] collectgarbage (unavailable)
         - [x] ipairs
         - [x] select
+        - [x] tonumber
         - [ ] assert
         - [ ] next
         - [ ] pairs
         - [ ] rawlen
-        - [ ] tonumber
         - [ ] dofile
         - [ ] loadfile
         - [ ] load
diff --git a/src/lapi.js b/src/lapi.js
index bc4dd54..19e3090 100644
--- a/src/lapi.js
+++ b/src/lapi.js
@@ -488,6 +488,13 @@ const lua_topointer = function(L, idx) {
     }
 };
 
+const lua_stringtonumber = function(L, s) {
+    let number = parseFloat(s);
+    L.stack[L.top++] = new TValue(number % 1 !== 0 ? CT.LUA_TNUMFLT : CT.LUA_TNUMINT, number);
+    assert(L.top <= L.ci.top, "stack overflow");
+    return s.length;
+};
+
 const f_call = function(L, ud) {
     ldo.luaD_callnoyield(L, ud.funcOff, ud.nresults);
 };
@@ -705,4 +712,5 @@ module.exports.lua_error           = lua_error;
 module.exports.lua_insert          = lua_insert;
 module.exports.lua_gc              = lua_gc;
 module.exports.lua_getallocf       = lua_getallocf;
-module.exports.lua_getextraspace   = lua_getextraspace;
\ No newline at end of file
+module.exports.lua_getextraspace   = lua_getextraspace;
+module.exports.lua_stringtonumber  = lua_stringtonumber;
\ No newline at end of file
diff --git a/src/lbaselib.js b/src/lbaselib.js
index 19a990e..9cc4fb1 100644
--- a/src/lbaselib.js
+++ b/src/lbaselib.js
@@ -127,6 +127,33 @@ const luaB_ipairs = function(L) {
     return 3;
 };
 
+const luaB_tonumber = function(L) {
+    if (lapi.lua_type(L, 2) <= 0) {  /* standard conversion? */
+        lauxlib.luaL_checkany(L, 1);
+        if (lapi.lua_type(L, 1) === CT.LUA_TNUMBER) {  /* already a number? */
+            lapi.lua_settop(L, 1);
+            return 1;
+        } else {
+            let s = lapi.lua_tostring(L, 1);
+            if (s !== null && lapi.lua_stringtonumber(L, s) === s.length)
+                return 1;  /* successful conversion to number */
+        }
+    } else {
+        let base = lauxlib.luaL_checkinteger(L, 2);
+        lauxlib.luaL_checktype(L, 1, CT.LUA_TSTRING);  /* no numbers as strings */
+        let s = lapi.lua_tostring(L, 1);
+        lauxlib.luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");
+        let n = parseInt(s, base);
+        if (!isNaN(n)) {
+            lapi.lua_pushinteger(L, n);
+            return 1;
+        }
+    }
+
+    lapi.lua_pushnil(L);
+    return 1;
+};
+
 const luaB_error = function(L) {
     let level = lauxlib.luaL_optinteger(L, 2, 1);
     lapi.lua_settop(L, 1);
@@ -195,6 +222,7 @@ const base_funcs = {
     "collectgarbage": function () {},
     "print":          luaB_print,
     "tostring":       luaB_tostring,
+    "tonumber":       luaB_tonumber,
     "getmetatable":   luaB_getmetatable,
     "ipairs":         luaB_ipairs,
     "select":         luaB_select,
diff --git a/tests/lbaselib.js b/tests/lbaselib.js
index 73d13a3..6dc6bfb 100644
--- a/tests/lbaselib.js
+++ b/tests/lbaselib.js
@@ -442,4 +442,51 @@ test('select', function (t) {
         [2, 3],
         "Correct element(s) on the stack"
     );
+});
+
+
+test('tonumber', function (t) {
+    let luaCode = `
+        return tonumber('123'), tonumber('12.3'), tonumber('az', 36), tonumber('10', 2)
+    `, L;
+    
+    t.plan(5);
+
+    t.doesNotThrow(function () {
+
+        let bc = toByteCode(luaCode).dataView;
+
+        L = lauxlib.luaL_newstate();
+
+        linit.luaL_openlibs(L);
+
+        lapi.lua_load(L, bc, "test-tonumber");
+
+        lapi.lua_call(L, 0, -1);
+
+    }, "JS Lua program ran without error");
+
+    t.strictEqual(
+        lapi.lua_tonumber(L, -4),
+        123,
+        "Correct element(s) on the stack"
+    );
+
+    t.strictEqual(
+        lapi.lua_tonumber(L, -3),
+        12.3,
+        "Correct element(s) on the stack"
+    );
+
+    t.strictEqual(
+        lapi.lua_tonumber(L, -2),
+        395,
+        "Correct element(s) on the stack"
+    );
+
+    t.strictEqual(
+        lapi.lua_tonumber(L, -1),
+        2,
+        "Correct element(s) on the stack"
+    );
 });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2