TS-4099: add core library infrastructure for Lua bindings This adds a libbindings.la library with a convenience wrapper class for dealing with a Lua state and binding APIs into it. We also add some low-level Lua convenience APIs that end up being useful.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/5942c420 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/5942c420 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/5942c420 Branch: refs/heads/master Commit: 5942c42020fde09e348c418ae0aec41cd8e4cf91 Parents: 4e2f0b8 Author: James Peach <[email protected]> Authored: Tue Nov 24 15:59:54 2015 -0800 Committer: James Peach <[email protected]> Committed: Thu Jan 21 18:52:18 2016 -0800 ---------------------------------------------------------------------- configure.ac | 1 + lib/Makefile.am | 2 +- lib/bindings/Makefile.am | 40 ++++++ lib/bindings/bindings.cc | 275 ++++++++++++++++++++++++++++++++++++++++++ lib/bindings/bindings.h | 72 +++++++++++ lib/bindings/lua.cc | 63 ++++++++++ lib/bindings/lua.h | 68 +++++++++++ lib/bindings/repl.cc | 60 +++++++++ lib/bindings/repl.h | 30 +++++ 9 files changed, 610 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 5712cf8..56793a1 100644 --- a/configure.ac +++ b/configure.ac @@ -1846,6 +1846,7 @@ AC_CONFIG_FILES([ iocore/net/Makefile iocore/utils/Makefile lib/Makefile + lib/bindings/Makefile lib/perl/Makefile lib/perl/lib/Apache/TS.pm lib/records/Makefile http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/Makefile.am ---------------------------------------------------------------------- diff --git a/lib/Makefile.am b/lib/Makefile.am index 3306483..685b6be 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -SUBDIRS = ts records tsconfig +SUBDIRS = ts records tsconfig bindings if BUILD_PERL_LIB SUBDIRS += perl http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/Makefile.am ---------------------------------------------------------------------- diff --git a/lib/bindings/Makefile.am b/lib/bindings/Makefile.am new file mode 100644 index 0000000..661411c --- /dev/null +++ b/lib/bindings/Makefile.am @@ -0,0 +1,40 @@ +# Makefile.am +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +AM_CPPFLAGS = \ + $(LUAJIT_CPPFLAGS) \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/lib/records + +AM_CXXFLAGS = \ + $(LUAJIT_CFLAGS) + +if BUILD_LUAJIT + +noinst_LTLIBRARIES = libbindings.la + +libbindings_la_SOURCES = \ + bindings.cc \ + bindings.h \ + lua.cc \ + lua.h \ + repl.cc \ + repl.h + +endif + http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/bindings.cc ---------------------------------------------------------------------- diff --git a/lib/bindings/bindings.cc b/lib/bindings/bindings.cc new file mode 100644 index 0000000..9cd01b8 --- /dev/null +++ b/lib/bindings/bindings.cc @@ -0,0 +1,275 @@ +/** @file + * + * A brief file description + * + * @section license License + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bindings.h" +#include "ts/Diags.h" + +static const char selfkey[] = "bb3ecc8d-de6b-4f48-9aca-b3a3f14bdbad"; + +static bool +is_indexable(lua_State *L, int index) +{ + return lua_istable(L, index) || lua_isuserdata(L, index); +} + +BindingInstance::BindingInstance() : lua(NULL) +{ +} + +BindingInstance::~BindingInstance() +{ + if (this->lua) { + lua_close(this->lua); + } +} + +void +BindingInstance::attach_ptr(const char *name, void *ptr) +{ + this->attachments[name] = ptr; +} + +void * +BindingInstance::retrieve_ptr(const char *name) +{ + auto ptr = this->attachments.find(name); + return (ptr == this->attachments.end()) ? NULL : ptr->second; +} + +void +BindingInstance::bind_constant(const char *name, lua_Integer value) +{ + lua_pushinteger(this->lua, value); + this->bind_value(name, -1); + lua_pop(this->lua, 1); +} + +void +BindingInstance::bind_constant(const char *name, const char *value) +{ + lua_pushlstring(this->lua, value, strlen(value)); + this->bind_value(name, -1); + lua_pop(this->lua, 1); +} + +void +BindingInstance::bind_function(const char *name, int (*value)(lua_State *)) +{ + lua_pushcfunction(this->lua, value); + this->bind_value(name, -1); + lua_pop(this->lua, 1); +} + +// Bind an arbitrary Lua value from the give stack position. +void +BindingInstance::bind_value(const char *name, int value) +{ + const char * start = name; + const char * end = name; + + int depth = 0; + + // Make the value an absolute stack inde because we are going to + // invalidate relative indices. + value = lua_absolute_index(this->lua, value); + + // XXX extract this code so that we can using it for binding constants + // into an arbitrary table path ... + Debug("lua", "binding %s value at %d to %s\n", luaL_typename(this->lua, value), value, name); + + for (; (end = ::strchr(start, '.')); start = end + 1) { + std::string name(start, end); + + Debug("lua", "checking for table '%s'\n", name.c_str()); + if (depth == 0) { + lua_getglobal(this->lua, name.c_str()); + if (lua_isnil(this->lua, -1)) { + // No table with this name, construct one. + Debug("lua", "creating global table '%s'\n", name.c_str()); + + lua_pop(this->lua, 1); // Pop the nil. + lua_newtable(this->lua); + lua_setglobal(this->lua, name.c_str()); + lua_getglobal(this->lua, name.c_str()); + + // Top of stack MUST be a table now. + ink_assert(lua_istable(this->lua, -1)); + } + + ink_assert(is_indexable(this->lua, -1)); + } else { + ink_assert(is_indexable(this->lua, -1)); + + Debug("lua", "checking for table key '%s'\n", name.c_str()); + + // Push the string key. + lua_pushlstring(this->lua, &name[0], name.size()); + // Get the table entry (now on top of the stack). + lua_gettable(this->lua, -2); + + if (lua_isnil(this->lua, -1)) { + Debug("lua", "creating table key '%s'\n", name.c_str()); + + lua_pop(this->lua, 1); // Pop the nil. + lua_pushlstring(this->lua, &name[0], name.size()); + lua_newtable(this->lua); + + // Set the table entry. The stack now looks like: + // -1 value (the new table) + // -2 index (string) + // -3 target (the table to add the index to) + lua_settable(this->lua, -3); + + // Get the table entry we just created. + lua_pushlstring(this->lua, &name[0], name.size()); + lua_gettable(this->lua, -2); + + // Top of stack MUST be a table now. + ink_assert(lua_istable(this->lua, -1)); + } + + // The new entry is on top of the stack. + ink_assert(is_indexable(this->lua, -1)); + } + + ++depth; + } + + Debug("lua", "stack depth is %d (expected %d)\n", lua_gettop(this->lua), depth); + Debug("lua", "last name token is '%s'\n", start); + + // If we pushed a series of tables onto the stack, bind the name to a table + // entry. otherwise bind it as a global name. + if (depth) { + lua_pushstring(this->lua, start); + lua_pushvalue(this->lua, value); + + ink_assert(is_indexable(this->lua, -3)); + + lua_settable(this->lua, -3); + Debug("lua", "stack depth is %d (expected %d)\n", lua_gettop(this->lua), depth); + lua_pop(this->lua, depth); + } else { + lua_pushvalue(this->lua, value); + lua_setglobal(this->lua, start); + } +} + +bool +BindingInstance::construct() +{ + ink_release_assert(this->lua == NULL); + + if ((this->lua = luaL_newstate())) { + luaL_openlibs(this->lua); + + // Push a pointer to ourself into the well-known registry key. + lua_pushlightuserdata(this->lua, this); + lua_setfield(this->lua, LUA_REGISTRYINDEX, selfkey); + + ink_release_assert(BindingInstance::self(this->lua) == this); + } + + return this->lua; +} + +bool +BindingInstance::require(const char *path) +{ + ink_release_assert(this->lua != NULL); + + if (luaL_dofile(this->lua, path) != 0) { + Warning("%s", lua_tostring(this->lua, -1)); + lua_pop(this->lua, 1); + return false; + } + + return true; +} + +BindingInstance * +BindingInstance::self(lua_State *lua) +{ + BindingInstance *binding; + + lua_getfield(lua, LUA_REGISTRYINDEX, selfkey); + binding = (BindingInstance *)lua_touserdata(lua, -1); + + ink_release_assert(binding != NULL); + ink_release_assert(binding->lua == lua); + + lua_pop(lua, 1); + return binding; +} + +void +BindingInstance::typecheck(lua_State *lua, const char *name, ...) +{ + int nargs = lua_gettop(lua); + int seen = 0; + va_list ap; + + va_start(ap, name); + + for (; seen < nargs; ++seen) { + int expected = va_arg(ap, int); + + if (expected == LUA_TNONE) { + va_end(ap); + luaL_error(lua, "too many arguments to '%s'", name); + return; + } + + if (lua_type(lua, seen + 1) != expected) { + va_end(ap); + luaL_error(lua, "bad argument #%d to '%s' (expected %s, received %s)", seen + 1, name, lua_typename(lua, expected), + lua_typename(lua, lua_type(lua, seen + 1))); + return; + } + } + + va_end(ap); + + if (seen != nargs) { + luaL_error(lua, "too few arguments to '%s' (seen %d, nargs %d)", name, seen, nargs); + } +} + +void +BindingInstance::register_metatable(lua_State *lua, const char *name, const luaL_reg *metatable) +{ + // Create a metatable, adding it to the Lua registry. + luaL_newmetatable(lua, name); + // Dup the metatable. + lua_pushvalue(lua, -1); + // Pop one of those copies and assign it to __index field on the 1st metatable + lua_setfield(lua, -2, "__index"); + // register functions in the metatable + luaL_register(lua, NULL, metatable); + + lua_pop(lua, 1); /* drop metatable */ + + ink_assert(lua_gettop(lua) == 0); +} + +/* vim: set sw=4 ts=4 tw=79 et: */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/bindings.h ---------------------------------------------------------------------- diff --git a/lib/bindings/bindings.h b/lib/bindings/bindings.h new file mode 100644 index 0000000..525cb60 --- /dev/null +++ b/lib/bindings/bindings.h @@ -0,0 +1,72 @@ +/** @file + * + * Lua bindings object. + * + * @section license License + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BINDINGS_H_02DF784C_94BD_4A5C_B57A_F986F5493C6A +#define BINDINGS_H_02DF784C_94BD_4A5C_B57A_F986F5493C6A + +#include <string> +#include <map> +#include "lua.h" + +struct BindingInstance { + BindingInstance(); + ~BindingInstance(); + + // Construct this Lua bindings instance. + bool construct(); + + // Import a Lua file. + bool require(const char *path); + + // Bind values to the specified global name. If the name contains '.' + // separators, intermediate tables are constucted and the value is bound + // to the final path component. + void bind_constant(const char *name, lua_Integer value); + void bind_constant(const char *name, const char *value); + void bind_function(const char *name, int (*value)(lua_State *)); + void bind_value(const char *name, int value); + + // Attach a named pointer that we can later fish out from a Lua state. + void attach_ptr(const char *, void *); + void *retrieve_ptr(const char *); + + // Generic typecheck helper for Lua APIs. Pass in a list of Lua type IDs + // (ie. LUA_Txxx) terminated by LUA_TNONE. Throws a Lua error string on + // failure. + static void typecheck(lua_State *, const char *name, ...); + + // Given a Lua state, return the binding instance that owns it. + static BindingInstance *self(lua_State *); + + // Register a Lua metatable for a custom type. + static void register_metatable(lua_State *, const char *, const luaL_reg *); + + lua_State *lua; + +private: + std::map<std::string, void *> attachments; + BindingInstance(const BindingInstance &); // noncopyable + BindingInstance &operator=(const BindingInstance &); // noncopyable +}; + +#endif /* BINDINGS_H_02DF784C_94BD_4A5C_B57A_F986F5493C6A */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/lua.cc ---------------------------------------------------------------------- diff --git a/lib/bindings/lua.cc b/lib/bindings/lua.cc new file mode 100644 index 0000000..57be614 --- /dev/null +++ b/lib/bindings/lua.cc @@ -0,0 +1,63 @@ +/** @file + * + * Lua utilities and extensions. + * + * @section license License + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "lua.h" + +int +lua_absolute_index(lua_State *L, int relative) +{ + return (relative > 0 || relative <= LUA_REGISTRYINDEX) ? relative : lua_gettop(L) + relative + 1; +} + +// Check the type at the given index. Error if it is not the expected type. +void +lua_checktype(lua_State *L, int index, int ltype) +{ + if (lua_type(L, index) != ltype) { + luaL_error(L, "bad type, expected '%s' but found '%s'", lua_typename(L, ltype), lua_typename(L, lua_type(L, index))); + } +} + +// luaL_checkudata() throws an exception if it fails, so to accept variadic +// user types, we need to non-destructively test whether a userdata is an +// instance of the type we want. +bool +lua_is_userdata(lua_State *L, int index, const char *metatype) +{ + int target = lua_absolute_index(L, index); + bool result = false; + + // Get the metatable of the target. + if (lua_getmetatable(L, target) != 0) { + // If there was one, get the metatable of the target type. + luaL_getmetatable(L, metatype); + + // Compare them. + result = lua_equal(L, -1, -2) == 1; + + // Pop the 2 metatables. + lua_pop(L, 2); + } + + return result; +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/lua.h ---------------------------------------------------------------------- diff --git a/lib/bindings/lua.h b/lib/bindings/lua.h new file mode 100644 index 0000000..8314df6 --- /dev/null +++ b/lib/bindings/lua.h @@ -0,0 +1,68 @@ +/** @file + * + * Lua utilities and extensions. + * + * @section license License + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LUA_H_7A9F5CCE_01C6_45C3_987A_FDCC1F437AA2 +#define LUA_H_7A9F5CCE_01C6_45C3_987A_FDCC1F437AA2 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +// Redeclare luaL_error with format string checking. +LUALIB_API int(luaL_error)(lua_State *L, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + +#ifdef __cplusplus +} +#endif + +// Our version of abs_index() from lauxlib.c. Converts a absolute or relative +// stack index into an absolute index. This is helpful for functions that +// accepts an index but are going to do stack manipulation themselves. +int lua_absolute_index(lua_State *L, int relative); + +// Check the type at the given index. Error if it is not the expected type. +void lua_checktype(lua_State *L, int index, int ltype); + +// luaL_checkudata() throws an exception if it fails, so to accept variadic +// user types, we need to non-destructively test whether a userdata is an +// instance of the type we want. +bool lua_is_userdata(lua_State *L, int index, const char *metatype); + +// Like lua_newuserdata() but for C++ objects. +template <typename T> +T * +lua_newuserobject(lua_State *L) +{ + T *ptr = (T *)lua_newuserdata(L, sizeof(T)); + if (ptr) { + return new (ptr) T(); + } + + return nullptr; +} + +#endif /* LUA_H_7A9F5CCE_01C6_45C3_987A_FDCC1F437AA2 */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/repl.cc ---------------------------------------------------------------------- diff --git a/lib/bindings/repl.cc b/lib/bindings/repl.cc new file mode 100644 index 0000000..2931db8 --- /dev/null +++ b/lib/bindings/repl.cc @@ -0,0 +1,60 @@ +/** @file + * + * Lua bindings REPL. + * + * @section license License + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ink_autoconf.h" +#include "bindings.h" +#include <stdlib.h> + +#if HAVE_READLINE_H +#include <readline.h> +#endif + +void +repl(BindingInstance &binding) +{ +#if HAVE_READLINE_H + for (;;) { + char *line; + + line = readline("> "); + if (line == NULL) { + exit(0); + } + + if (*line) { + ::add_history(line); + + if (luaL_loadbuffer(this->lua, line, ::strlen(line), "@stdin" /* source */) != 0 || + lua_pcall(this->lua, 0, LUA_MULTRET, 0) != 0) { + // Pop the error message off the top of the stack and show it ... + error("%s\n", lua_tostring(this->lua, -1)); + lua_pop(this->lua, 1); + } + } + + ::free(line); + } + +#endif /* HAVE_READLINE_H */ + ::exit(0); +} http://git-wip-us.apache.org/repos/asf/trafficserver/blob/5942c420/lib/bindings/repl.h ---------------------------------------------------------------------- diff --git a/lib/bindings/repl.h b/lib/bindings/repl.h new file mode 100644 index 0000000..944a344 --- /dev/null +++ b/lib/bindings/repl.h @@ -0,0 +1,30 @@ +/** @file + * + * Lua bindings REPL. + * + * @section license License + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef REPL_H_DB690467_F8FE_4797_8B08_7FD1F090DBA2 +#define REPL_H_DB690467_F8FE_4797_8B08_7FD1F090DBA2 + +// Drop this binding instance into a Lua REPL (and never come out). +void repl(BindingInstance &binding) TS_NORETURN; + +#endif /* REPL_H_DB690467_F8FE_4797_8B08_7FD1F090DBA2 */
