From b62bcdfa67d6b0359bf45930ab392953d69eb399 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Sat, 18 Feb 2017 16:07:47 +0100 Subject: setmetatable, getmetatable --- README.md | 6 ++-- src/lapi.js | 31 +++++++++++++++++++- src/lauxlib.js | 35 +++++++++++++++++++++-- src/lbaselib.js | 36 +++++++++++++++++++---- tests/lapi.js | 23 --------------- tests/lbaselib.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 tests/lbaselib.js diff --git a/README.md b/README.md index 3407512..19306fe 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ - [x] lua_getfield - [x] lua_getglobal - [x] lua_getmetatable + - [x] lua_setmetatable - [x] lua_pushglobaltable - [x] lua_pushliteral - [x] lua_rawget @@ -124,7 +125,6 @@ - [ ] lua_sethook - [ ] lua_seti - [ ] lua_setlocal - - [ ] lua_setmetatable - [ ] lua_setupvalue - [ ] lua_setuservalue - [ ] lua_status @@ -142,6 +142,7 @@ - [x] luaL_newstate - [x] luaL_typename - [x] luaL_checkany + - [x] luaL_checktype - [x] luaL_callmeta - [x] luaL_getmetafield - [x] luaL_setfuncs @@ -167,7 +168,6 @@ - [ ] luaL_checknumber - [ ] luaL_checkoption - [ ] luaL_checkstring - - [ ] luaL_checktype - [ ] luaL_checkudata - [ ] luaL_checkversion - [ ] luaL_dofile @@ -204,6 +204,8 @@ - [ ] Standard library - [x] tostring - [x] print + - [x] getmetatable + - [x] setmetatable - [ ] ... - [ ] Debug (errors) - [ ] DOM API binding diff --git a/src/lapi.js b/src/lapi.js index ece02e0..e1e548d 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -260,6 +260,33 @@ const lua_setglobal = function(L, name) { auxsetstr(L, L.l_G.l_registry.value.array[lua.LUA_RIDX_GLOBALS - 1], name); }; +const lua_setmetatable = function(L, objindex) { + assert(1 < L.top - L.ci.funcOff, "not enough elements in the stack"); + let mt; + let obj = index2addr(L, objindex); + if (L.stack[L.top - 1].ttisnil()) + mt = null; + else { + assert(L.stack[L.top - 1].ttistable(), "table expected"); + mt = L.stack[L.top - 1]; + } + + switch (obj.ttnov()) { + case CT.LUA_TUSERDATA: + case CT.LUA_TTABLE: { + obj.metatable = mt; + break; + } + default: { + L.l_G.mt[obj.ttnov()] = mt; + break; + } + } + + L.top--; + return true; +}; + const lua_settable = function(L, idx) { assert(2 < L.top - L.ci.funcOff, "not enough elements in the stack"); @@ -562,4 +589,6 @@ module.exports.lua_pushglobaltable = lua_pushglobaltable; module.exports.lua_setfield = lua_setfield; module.exports.lua_getfield = lua_getfield; module.exports.lua_getglobal = lua_getglobal; -module.exports.lua_getmetatable = lua_getmetatable; \ No newline at end of file +module.exports.lua_getmetatable = lua_getmetatable; +module.exports.lua_setmetatable = lua_setmetatable; +module.exports.lua_settop = lua_settop; \ No newline at end of file diff --git a/src/lauxlib.js b/src/lauxlib.js index 6db0aae..a1f9d64 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -13,7 +13,27 @@ const LUA_LOADED_TABLE = "_LOADED" const panic = function(L) { console.log(`PANIC: unprotected error in call to Lua API (...)`); return 0; -} +}; + +const typeerror = function(L, arg, tname) { + let typearg; + if (luaL_getmetafield(L, arg, "__name") === CT.LUA_TSTRING) + typearg = lapi.lua_tostring(L, -1); + else if (lapi.lua_type(L, arg) === CT.LUA_TLIGHTUSERDATA) + typearg = "light userdata"; + else + typearg = luaL_typename(L, arg); + + throw new Error(`${tname} expected, got ${typearg}`); + + // TODO: + // let msg = lua_pushstring(L, `${tname} expected, got ${typearg}`); + // return luaL_argerror(L, arg, msg); +}; + +const tag_error = function(L, arg, tag) { + typeerror(L, arg, lapi.lua_typename(L, tag)); +}; const luaL_newstate = function() { let L = lstate.lua_newstate(); @@ -26,11 +46,20 @@ const luaL_typename = function(L, i) { return lapi.lua_typename(L, lapi.lua_type(L, i)); }; +const luaL_argcheck = function(L, cond, arg, extramsg) { + if (!cond) throw new Error("bad argument"); // TODO: luaL_argerror +}; + const luaL_checkany = function(L, arg) { if (lapi.lua_type(L, arg) === CT.LUA_TNONE) throw new Error("value expected"); // TODO: luaL_argerror(L, arg, "value expected"); }; +const luaL_checktype = function(L, arg, t) { + if (lapi.lua_type(L, arg) !== t) + tag_error(L, arg, t); +}; + const luaL_getmetafield = function(L, obj, event) { if (!lapi.lua_getmetatable(L, obj)) return CT.LUA_TNIL; @@ -157,6 +186,7 @@ const luaL_checkstack = function(L, space, msg) { module.exports.luaL_newstate = luaL_newstate; module.exports.luaL_typename = luaL_typename; module.exports.luaL_checkany = luaL_checkany; +module.exports.luaL_checktype = luaL_checktype; module.exports.luaL_callmeta = luaL_callmeta; module.exports.luaL_getmetafield = luaL_getmetafield; module.exports.luaL_requiref = luaL_requiref; @@ -164,4 +194,5 @@ module.exports.luaL_getsubtable = luaL_getsubtable; module.exports.luaL_setfuncs = luaL_setfuncs; module.exports.luaL_checkstack = luaL_checkstack; module.exports.LUA_LOADED_TABLE = LUA_LOADED_TABLE; -module.exports.luaL_tolstring = luaL_tolstring; \ No newline at end of file +module.exports.luaL_tolstring = luaL_tolstring; +module.exports.luaL_argcheck = luaL_argcheck; \ No newline at end of file diff --git a/src/lbaselib.js b/src/lbaselib.js index b046f67..12c28fe 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -6,6 +6,7 @@ const assert = require('assert'); const lua = require('./lua.js'); const lapi = require('./lapi.js'); const lauxlib = require('./lauxlib.js'); +const CT = lua.constant_types; const luaB_print = function(L) { let n = lapi.lua_gettop(L); /* number of arguments */ @@ -35,9 +36,32 @@ const luaB_tostring = function(L) { return true; }; +const luaB_getmetatable = function(L) { + lauxlib.luaL_checkany(L, 1); + if (!lapi.lua_getmetatable(L, 1)) { + lapi.lua_pushnil(L); + return true; /* no metatable */ + } + lauxlib.luaL_getmetafield(L, 1, "__metatable"); + return true; /* returns either __metatable field (if present) or metatable */ +}; + +const luaB_setmetatable = function(L) { + let t = lapi.lua_type(L, 2); + lauxlib.luaL_checktype(L, 1, CT.LUA_TTABLE); + lauxlib.luaL_argcheck(L, t === CT.LUA_TNIL || t === CT.LUA_TTABLE, 2, "nil or table expected"); + if (lauxlib.luaL_getmetafield(L, 1, "__metatable") !== CT.LUA_TNIL) + throw new Error("cannot change a protected metatable"); + lapi.lua_settop(L, 2); + lapi.lua_setmetatable(L, 1); + return true; +}; + const base_funcs = { - "print": luaB_print, - "tostring": luaB_tostring, + "print": luaB_print, + "tostring": luaB_tostring, + "getmetatable": luaB_getmetatable, + "setmetatable": luaB_setmetatable, }; const luaopen_base = function(L) { @@ -53,6 +77,8 @@ const luaopen_base = function(L) { return true; }; -module.exports.luaB_tostring = luaB_tostring; -module.exports.luaB_print = luaB_print; -module.exports.luaopen_base = luaopen_base; +module.exports.luaB_tostring = luaB_tostring; +module.exports.luaB_print = luaB_print; +module.exports.luaB_getmetatable = luaB_getmetatable; +module.exports.luaB_setmetatable = luaB_setmetatable; +module.exports.luaopen_base = luaopen_base; diff --git a/tests/lapi.js b/tests/lapi.js index 561b774..30366d5 100644 --- a/tests/lapi.js +++ b/tests/lapi.js @@ -490,27 +490,4 @@ test('lua_settable, lua_gettable', function (t) { "value", "Correct element(s) on the stack" ); -}); - - -test('print', function (t) { - let luaCode = ` - print("hello", "world", 123) - `, L; - - t.plan(1); - - t.doesNotThrow(function () { - - let bc = toByteCode(luaCode).dataView; - - L = lauxlib.luaL_newstate(); - - linit.luaL_openlibs(L); - - lapi.lua_load(L, bc, "test-lua_load") - - lapi.lua_call(L, 0, 1); - - }, "JS Lua program ran without error"); }); \ No newline at end of file diff --git a/tests/lbaselib.js b/tests/lbaselib.js new file mode 100644 index 0000000..3ddec2d --- /dev/null +++ b/tests/lbaselib.js @@ -0,0 +1,85 @@ +/*jshint esversion: 6 */ +"use strict"; + +const test = require('tape'); +const beautify = require('js-beautify').js_beautify; + +const tests = require("./tests.js"); +const getState = tests.getState; +const toByteCode = tests.toByteCode; + +const VM = require("../src/lvm.js"); +const ldo = require("../src/ldo.js"); +const lapi = require("../src/lapi.js"); +const lauxlib = require("../src/lauxlib.js"); +const lua = require('../src/lua.js'); +const linit = require('../src/linit.js'); +const CT = lua.constant_types; + + +test('print', function (t) { + let luaCode = ` + print("hello", "world", 123) + `, L; + + t.plan(1); + + t.doesNotThrow(function () { + + let bc = toByteCode(luaCode).dataView; + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, bc, "test-print"); + + lapi.lua_call(L, 0, 1); + + }, "JS Lua program ran without error"); +}); + + +test('setmetatable, getmetatable', function (t) { + let luaCode = ` + local mt = { + __index = function () + print("hello") + return "hello" + end + } + + local t = {} + + setmetatable(t, mt); + + return t[1], getmetatable(t) + `, L; + + t.plan(3); + + t.doesNotThrow(function () { + + let bc = toByteCode(luaCode).dataView; + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lapi.lua_load(L, bc, "test-setmetatable-getmetatable"); + + lapi.lua_call(L, 0, -1); + + }, "JS Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "hello", + "Correct element(s) on the stack" + ); + + t.ok( + lapi.lua_istable(L, -1), + "Correct element(s) on the stack" + ); +}); \ No newline at end of file -- cgit v1.2.3-70-g09d2