commit 4b2c3eb9d49a797f91eee2be4e41cacb3b8167e7 Author: Brooks Davis Date: Tue Mar 17 16:48:52 2020 +0000 Import lutok, a Lightweight C++ API for Lua. This a snapshot of the latest version with git hash: 8f8eaef. Obtained from: https://github.com/jmmv/lutok Sponsored by: DARPA diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..f1e0159d4427 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.la +*.lo +*.o +*_test + +.deps +.libs +Doxyfile +Makefile +Makefile.in +aclocal.m4 +api-docs +autom4te.cache +config.h +config.h.in +config.log +config.status +configure +libtool +lutok.pc +stamp-h1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..1e2fb77df152 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: cpp + +compiler: + - gcc + - clang + +before_install: + - ./admin/travis-install-deps.sh + +env: + - ARCH=amd64 AS_ROOT=no + - ARCH=amd64 AS_ROOT=yes + - ARCH=i386 AS_ROOT=no + +matrix: + exclude: + - compiler: clang + env: ARCH=i386 AS_ROOT=no + +script: + - ./admin/travis-build.sh + +notifications: + email: + - lutok-log@googlegroups.com diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000000..0f707683aa82 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +* Julio Merino diff --git a/COPYING b/COPYING new file mode 100644 index 000000000000..be29aafc53cf --- /dev/null +++ b/COPYING @@ -0,0 +1,27 @@ +Copyright 2011, 2012 Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of Google Inc. nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Doxyfile.in b/Doxyfile.in new file mode 100644 index 000000000000..73ad7bfdbe23 --- /dev/null +++ b/Doxyfile.in @@ -0,0 +1,53 @@ +# Copyright 2010 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +BUILTIN_STL_SUPPORT = YES +ENABLE_PREPROCESSING = YES +EXTRACT_ANON_NSPACES = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +EXPAND_ONLY_PREDEF = YES +FILE_PATTERNS = *.cpp *.hpp *.ipp +GENERATE_LATEX = NO +GENERATE_TAGFILE = @top_builddir@/api-docs/api-docs.tag +INPUT = @top_srcdir@ +INPUT_ENCODING = ISO-8859-1 +JAVADOC_AUTOBRIEF = YES +MACRO_EXPANSION = YES +OUTPUT_DIRECTORY = @top_builddir@/api-docs +OUTPUT_LANGUAGE = English +PREDEFINED = "UTILS_UNUSED_PARAM(name)=unused_ ## name" +PROJECT_NAME = "@PACKAGE_NAME@" +PROJECT_NUMBER = @VERSION@ +QUIET = YES +RECURSIVE = NO +SHORT_NAMES = YES # Cope with gnutar limitations during 'make dist'. +SORT_BY_SCOPE_NAME = YES +SORT_MEMBERS_CTORS_1ST = YES +WARN_NO_PARAMDOC = YES diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000000..890b0e1b05af --- /dev/null +++ b/INSTALL @@ -0,0 +1,181 @@ +Introduction +============ + +Lutok uses the GNU Automake, GNU Autoconf and GNU Libtool utilities as +its build system. These are used only when compiling the library from +the source code package. If you want to install Lutok from a binary +package, you do not need to read this document. + +For the impatient: + + $ ./configure + $ make + $ make check + Gain root privileges + # make install + Drop root privileges + $ make installcheck + +Or alternatively, install as a regular user into your home directory: + + $ ./configure --prefix ~/local + $ make + $ make check + $ make install + $ make installcheck + + +Dependencies +============ + +To build and use Lutok successfully you need: + +* A standards-compliant C++ complier. +* Lua 5.1 or greater. +* pkg-config. + +Optionally, if you want to build and run the tests (recommended), you +need: + +* Kyua 0.5 or greater. +* ATF 0.15 or greater. + +If you are building Lutok from the code on the repository, you will also +need the following tools: + +* GNU Autoconf. +* GNU Automake. +* GNU Libtool. + + +Regenerating the build system +============================= + +This is not necessary if you are building from a formal release +distribution file. + +On the other hand, if you are building Lutok from code extracted from +the repository, you must first regenerate the files used by the build +system. You will also need to do this if you modify configure.ac, +Makefile.am or any of the other build system files. To do this, simply +run: + + $ autoreconf -i -s + +If ATF is installed in a different prefix than Autoconf, you will also +need to tell autoreconf where the ATF M4 macros are located. Otherwise, +the configure script will be incomplete and will show confusing syntax +errors mentioning, for example, ATF_CHECK_SH. To fix this, you have +to run autoreconf in the following manner, replacing '' with +the appropriate path: + + $ autoreconf -i -s -I /share/aclocal + + +General build procedure +======================= + +To build and install the source package, you must follow these steps: + +1. Configure the sources to adapt to your operating system. This is + done using the 'configure' script located on the sources' top + directory, and it is usually invoked without arguments unless you + want to change the installation prefix. More details on this + procedure are given on a later section. + +2. Build the sources to generate the binaries and scripts. Simply run + 'make' on the sources' top directory after configuring them. No + problems should arise. + +3. Install the library by running 'make install'. You may need to + become root to issue this step. + +4. Issue any manual installation steps that may be required. These are + described later in their own section. + +5. Check that the installed library works by running 'make + installcheck'. You do not need to be root to do this. + + +Configuration flags +=================== + +The most common, standard flags given to 'configure' are: + +* --prefix=directory + Possible values: Any path + Default: /usr/local + + Specifies where the library (binaries and all associated files) will + be installed. + +* --help + Shows information about all available flags and exits immediately, + without running any configuration tasks. + +The following flags are specific to Lutok's 'configure' script: + +* --enable-developer + Possible values: yes, no + Default: 'yes' in Git HEAD builds; 'no' in formal releases. + + Enables several features useful for development, such as the inclusion + of debugging symbols in all objects or the enforcement of compilation + warnings. + + The compiler will be executed with an exhaustive collection of warning + detection features regardless of the value of this flag. However, such + warnings are only fatal when --enable-developer is 'yes'. + +* --with-atf + Possible values: yes, no, auto. + Default: auto. + + Enables usage of ATF to build (and later install) the tests. + + Setting this to 'yes' causes the configure script to look for ATF + unconditionally and abort if not found. Setting this to 'auto' lets + configure perform the best decision based on availability of ATF. + Setting this to 'no' explicitly disables ATF usage. + + When support for tests is enabled, the build process will generate the + test programs and will later install them into the tests tree. + Running 'make check' or 'make installcheck' from within the source + directory will cause these tests to be run with Kyua (assuming it is + also installed). + +* --with-doxygen + Possible values: yes, no, auto or a path. + Default: auto. + + Enables usage of Doxygen to generate documentation for internal APIs. + + Setting this to 'yes' causes the configure script to look for Doxygen + unconditionally and abort if not found. Setting this to 'auto' lets + configure perform the best decision based on availability of Doxygen. + Setting this to 'no' explicitly disables Doxygen usage. And, lastly, + setting this to a path forces configure to use a specific Doxygen + binary, which must exist. + + When support for Doxygen is enabled, the build process will generate + HTML documentation for the Lutok API. This documentation will later + be installed in the HTML directory specified by the configure script. + You can change the location of the HTML documents by providing your + desired override with the '--htmldir' flag to the configure script. + + +Run the tests! +============== + +Lastly, after a successful installation (and assuming you built the +sources with support for ATF), you should periodically run the tests +from the final location to ensure things remain stable. Do so as +follows: + + $ kyua test -k /usr/local/tests/lutok/Kyuafile + +And if you see any tests fail, do not hesitate to report them in: + + https://github.com/jmmv/lutok/issues/ + +Thank you! diff --git a/Kyuafile b/Kyuafile new file mode 100644 index 000000000000..48c912dab730 --- /dev/null +++ b/Kyuafile @@ -0,0 +1,11 @@ +syntax("kyuafile", 1) + +test_suite("lutok") + +atf_test_program{name="c_gate_test"} +atf_test_program{name="debug_test"} +atf_test_program{name="examples_test"} +atf_test_program{name="exceptions_test"} +atf_test_program{name="operations_test"} +atf_test_program{name="stack_cleaner_test"} +atf_test_program{name="state_test"} diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000000..ec7e87814548 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,221 @@ +# Copyright 2010 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +ACLOCAL_AMFLAGS = -I m4 + +doc_DATA = AUTHORS COPYING NEWS README +noinst_DATA = INSTALL README +EXTRA_DIST = $(doc_DATA) INSTALL README + +LUTOK_CFLAGS = -I$(srcdir)/include $(LUA_CFLAGS) +LUTOK_LIBS = liblutok.la $(LUA_LIBS) + +pkginclude_HEADERS = c_gate.hpp +pkginclude_HEADERS += debug.hpp +pkginclude_HEADERS += exceptions.hpp +pkginclude_HEADERS += operations.hpp +pkginclude_HEADERS += stack_cleaner.hpp +pkginclude_HEADERS += state.hpp +pkginclude_HEADERS += state.ipp +pkginclude_HEADERS += test_utils.hpp + +EXTRA_DIST += include/lutok/README +EXTRA_DIST += include/lutok/c_gate.hpp +EXTRA_DIST += include/lutok/debug.hpp +EXTRA_DIST += include/lutok/exceptions.hpp +EXTRA_DIST += include/lutok/operations.hpp +EXTRA_DIST += include/lutok/stack_cleaner.hpp +EXTRA_DIST += include/lutok/state.hpp +EXTRA_DIST += include/lutok/state.ipp + +lib_LTLIBRARIES = liblutok.la +liblutok_la_SOURCES = c_gate.cpp +liblutok_la_SOURCES += c_gate.hpp +liblutok_la_SOURCES += debug.cpp +liblutok_la_SOURCES += debug.hpp +liblutok_la_SOURCES += exceptions.cpp +liblutok_la_SOURCES += exceptions.hpp +liblutok_la_SOURCES += operations.cpp +liblutok_la_SOURCES += operations.hpp +liblutok_la_SOURCES += stack_cleaner.cpp +liblutok_la_SOURCES += stack_cleaner.hpp +liblutok_la_SOURCES += state.cpp +liblutok_la_SOURCES += state.hpp +liblutok_la_SOURCES += state.ipp +liblutok_la_SOURCES += test_utils.hpp +liblutok_la_CPPFLAGS = $(LUTOK_CFLAGS) +liblutok_la_LDFLAGS = -version-info 3:0:0 +liblutok_la_LIBADD = $(LUA_LIBS) + +pkgconfig_DATA = lutok.pc +CLEANFILES = lutok.pc +EXTRA_DIST += lutok.pc.in +lutok.pc: $(srcdir)/lutok.pc.in Makefile + $(AM_V_GEN)sed -e 's#__INCLUDEDIR__#$(includedir)#g' \ + -e 's#__LIBDIR__#$(libdir)#g' \ + -e 's#__LUA_CFLAGS__#$(LUA_CFLAGS)#g' \ + -e 's#__LUA_LIBS__#$(LUA_LIBS)#g' \ + -e 's#__VERSION__#$(PACKAGE_VERSION)#g' \ + <$(srcdir)/lutok.pc.in >lutok.pc.tmp; \ + mv lutok.pc.tmp lutok.pc + +CLEAN_TARGETS = +DIST_HOOKS = +PHONY_TARGETS = + +examplesdir = $(docdir)/examples +examples_DATA = examples/Makefile +examples_DATA += examples/bindings.cpp +examples_DATA += examples/hello.cpp +examples_DATA += examples/interpreter.cpp +examples_DATA += examples/raii.cpp +EXTRA_DIST += $(examples_DATA) + +if WITH_ATF +tests_DATA = Kyuafile +EXTRA_DIST += $(tests_DATA) + +tests_PROGRAMS = c_gate_test +c_gate_test_SOURCES = c_gate_test.cpp test_utils.hpp +c_gate_test_CXXFLAGS = $(LUTOK_CFLAGS) $(ATF_CXX_CFLAGS) +c_gate_test_LDADD = $(LUTOK_LIBS) $(ATF_CXX_LIBS) + +tests_PROGRAMS += debug_test +debug_test_SOURCES = debug_test.cpp test_utils.hpp +debug_test_CXXFLAGS = $(LUTOK_CFLAGS) $(ATF_CXX_CFLAGS) +debug_test_LDADD = $(LUTOK_LIBS) $(ATF_CXX_LIBS) + +tests_SCRIPTS = examples_test +CLEANFILES += examples_test +EXTRA_DIST += examples_test.sh +examples_test: $(srcdir)/examples_test.sh + $(AM_V_GEN)sed -e 's,__ATF_SH__,$(ATF_SH),g' \ + -e 's,__CXX__,$(CXX),g' \ + -e 's,__EXAMPLESDIR__,$(examplesdir),g' \ + -e 's,__LIBDIR__,$(libdir),g' \ + <$(srcdir)/examples_test.sh >examples_test.tmp; \ + chmod +x examples_test.tmp; \ + rm -f examples_test; \ + mv examples_test.tmp examples_test + +tests_PROGRAMS += exceptions_test +exceptions_test_SOURCES = exceptions_test.cpp +exceptions_test_CXXFLAGS = $(LUTOK_CFLAGS) $(ATF_CXX_CFLAGS) +exceptions_test_LDADD = $(LUTOK_LIBS) $(ATF_CXX_LIBS) + +tests_PROGRAMS += operations_test +operations_test_SOURCES = operations_test.cpp test_utils.hpp +operations_test_CXXFLAGS = $(LUTOK_CFLAGS) $(ATF_CXX_CFLAGS) +operations_test_LDADD = $(LUTOK_LIBS) $(ATF_CXX_LIBS) + +tests_PROGRAMS += stack_cleaner_test +stack_cleaner_test_SOURCES = stack_cleaner_test.cpp test_utils.hpp +stack_cleaner_test_CXXFLAGS = $(LUTOK_CFLAGS) $(ATF_CXX_CFLAGS) +stack_cleaner_test_LDADD = $(LUTOK_LIBS) $(ATF_CXX_LIBS) + +tests_PROGRAMS += state_test +state_test_SOURCES = state_test.cpp test_utils.hpp +state_test_CXXFLAGS = $(LUTOK_CFLAGS) $(ATF_CXX_CFLAGS) +state_test_LDADD = $(LUTOK_LIBS) $(ATF_CXX_LIBS) + +if HAVE_KYUA +check-local: check-kyua +PHONY_TARGETS += check-kyua +check-kyua: + $(TESTS_ENVIRONMENT) kyua test \ + --kyuafile='$(top_srcdir)/Kyuafile' --build-root='$(top_builddir)' + +installcheck-local: installcheck-kyua +PHONY_TARGETS += installcheck-kyua +installcheck-kyua: + cd $(testsdir) && $(TESTS_ENVIRONMENT) kyua test +endif +else +DIST_HOOKS += dist-no-atf +PHONY_TARGETS += dist-no-atf +dist-no-atf: + @echo "Sorry; cannot build a distfile without atf" + @false +endif + +if WITH_DOXYGEN +# This should probably be html-local, but it seems better to generate the +# documentation in all cases to get warnings about missing documentation every +# time the code is modified. (And, after all, the documentation is not +# installed so generating it unconditionally is not a big problem.) +all-local: api-docs/api-docs.tag + +api-docs/api-docs.tag: $(builddir)/Doxyfile $(SOURCES) + $(AM_V_GEN)rm -rf api-docs; \ + mkdir -p api-docs; \ + ${DOXYGEN} $(builddir)/Doxyfile 2>&1 | tee api-docs/warnings; \ + rm -f api-docs/html/installdox +api-docs/html: api-docs/api-docs.tag + +CLEAN_TARGETS += clean-api-docs +clean-api-docs: + rm -rf api-docs + +EXTRA_DIST += api-docs/html +else +DIST_HOOKS += dist-no-doxygen +PHONY_TARGETS += dist-no-doxygen +dist-no-doxygen: + @echo "Sorry; cannot build a distfile without Doxygen" + @false +endif + +install-data-local: install-api-docs +install-api-docs: install-docDATA + @echo "Installing HTML documentation into $(DESTDIR)$(htmldir)" + @if [ -d api-docs/html ]; then \ + test -z "$(htmldir)" || $(MKDIR_P) "$(DESTDIR)$(htmldir)"; \ + ( cd api-docs/html && tar cf - . ) | \ + ( cd "$(DESTDIR)$(htmldir)" && tar xf - ); \ + elif [ -d "$(srcdir)/api-docs/html" ]; then \ + test -z "$(htmldir)" || $(MKDIR_P) "$(DESTDIR)$(htmldir)"; \ + ( cd "$(srcdir)/api-docs/html" && tar cf - . ) | \ + ( cd "$(DESTDIR)$(htmldir)" && tar xf - ); \ + else \ + echo "Doxygen not installed and prebuilt documents not found"; \ + fi + +uninstall-local: uninstall-api-docs +uninstall-api-docs: + find "$(DESTDIR)$(htmldir)" -type d -exec chmod 755 {} \; + rm -rf "$(DESTDIR)$(htmldir)" + +clean-local: $(CLEAN_TARGETS) + +PHONY_TARGETS += clean-all +clean-all: + GIT="$(GIT)" $(SH) $(srcdir)/admin/clean-all.sh + +dist-hook: $(DIST_HOOKS) + +.PHONY: ${PHONY_TARGETS} diff --git a/NEWS b/NEWS new file mode 100644 index 000000000000..3cb25cd53458 --- /dev/null +++ b/NEWS @@ -0,0 +1,68 @@ +Changes in version 0.4 +====================== + +Released on 2013/12/07. + +* Cope with the lack of AM_PROG_AR in configure.ac, which first + appeared in Automake 1.11.2. Fixes a problem in Ubuntu 10.04 + LTS, which appears stuck in 1.11.1. + +* Stopped shipping an Atffile. The only supported way to run the tests + is via Kyua. + +Interface changes: + +* Issue 5: New methods added to the state class: open_all. + +* Removed default parameter values from all state methods and all + standalone operations. It is often unclear what the default value is + given that it depends on the specific Lua operation. Being explicit + on the caller side is clearer. + +* Modified operations do_file and do_string to support passing a number + of arguments to the loaded chunks and an error handler to the backing + pcall call. + + +Changes in version 0.3 +====================== + +Released on 2013/06/14. + +* Issue 1: Added support for Lua 5.2 while maintaining support for Lua + 5.1. Applications using Lutok can be modified to use the new + interface in this new version and thus support both Lua releases. + However, because of incompatible changes to the Lua API, this release + of Lutok is incompatible with previous releases as well. + +* Issue 3: Tweaked configure to look for Lua using the pkg-config names + lua-5.2 and lua-5.1. These are the names used by FreeBSD. + +Interface changes: + +* New global constants: registry_index. + +* New methods added to the state class: get_global_table. + +* Removed global constants: globals_index. + + +Changes in version 0.2 +====================== + +Released on 2012/05/30. + +* New global constants: globals_index. + +* New methods added to the state class: get_metafield, get_metatable, + insert, push_value, raw_get and raw_set. + +* Acknowledged that Lua 5.2 is currently not supported. + + +Changes in version 0.1 +====================== + +Released on 2012/01/29. + +* This is the first public release of the Lutok package. diff --git a/README b/README new file mode 100644 index 000000000000..f39d33d77bb4 --- /dev/null +++ b/README @@ -0,0 +1,27 @@ +Lutok is a lightweight C++ API library for Lua. + +Lutok provides thin C++ wrappers around the Lua C API to ease the +interaction between C++ and Lua. These wrappers make intensive use of +RAII to prevent resource leakage, expose C++-friendly data types, report +errors by means of exceptions and ensure that the Lua stack is always +left untouched in the face of errors. The library also provides a small +subset of miscellaneous utility functions built on top of the wrappers. + +Lutok focuses on providing a clean and safe C++ interface; the drawback +is that it is not suitable for performance-critical environments. In +order to implement error-safe C++ wrappers on top of a Lua C binary +library, Lutok adds several layers or abstraction and error checking +that go against the original spirit of the Lua C API and thus degrade +performance. + +For further information on the contents of this distribution file, +please refer to the following other documents: + +* AUTHORS: List of authors and contributors to this project. +* COPYING: License information. +* INSTALL: Compilation and installation instructions. +* NEWS: List of major changes between formal releases. + +For general project information, please visit: + + https://github.com/jmmv/lutok/ diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 000000000000..64f348c68ebf --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,8 @@ +ar-lib +compile +config.guess +config.sub +depcomp +install-sh +ltmain.sh +missing diff --git a/admin/clean-all.sh b/admin/clean-all.sh new file mode 100755 index 000000000000..27244437ac82 --- /dev/null +++ b/admin/clean-all.sh @@ -0,0 +1,90 @@ +#! /bin/sh +# Copyright 2010 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Prog_Name=${0##*/} + +if [ ! -f ./state.hpp ]; then + echo "${Prog_Name}: must be run from the source top directory" 1>&2 + exit 1 +fi + +if [ ! -f configure ]; then + echo "${Prog_Name}: configure not found; nothing to clean?" 1>&2 + exit 1 +fi + +[ -f Makefile ] || ./configure +make distclean + +# Top-level directory. +rm -f Makefile.in +rm -f aclocal.m4 +rm -rf autom4te.cache +rm -f config.h.in +rm -f configure +rm -f mkinstalldirs +rm -f lutok-*.tar.gz + +# admin directory. +rm -f admin/compile +rm -f admin/config.guess +rm -f admin/config.sub +rm -f admin/depcomp +rm -f admin/install-sh +rm -f admin/ltmain.sh +rm -f admin/mdate-sh +rm -f admin/missing +rm -f admin/texinfo.tex + +# bootstrap directory. +rm -f bootstrap/package.m4 +rm -f bootstrap/testsuite + +# doc directory. +rm -f doc/*.info +rm -f doc/stamp-vti +rm -f doc/version.texi + +# m4 directory. +rm -f m4/libtool.m4 +rm -f m4/lt*.m4 + +# Files and directories spread all around the tree. +find . -name '#*' | xargs rm -rf +find . -name '*~' | xargs rm -rf +find . -name .deps | xargs rm -rf +find . -name .gdb_history | xargs rm -rf +find . -name .libs | xargs rm -rf +find . -name .tmp | xargs rm -rf + +# Show remaining files. +if [ -n "${GIT}" ]; then + echo ">>> untracked and ignored files" + "${GIT}" status --porcelain --ignored | grep -E '^(\?\?|!!)' || true +fi diff --git a/admin/travis-build.sh b/admin/travis-build.sh new file mode 100755 index 000000000000..1da582fb3308 --- /dev/null +++ b/admin/travis-build.sh @@ -0,0 +1,51 @@ +#! /bin/sh +# Copyright 2014 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set -e -x + +if [ -d /usr/local/share/aclocal ]; then + autoreconf -isv -I/usr/local/share/aclocal +else + autoreconf -isv +fi +./configure + +archflags= +[ "${ARCH?}" != i386 ] || archflags=-m32 + +f= +f="${f} CPPFLAGS='-I/usr/local/include'" +f="${f} CXX='${CXX} ${archflags}'" +f="${f} LDFLAGS='-L/usr/local/lib -Wl,-R/usr/local/lib'" +f="${f} PKG_CONFIG_PATH='/usr/local/lib/pkgconfig'" +if [ "${AS_ROOT:-no}" = yes ]; then + sudo -H PATH="${PATH}" make distcheck DISTCHECK_CONFIGURE_FLAGS="${f}" +else + make distcheck DISTCHECK_CONFIGURE_FLAGS="${f}" +fi diff --git a/admin/travis-install-deps.sh b/admin/travis-install-deps.sh new file mode 100755 index 000000000000..a6d4cc53f8ce --- /dev/null +++ b/admin/travis-install-deps.sh @@ -0,0 +1,109 @@ +#! /bin/sh +# Copyright 2014 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set -e -x + +install_deps() { + sudo apt-get update -qq + + local pkgsuffix= + local packages= + if [ "${ARCH?}" = i386 ]; then + pkgsuffix=:i386 + packages="${packages} gcc-multilib" + packages="${packages} g++-multilib" + fi + packages="${packages} doxygen" + packages="${packages} gdb" + packages="${packages} liblua5.2-0${pkgsuffix}" + packages="${packages} liblua5.2-dev${pkgsuffix}" + packages="${packages} libsqlite3-0${pkgsuffix}" + packages="${packages} libsqlite3-dev${pkgsuffix}" + packages="${packages} pkg-config${pkgsuffix}" + packages="${packages} sqlite3" + sudo apt-get install -y ${packages} +} + +install_from_github() { + local project="${1}"; shift + local name="${1}"; shift + local release="${1}"; shift + + local distname="${name}-${release}" + + local baseurl="https://github.com/jmmv/${project}" + wget --no-check-certificate \ + "${baseurl}/releases/download/${distname}/${distname}.tar.gz" + tar -xzvf "${distname}.tar.gz" + + local archflags= + [ "${ARCH?}" != i386 ] || archflags=-m32 + + cd "${distname}" + ./configure \ + --disable-developer \ + --without-atf \ + --without-doxygen \ + CFLAGS="${archflags}" \ + CPPFLAGS="-I/usr/local/include" \ + CXXFLAGS="${archflags}" \ + LDFLAGS="-L/usr/local/lib -Wl,-R/usr/local/lib" \ + PKG_CONFIG_PATH="/usr/local/lib/pkgconfig" + make + sudo make install + cd - + + rm -rf "${distname}" "${distname}.tar.gz" +} + +install_from_bintray() { + case "${ARCH?}" in + amd64) + name="20160204-usr-local-kyua-ubuntu-12-04-amd64-${CC?}.tar.gz" + ;; + i386) + name="20160714-usr-local-kyua-ubuntu-12-04-i386-${CC?}.tar.gz" + ;; + *) + echo "ERROR: Unknown ARCH value ${ARCH}" 1>&2 + exit 1 + ;; + esac + wget "http://dl.bintray.com/jmmv/kyua/${name}" || return 1 + sudo tar -xzvp -C / -f "${name}" + rm -f "${name}" +} + +install_deps +if ! install_from_bintray; then + install_from_github atf atf 0.20 + install_from_github lutok lutok 0.4 + install_from_github kyua kyua-testers 0.2 + install_from_github kyua kyua-cli 0.8 +fi diff --git a/c_gate.cpp b/c_gate.cpp new file mode 100644 index 000000000000..dde366e2dee2 --- /dev/null +++ b/c_gate.cpp @@ -0,0 +1,76 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "c_gate.hpp" +#include "state.ipp" + + +/// Creates a new gateway to an existing C++ Lua state. +/// +/// \param state_ The state to connect to. This object must remain alive while +/// the newly-constructed state_c_gate is alive. +lutok::state_c_gate::state_c_gate(state& state_) : + _state(state_) +{ +} + + +/// Destructor. +/// +/// Destroying this object has no implications on the life cycle of the Lua +/// state. Only the corresponding state object controls when the Lua state is +/// closed. +lutok::state_c_gate::~state_c_gate(void) +{ +} + + +/// Creates a C++ state for a C Lua state. +/// +/// \warning The created state object does NOT own the C state. You must take +/// care to properly destroy the input lua_State when you are done with it to +/// not leak resources. +/// +/// \param raw_state The raw state to wrap temporarily. +/// +/// \return The wrapped state without strong ownership on the input state. +lutok::state +lutok::state_c_gate::connect(lua_State* raw_state) +{ + return state(static_cast< void* >(raw_state)); +} + + +/// Returns the C native Lua state. +/// +/// \return A native lua_State object holding the Lua C API state. +lua_State* +lutok::state_c_gate::c_state(void) +{ + return static_cast< lua_State* >(_state.raw_state()); +} diff --git a/c_gate.hpp b/c_gate.hpp new file mode 100644 index 000000000000..36bc9c228f84 --- /dev/null +++ b/c_gate.hpp @@ -0,0 +1,71 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file c_gate.hpp +/// Provides direct access to the C state of the Lua wrappers. + +#if !defined(LUTOK_C_GATE_HPP) +#define LUTOK_C_GATE_HPP + +#include + +namespace lutok { + + +class state; + + +/// Gateway to the raw C state of Lua. +/// +/// This class provides a mechanism to muck with the internals of the state +/// wrapper class. Client code may wish to do so if Lutok is missing some +/// features of the performance of Lutok in a particular situation is not +/// reasonable. +/// +/// \warning The use of this class is discouraged. By using this class, you are +/// entering the world of unsafety. Anything you do through the objects exposed +/// through this class will not be controlled by RAII patterns not validated in +/// any other way, so you can end up corrupting the Lua state and later get +/// crashes on otherwise perfectly-valid C++ code. +class state_c_gate { + /// The C++ state that this class wraps. + state& _state; + +public: + state_c_gate(state&); + ~state_c_gate(void); + + static state connect(lua_State*); + + lua_State* c_state(void); +}; + + +} // namespace lutok + +#endif // !defined(LUTOK_C_GATE_HPP) diff --git a/c_gate_test.cpp b/c_gate_test.cpp new file mode 100644 index 000000000000..33e3d10da457 --- /dev/null +++ b/c_gate_test.cpp @@ -0,0 +1,74 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "c_gate.hpp" + +#include +#include + +#include "state.ipp" +#include "test_utils.hpp" + + +ATF_TEST_CASE_WITHOUT_HEAD(connect); +ATF_TEST_CASE_BODY(connect) +{ + lua_State* raw_state = luaL_newstate(); + ATF_REQUIRE(raw_state != NULL); + + { + lutok::state state = lutok::state_c_gate::connect(raw_state); + lua_pushinteger(raw(state), 123); + } + // If the wrapper object had closed the Lua state, we could very well crash + // here. + ATF_REQUIRE_EQ(123, lua_tointeger(raw_state, -1)); + + lua_close(raw_state); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(c_state); +ATF_TEST_CASE_BODY(c_state) +{ + lutok::state state; + state.push_integer(5); + { + lutok::state_c_gate gate(state); + lua_State* raw_state = gate.c_state(); + ATF_REQUIRE_EQ(5, lua_tointeger(raw_state, -1)); + } + state.pop(1); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, c_state); + ATF_ADD_TEST_CASE(tcs, connect); +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000000..66c7c5d6b613 --- /dev/null +++ b/configure.ac @@ -0,0 +1,70 @@ +dnl Copyright 2011 Google Inc. +dnl All rights reserved. +dnl +dnl Redistribution and use in source and binary forms, with or without +dnl modification, are permitted provided that the following conditions are +dnl met: +dnl +dnl * Redistributions of source code must retain the above copyright +dnl notice, this list of conditions and the following disclaimer. +dnl * Redistributions in binary form must reproduce the above copyright +dnl notice, this list of conditions and the following disclaimer in the +dnl documentation and/or other materials provided with the distribution. +dnl * Neither the name of Google Inc. nor the names of its contributors +dnl may be used to endorse or promote products derived from this software +dnl without specific prior written permission. +dnl +dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +AC_INIT([Lutok], [0.4], + [lutok-discuss@googlegroups.com], [lutok], + [https://github.com/jmmv/lutok/]) +AC_PREREQ([2.65]) + + +AC_COPYRIGHT([Copyright 2011 Google Inc.]) +AC_CONFIG_AUX_DIR([admin]) +AC_CONFIG_FILES([Doxyfile Makefile]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_SRCDIR([state.hpp]) + + +AM_INIT_AUTOMAKE([1.9 check-news foreign subdir-objects -Wall]) +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +LT_INIT + + +AC_LANG([C++]) +AC_PROG_CXX +KYUA_REQUIRE_CXX +KYUA_DEVELOPER_MODE([C++]) + + +ATF_CHECK_CXX([>= 0.15]) +ATF_CHECK_SH([>= 0.15]) +ATF_ARG_WITH +KYUA_DOXYGEN +KYUA_LUA + + +AC_PATH_PROG([KYUA], [kyua]) +AM_CONDITIONAL([HAVE_KYUA], [test -n "${KYUA}"]) +AC_PATH_PROG([GIT], [git]) + + +AC_SUBST(pkgconfigdir, \${libdir}/pkgconfig) +AC_SUBST(testsdir, \${exec_prefix}/tests/lutok) + + +AC_OUTPUT diff --git a/debug.cpp b/debug.cpp new file mode 100644 index 000000000000..e0a0b5eb2113 --- /dev/null +++ b/debug.cpp @@ -0,0 +1,192 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +#include +#include +#include +#include + + +/// Internal implementation for lutok::debug. +struct lutok::debug::impl { + /// The Lua internal debug state. + lua_Debug lua_debug; +}; + + +/// Constructor for an empty debug structure. +lutok::debug::debug(void) : + _pimpl(new impl()) +{ +} + + +/// Destructor. +lutok::debug::~debug(void) +{ +} + + +/// Wrapper around lua_getinfo. +/// +/// \param s The Lua state. +/// \param what_ The second parameter to lua_getinfo. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +void +lutok::debug::get_info(state& s, const std::string& what_) +{ + lua_State* raw_state = state_c_gate(s).c_state(); + + if (lua_getinfo(raw_state, what_.c_str(), &_pimpl->lua_debug) == 0) + throw lutok::api_error::from_stack(s, "lua_getinfo"); +} + + +/// Wrapper around lua_getstack. +/// +/// \param s The Lua state. +/// \param level The second parameter to lua_getstack. +void +lutok::debug::get_stack(state& s, const int level) +{ + lua_State* raw_state = state_c_gate(s).c_state(); + + lua_getstack(raw_state, level, &_pimpl->lua_debug); +} + + +/// Accessor for the 'event' field of lua_Debug. +/// +/// \return Returns the 'event' field of the internal lua_Debug structure. +int +lutok::debug::event(void) const +{ + return _pimpl->lua_debug.event; +} + + +/// Accessor for the 'name' field of lua_Debug. +/// +/// \return Returns the 'name' field of the internal lua_Debug structure. +std::string +lutok::debug::name(void) const +{ + assert(_pimpl->lua_debug.name != NULL); + return _pimpl->lua_debug.name; +} + + +/// Accessor for the 'namewhat' field of lua_Debug. +/// +/// \return Returns the 'namewhat' field of the internal lua_Debug structure. +std::string +lutok::debug::name_what(void) const +{ + assert(_pimpl->lua_debug.namewhat != NULL); + return _pimpl->lua_debug.namewhat; +} + + +/// Accessor for the 'what' field of lua_Debug. +/// +/// \return Returns the 'what' field of the internal lua_Debug structure. +std::string +lutok::debug::what(void) const +{ + assert(_pimpl->lua_debug.what != NULL); + return _pimpl->lua_debug.what; +} + + +/// Accessor for the 'source' field of lua_Debug. +/// +/// \return Returns the 'source' field of the internal lua_Debug structure. +std::string +lutok::debug::source(void) const +{ + assert(_pimpl->lua_debug.source != NULL); + return _pimpl->lua_debug.source; +} + + +/// Accessor for the 'currentline' field of lua_Debug. +/// +/// \return Returns the 'currentline' field of the internal lua_Debug structure. +int +lutok::debug::current_line(void) const +{ + return _pimpl->lua_debug.currentline; +} + + +/// Accessor for the 'nups' field of lua_Debug. +/// +/// \return Returns the 'nups' field of the internal lua_Debug structure. +int +lutok::debug::n_ups(void) const +{ + return _pimpl->lua_debug.nups; +} + + +/// Accessor for the 'linedefined' field of lua_Debug. +/// +/// \return Returns the 'linedefined' field of the internal lua_Debug structure. +int +lutok::debug::line_defined(void) const +{ + return _pimpl->lua_debug.linedefined; +} + + +/// Accessor for the 'lastlinedefined' field of lua_Debug. +/// +/// \return Returns the 'lastlinedefined' field of the internal lua_Debug +/// structure. +int +lutok::debug::last_line_defined(void) const +{ + return _pimpl->lua_debug.lastlinedefined; +} + + +/// Accessor for the 'short_src' field of lua_Debug. +/// +/// \return Returns the 'short_src' field of the internal lua_Debug structure. +std::string +lutok::debug::short_src(void) const +{ + assert(_pimpl->lua_debug.short_src != NULL); + return _pimpl->lua_debug.short_src; +} diff --git a/debug.hpp b/debug.hpp new file mode 100644 index 000000000000..6fc074d95d41 --- /dev/null +++ b/debug.hpp @@ -0,0 +1,90 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file debug.hpp +/// Provides the debug wrapper class for the Lua C debug state. + +#if !defined(LUTOK_DEBUG_HPP) +#define LUTOK_DEBUG_HPP + +#include +#if defined(_LIBCPP_VERSION) || __cplusplus >= 201103L +#include +#else +#include +#endif + +namespace lutok { + + +class state; + + +/// A model for the Lua debug state. +/// +/// This extremely-simple class provides a mechanism to hide the internals of +/// the C native lua_Debug type, exposing its internal fields using friendlier +/// C++ types. +/// +/// This class also acts as a complement to the state class by exposing any +/// state-related functions as methods of this function. For example, while it +/// might seem that get_info() belongs in state, we expose it from here because +/// its result is really mutating a debug object, not the state object. +class debug { + struct impl; + + /// Pointer to the shared internal implementation. +#if defined(_LIBCPP_VERSION) || __cplusplus >= 201103L + std::shared_ptr< impl > _pimpl; +#else + std::tr1::shared_ptr< impl > _pimpl; +#endif + +public: + debug(void); + ~debug(void); + + void get_info(state&, const std::string&); + void get_stack(state&, const int); + + int event(void) const; + std::string name(void) const; + std::string name_what(void) const; + std::string what(void) const; + std::string source(void) const; + int current_line(void) const; + int n_ups(void) const; + int line_defined(void) const; + int last_line_defined(void) const; + std::string short_src(void) const; +}; + + +} // namespace lutok + +#endif // !defined(LUTOK_DEBUG_HPP) diff --git a/debug_test.cpp b/debug_test.cpp new file mode 100644 index 000000000000..a88b892d0fa3 --- /dev/null +++ b/debug_test.cpp @@ -0,0 +1,68 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "debug.hpp" + +#include +#include + +#include "state.ipp" +#include "test_utils.hpp" + + +ATF_TEST_CASE_WITHOUT_HEAD(get_info); +ATF_TEST_CASE_BODY(get_info) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "\n\nfunction hello() end\n" + "return hello") == 0); + lutok::debug debug; + debug.get_info(state, ">S"); + ATF_REQUIRE_EQ(3, debug.line_defined()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_stack); +ATF_TEST_CASE_BODY(get_stack) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "error('Hello')") == 1); + lutok::debug debug; + debug.get_stack(state, 0); + lua_pop(raw(state), 1); + // Not sure if we can actually validate anything here, other than we did not + // crash... (because get_stack only is supposed to update internal values of + // the debug structure). +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, get_info); + ATF_ADD_TEST_CASE(tcs, get_stack); +} diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 000000000000..834b20fc114e --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,67 @@ +# Copyright 2012 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +CXX ?= c++ +CPPFLAGS ?= +CXXFLAGS ?= -Wall -O2 +LDFLAGS ?= +LIBS ?= + +LUTOK_CPPFLAGS = $$(pkg-config --cflags-only-I lutok) +LUTOK_CXXFLAGS = $$(pkg-config --cflags-only-other lutok) +LUTOK_LDFLAGS = $$(pkg-config --libs-only-L lutok) \ + $$(pkg-config --libs-only-other lutok) +LUTOK_LIBS = $$(pkg-config --libs-only-l lutok) + +BUILD = $(CXX) \ + $(CPPFLAGS) $(LUTOK_CPPFLAGS) \ + $(CXXFLAGS) $(LUTOK_CXXFLAGS) \ + $(LDFLAGS) $(LUTOK_LDFLAGS) \ + -o $${target} $${source} \ + $(LIBS) $(LUTOK_LIBS) + +PROGRAMS = bindings hello interpreter raii + +.PHONY: all +all: $(PROGRAMS) + +bindings: bindings.cpp + @target=bindings source=bindings.cpp; echo $(BUILD); $(BUILD) + +hello: hello.cpp + @target=hello source=hello.cpp; echo $(BUILD); $(BUILD) + +interpreter: interpreter.cpp + @target=interpreter source=interpreter.cpp; echo $(BUILD); $(BUILD) + +raii: raii.cpp + @target=raii source=raii.cpp; echo $(BUILD); $(BUILD) + +.PHONY: clean +clean: + rm -f $(PROGRAMS) diff --git a/examples/bindings.cpp b/examples/bindings.cpp new file mode 100644 index 000000000000..dca235a8962d --- /dev/null +++ b/examples/bindings.cpp @@ -0,0 +1,133 @@ +// Copyright 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file examples/bindings.cpp +/// Showcases how to define Lua functions from C++ code. +/// +/// A major selling point of Lua is that it is very easy too hook native C and +/// C++ functions into the runtime environment so that Lua can call them. The +/// purpose of this example program is to show how this is done by using Lutok. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +/// Calculates the factorial of a given number. +/// +/// \param i The postivie number to calculate the factorial of. +/// +/// \return The factorial of i. +static int +factorial(const int i) +{ + assert(i >= 0); + + if (i == 0) + return 1; + else + return i * factorial(i - 1); +} + + +/// A custom factorial function for Lua. +/// +/// \pre stack(-1) contains the number to calculate the factorial of. +/// \post stack(-1) contains the result of the operation. +/// +/// \param state The Lua state from which to get the function arguments and into +/// which to push the results. +/// +/// \return The number of results pushed onto the stack, i.e. 1. +/// +/// \throw std::runtime_error If the input parameters are invalid. Note that +/// Lutok will convert this exception to lutok::error. +static int +lua_factorial(lutok::state& state) +{ + if (!state.is_number(-1)) + throw std::runtime_error("Argument to factorial must be an integer"); + const int i = state.to_integer(-1); + if (i < 0) + throw std::runtime_error("Argument to factorial must be positive"); + state.push_integer(factorial(i)); + return 1; +} + + +/// Program's entry point. +/// +/// \param argc Length of argv. Must be 2. +/// \param argv Command-line arguments to the program. The first argument to +/// the tool has to be a number. +/// +/// \return A system exit code. +int +main(int argc, char** argv) +{ + if (argc != 2) { + std::cerr << "Usage: bindings \n"; + return EXIT_FAILURE; + } + + // Create a new Lua session and load the print() function. + lutok::state state; + state.open_base(); + + // Construct a 'module' that contains an entry point to our native factorial + // function. A module is just a Lua table that contains a mapping of names + // to functions. Instead of creating a module by using our create_module() + // helper function, we could have used push_cxx_function on the state to + // define the function ourselves. + std::map< std::string, lutok::cxx_function > module; + module["factorial"] = lua_factorial; + lutok::create_module(state, "native", module); + + // Use a little Lua script to call our native factorial function providing + // it the first argument passed to the program. Note that this will error + // out in a controlled manner if the passed argument is not an integer. The + // important thing to notice is that the exception comes from our own C++ + // binding and that it has been converted to a lutok::error. + std::ostringstream script; + script << "print(native.factorial(" << argv[1] << "))"; + try { + lutok::do_string(state, script.str(), 0, 0, 0); + return EXIT_SUCCESS; + } catch (const lutok::error& e) { + std::cerr << "ERROR: " << e.what() << '\n'; + return EXIT_FAILURE; + } +} diff --git a/examples/hello.cpp b/examples/hello.cpp new file mode 100644 index 000000000000..40afdd5724b7 --- /dev/null +++ b/examples/hello.cpp @@ -0,0 +1,58 @@ +// Copyright 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file examples/hello.cpp +/// Minimal example using Lua code to print a traditional hello-world message. + +#include + +#include + + +/// Program's entry point. +/// +/// \return A system exit code. +int +main(void) +{ + // Initializes a new Lua state. Every Lua state is independent from each + // other. + lutok::state state; + + // Loads the standard library into the Lua state. Among many other + // functions, this gives us access to print(), which is used below. + state.open_base(); + + // Pushes the print() function into the Lua stack, then its only argument, + // and then proceeds to execute it within the Lua state. + state.get_global("print"); + state.push_string("Hello, world!"); + state.pcall(1, 0, 0); + + return EXIT_SUCCESS; +} diff --git a/examples/interpreter.cpp b/examples/interpreter.cpp new file mode 100644 index 000000000000..08fba796a45d --- /dev/null +++ b/examples/interpreter.cpp @@ -0,0 +1,83 @@ +// Copyright 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file examples/interpreter.cpp +/// Implementation of a basic command-line Lua interpreter. + +#include +#include +#include + +#include +#include +#include + + +/// Executes a Lua statement provided by the user with error checking. +/// +/// \param state The Lua state in which to process the statement. +/// \param line The textual statement provided by the user. +static void +run_statement(lutok::state& state, const std::string& line) +{ + try { + // This utility function allows us to feed a given piece of Lua code to + // the interpreter and process it. The piece of code can include + // multiple statements separated by a semicolon or by a newline + // character. + lutok::do_string(state, line, 0, 0, 0); + } catch (const lutok::error& error) { + std::cerr << "ERROR: " << error.what() << '\n'; + } +} + + +/// Program's entry point. +/// +/// \return A system exit code. +int +main(void) +{ + // Create a new session and load some standard libraries. + lutok::state state; + state.open_base(); + state.open_string(); + state.open_table(); + + for (;;) { + std::cout << "lua> "; + std::cout.flush(); + + std::string line; + if (!std::getline(std::cin, line).good()) + break; + run_statement(state, line); + } + + return EXIT_SUCCESS; +} diff --git a/examples/raii.cpp b/examples/raii.cpp new file mode 100644 index 000000000000..eae76538e992 --- /dev/null +++ b/examples/raii.cpp @@ -0,0 +1,126 @@ +// Copyright 2012 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file examples/raii.cpp +/// Demonstrates how RAII helps in keeping the Lua state consistent. +/// +/// One of the major complains that is raised against the Lua C API is that it +/// is very hard to ensure it remains consistent during the execution of the +/// program. In the case of native C code, there exist many tools that help the +/// developer catch memory leaks, access to uninitialized variables, etc. +/// However, when using the Lua C API, none of these tools can validate that, +/// for example, the Lua stack remains balanced across calls. +/// +/// Enter RAII. The RAII pattern, intensively applied by Lutok, helps the +/// developer in maintaining the Lua state consistent at all times in a +/// transparent manner. This example program attempts to illustrate this. + +#include +#include +#include +#include + +#include +#include +#include + + +/// Prints the string-typed field of a table. +/// +/// If the field contains a string, this function prints its value. If the +/// field contains any other type, this prints an error message. +/// +/// \pre The top of the Lua stack in 'state' references a table. +/// +/// \param state The Lua state. +/// \param field The name of the string-typed field. +static void +print_table_field(lutok::state& state, const std::string& field) +{ + assert(state.is_table(-1)); + + // Bring in some RAII magic: the stack_cleaner object captures the current + // height of the Lua stack at this point. Whenever the object goes out of + // scope, it will pop as many entries from the stack as necessary to restore + // the stack to its previous level. + // + // This ensures that, no matter how we exit the function, we do not leak + // objects in the stack. + lutok::stack_cleaner cleaner(state); + + // Stack contents: -1: table. + state.push_string(field); + // Stack contents: -2: table, -1: field name. + state.get_table(-2); + // Stack contents: -2: table, -1: field value. + + if (!state.is_string(-1)) { + std::cout << "The field " << field << " does not contain a string\n"; + // Stack contents: -2: table, -1: field value. + // + // This is different than when we started! We should pop our extra + // value from the stack at this point. However, it is extremely common + // for software to have bugs (in this case, leaks) in error paths, + // mostly because such code paths are rarely exercised. + // + // By using the stack_cleaner object, we can be confident that the Lua + // stack will be cleared for us at this point, no matter what happened + // earlier on the stack nor how we exit the function. + return; + } + + std::cout << "String in field " << field << ": " << state.to_string(-1) + << '\n'; + // A well-behaved program explicitly pops anything extra from the stack to + // return it to its original state. Mostly for clarity. + state.pop(1); + + // Stack contents: -1: table. Same as when we started. +} + + +/// Program's entry point. +/// +/// \return A system exit code. +int +main(void) +{ + lutok::state state; + state.open_base(); + + lutok::do_string(state, "example = {foo='hello', bar=123, baz='bye'}", + 0, 0, 0); + + state.get_global("example"); + print_table_field(state, "foo"); + print_table_field(state, "bar"); + print_table_field(state, "baz"); + state.pop(1); + + return EXIT_SUCCESS; +} diff --git a/examples_test.sh b/examples_test.sh new file mode 100755 index 000000000000..936fa865d0d4 --- /dev/null +++ b/examples_test.sh @@ -0,0 +1,115 @@ +#! __ATF_SH__ +# Copyright 2012 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Cxx="__CXX__" +ExamplesDir="__EXAMPLESDIR__" +LibDir="__LIBDIR__" + + +make_example() { + cp "${ExamplesDir}/Makefile" "${ExamplesDir}/${1}.cpp" . + make CXX="${Cxx}" "${1}" + + # Ensure that the binary we just built can find liblutok. This is + # needed because the lutok.pc file (which the Makefile used above + # queries) does not provide rpaths to the installed library and + # therefore the binary may not be able to locate it. Hardcoding the + # rpath flags into lutok.pc is non-trivial because we simply don't + # have any knowledge about what the correct flag to set an rpath is. + # + # Additionally, setting rpaths is not always the right thing to do. + # For example, pkgsrc will automatically change lutok.pc to add the + # missing rpath, in which case this is unnecessary. But in the case + # of Fedora, adding rpaths goes against the packaging guidelines. + if [ -n "${LD_LIBRARY_PATH}" ]; then + export LD_LIBRARY_PATH="${LibDir}:${LD_LIBRARY_PATH}" + else + export LD_LIBRARY_PATH="${LibDir}" + fi +} + + +example_test_case() { + local name="${1}"; shift + + atf_test_case "${name}" + eval "${name}_head() { \ + atf_set 'require.files' '${ExamplesDir}/${name}.cpp'; \ + atf_set 'require.progs' 'make pkg-config'; \ + }" + eval "${name}_body() { \ + make_example '${name}'; \ + ${name}_validate; \ + }" +} + + +example_test_case bindings +bindings_validate() { + atf_check -s exit:0 -o inline:'120\n' ./bindings 5 + atf_check -s exit:1 -e match:'Argument.*must be an integer' ./bindings foo + atf_check -s exit:1 -e match:'Argument.*must be positive' ./bindings -5 +} + + +example_test_case hello +hello_validate() { + atf_check -s exit:0 -o inline:'Hello, world!\n' ./hello +} + + +example_test_case interpreter +interpreter_validate() { + cat >script.lua <expout < + +#include + +#include "c_gate.hpp" +#include "exceptions.hpp" +#include "state.ipp" + + +/// Constructs a new error with a plain-text message. +/// +/// \param message The plain-text error message. +lutok::error::error(const std::string& message) : + std::runtime_error(message) +{ +} + + +/// Destructor for the error. +lutok::error::~error(void) throw() +{ +} + + +/// Constructs a new error. +/// +/// \param api_function_ The name of the API function that caused the error. +/// \param message The plain-text error message provided by Lua. +lutok::api_error::api_error(const std::string& api_function_, + const std::string& message) : + error(message), + _api_function(api_function_) +{ +} + + +/// Destructor for the error. +lutok::api_error::~api_error(void) throw() +{ +} + + +/// Constructs a new api_error with the message on the top of the Lua stack. +/// +/// \pre There is an error message on the top of the stack. +/// \post The error message is popped from the stack. +/// +/// \param state_ The Lua state. +/// \param api_function_ The name of the Lua API function that caused the error. +/// +/// \return A new api_error with the popped message. +lutok::api_error +lutok::api_error::from_stack(state& state_, const std::string& api_function_) +{ + lua_State* raw_state = lutok::state_c_gate(state_).c_state(); + + assert(lua_isstring(raw_state, -1)); + const std::string message = lua_tostring(raw_state, -1); + lua_pop(raw_state, 1); + return lutok::api_error(api_function_, message); +} + + +/// Gets the name of the Lua API function that caused this error. +/// +/// \return The name of the function. +const std::string& +lutok::api_error::api_function(void) const +{ + return _api_function; +} + + +/// Constructs a new error. +/// +/// \param filename_ The file that count not be found. +lutok::file_not_found_error::file_not_found_error( + const std::string& filename_) : + error("File '" + filename_ + "' not found"), + _filename(filename_) +{ +} + + +/// Destructor for the error. +lutok::file_not_found_error::~file_not_found_error(void) throw() +{ +} + + +/// Gets the name of the file that could not be found. +/// +/// \return The name of the file. +const std::string& +lutok::file_not_found_error::filename(void) const +{ + return _filename; +} diff --git a/exceptions.hpp b/exceptions.hpp new file mode 100644 index 000000000000..93a794873fad --- /dev/null +++ b/exceptions.hpp @@ -0,0 +1,83 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file exceptions.hpp +/// Exception types raised by lutok. + +#if !defined(LUTOK_EXCEPTIONS_HPP) +#define LUTOK_EXCEPTIONS_HPP + +#include +#include + +namespace lutok { + + +class state; + + +/// Base exception for lua errors. +class error : public std::runtime_error { +public: + explicit error(const std::string&); + virtual ~error(void) throw(); +}; + + +/// Exception for errors raised by the Lua API library. +class api_error : public error { + /// Name of the Lua C API function that caused the error. + std::string _api_function; + +public: + explicit api_error(const std::string&, const std::string&); + virtual ~api_error(void) throw(); + + static api_error from_stack(state&, const std::string&); + + const std::string& api_function(void) const; +}; + + +/// File not found error. +class file_not_found_error : public error { + /// Name of the not-found file. + std::string _filename; + +public: + explicit file_not_found_error(const std::string&); + virtual ~file_not_found_error(void) throw(); + + const std::string& filename(void) const; +}; + + +} // namespace lutok + + +#endif // !defined(LUTOK_EXCEPTIONS_HPP) diff --git a/exceptions_test.cpp b/exceptions_test.cpp new file mode 100644 index 000000000000..81f2c33d2b7b --- /dev/null +++ b/exceptions_test.cpp @@ -0,0 +1,88 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "exceptions.hpp" + +#include + +#include + +#include "state.ipp" + + +ATF_TEST_CASE_WITHOUT_HEAD(error); +ATF_TEST_CASE_BODY(error) +{ + const lutok::error e("Some text"); + ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(api_error__explicit); +ATF_TEST_CASE_BODY(api_error__explicit) +{ + const lutok::api_error e("some_function", "Some text"); + ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0); + ATF_REQUIRE_EQ("some_function", e.api_function()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(api_error__from_stack); +ATF_TEST_CASE_BODY(api_error__from_stack) +{ + lutok::state state; + state.push_integer(123); + state.push_string("The error message"); + const lutok::api_error e = lutok::api_error::from_stack(state, + "the_function"); + ATF_REQUIRE_EQ(1, state.get_top()); + ATF_REQUIRE_EQ(123, state.to_integer(-1)); + state.pop(1); + ATF_REQUIRE(std::strcmp("The error message", e.what()) == 0); + ATF_REQUIRE_EQ("the_function", e.api_function()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(file_not_found_error); +ATF_TEST_CASE_BODY(file_not_found_error) +{ + const lutok::file_not_found_error e("missing-file"); + ATF_REQUIRE(std::strcmp("File 'missing-file' not found", e.what()) == 0); + ATF_REQUIRE_EQ("missing-file", e.filename()); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, error); + + ATF_ADD_TEST_CASE(tcs, api_error__explicit); + ATF_ADD_TEST_CASE(tcs, api_error__from_stack); + + ATF_ADD_TEST_CASE(tcs, file_not_found_error); +} diff --git a/include/lutok/README b/include/lutok/README new file mode 100644 index 000000000000..1177b530f9ee --- /dev/null +++ b/include/lutok/README @@ -0,0 +1,4 @@ +This directory contains forward includes for the public header files of +Lutok. These files are only necessary during the build of Lutok itself +so that the compiler can locate the include files in a path that mimics +the final installation location. diff --git a/include/lutok/c_gate.hpp b/include/lutok/c_gate.hpp new file mode 100644 index 000000000000..4fabe16a0ed8 --- /dev/null +++ b/include/lutok/c_gate.hpp @@ -0,0 +1 @@ +#include "../../c_gate.hpp" diff --git a/include/lutok/debug.hpp b/include/lutok/debug.hpp new file mode 100644 index 000000000000..b942bbd01ccd --- /dev/null +++ b/include/lutok/debug.hpp @@ -0,0 +1 @@ +#include "../../debug.hpp" diff --git a/include/lutok/exceptions.hpp b/include/lutok/exceptions.hpp new file mode 100644 index 000000000000..97f49a15fd9f --- /dev/null +++ b/include/lutok/exceptions.hpp @@ -0,0 +1 @@ +#include "../../exceptions.hpp" diff --git a/include/lutok/operations.hpp b/include/lutok/operations.hpp new file mode 100644 index 000000000000..87d105376d1d --- /dev/null +++ b/include/lutok/operations.hpp @@ -0,0 +1 @@ +#include "../../operations.hpp" diff --git a/include/lutok/stack_cleaner.hpp b/include/lutok/stack_cleaner.hpp new file mode 100644 index 000000000000..99edfb8dfd01 --- /dev/null +++ b/include/lutok/stack_cleaner.hpp @@ -0,0 +1 @@ +#include "../../stack_cleaner.hpp" diff --git a/include/lutok/state.hpp b/include/lutok/state.hpp new file mode 100644 index 000000000000..48ac65ceb64f --- /dev/null +++ b/include/lutok/state.hpp @@ -0,0 +1 @@ +#include "../../state.hpp" diff --git a/include/lutok/state.ipp b/include/lutok/state.ipp new file mode 100644 index 000000000000..531d9c19dacd --- /dev/null +++ b/include/lutok/state.ipp @@ -0,0 +1 @@ +#include "../../state.ipp" diff --git a/lutok.pc.in b/lutok.pc.in new file mode 100644 index 000000000000..2886dfc3eeb8 --- /dev/null +++ b/lutok.pc.in @@ -0,0 +1,8 @@ +includedir=__INCLUDEDIR__ +libdir=__LIBDIR__ + +Name: lutok +Description: Lightweight C++ API for Lua +Version: __VERSION__ +Cflags: __LUA_CFLAGS__ -I${includedir} +Libs: __LUA_LIBS__ -L${libdir} -llutok diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 000000000000..38066ddf7cad --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +libtool.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 +lt~obsolete.m4 diff --git a/m4/compiler-features.m4 b/m4/compiler-features.m4 new file mode 100644 index 000000000000..55ff4f42b261 --- /dev/null +++ b/m4/compiler-features.m4 @@ -0,0 +1,100 @@ +dnl Copyright 2010 Google Inc. +dnl All rights reserved. +dnl +dnl Redistribution and use in source and binary forms, with or without +dnl modification, are permitted provided that the following conditions are +dnl met: +dnl +dnl * Redistributions of source code must retain the above copyright +dnl notice, this list of conditions and the following disclaimer. +dnl * Redistributions in binary form must reproduce the above copyright +dnl notice, this list of conditions and the following disclaimer in the +dnl documentation and/or other materials provided with the distribution. +dnl * Neither the name of Google Inc. nor the names of its contributors +dnl may be used to endorse or promote products derived from this software +dnl without specific prior written permission. +dnl +dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +dnl +dnl KYUA_REQUIRE_CXX +dnl +dnl Ensures the C++ compiler detected by AC_PROG_CXX is present and works. +dnl The compiler check should be performed here, but it seems like Autoconf +dnl does not allow it. +dnl +AC_DEFUN([KYUA_REQUIRE_CXX], [ + AC_CACHE_CHECK([whether the C++ compiler works], + [atf_cv_prog_cxx_works], + [AC_LANG_PUSH([C++]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([], [])], + [atf_cv_prog_cxx_works=yes], + [atf_cv_prog_cxx_works=no]) + AC_LANG_POP([C++])]) + if test "${atf_cv_prog_cxx_works}" = no; then + AC_MSG_ERROR([C++ compiler cannot create executables]) + fi +]) + +dnl +dnl KYUA_ATTRIBUTE_NORETURN +dnl +dnl Checks if the current compiler has a way to mark functions that do not +dnl return and defines ATTRIBUTE_NORETURN to the appropriate string. +dnl +AC_DEFUN([KYUA_ATTRIBUTE_NORETURN], [ + dnl This check is overly simple and should be fixed. For example, + dnl Sun's cc does support the noreturn attribute but CC (the C++ + dnl compiler) does not. And in that case, CC just raises a warning + dnl during compilation, not an error. + AC_MSG_CHECKING(whether __attribute__((noreturn)) is supported) + AC_RUN_IFELSE([AC_LANG_PROGRAM([], [ +#if ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + return 0; +#else + return 1; +#endif + ])], + [AC_MSG_RESULT(yes) + value="__attribute__((noreturn))"], + [AC_MSG_RESULT(no) + value=""] + ) + AC_SUBST([ATTRIBUTE_NORETURN], [${value}]) +]) + + +dnl +dnl KYUA_ATTRIBUTE_UNUSED +dnl +dnl Checks if the current compiler has a way to mark parameters as unused +dnl so that the -Wunused-parameter warning can be avoided. +dnl +AC_DEFUN([KYUA_ATTRIBUTE_UNUSED], [ + AC_MSG_CHECKING(whether __attribute__((__unused__)) is supported) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([ +static void +function(int a __attribute__((__unused__))) +{ +}], [ + function(3); + return 0; +])], + [AC_MSG_RESULT(yes) + value="__attribute__((__unused__))"], + [AC_MSG_RESULT(no) + value=""] + ) + AC_SUBST([ATTRIBUTE_UNUSED], [${value}]) +]) diff --git a/m4/compiler-flags.m4 b/m4/compiler-flags.m4 new file mode 100644 index 000000000000..480e5c740a2a --- /dev/null +++ b/m4/compiler-flags.m4 @@ -0,0 +1,159 @@ +dnl Copyright 2010 Google Inc. +dnl All rights reserved. +dnl +dnl Redistribution and use in source and binary forms, with or without +dnl modification, are permitted provided that the following conditions are +dnl met: +dnl +dnl * Redistributions of source code must retain the above copyright +dnl notice, this list of conditions and the following disclaimer. +dnl * Redistributions in binary form must reproduce the above copyright +dnl notice, this list of conditions and the following disclaimer in the +dnl documentation and/or other materials provided with the distribution. +dnl * Neither the name of Google Inc. nor the names of its contributors +dnl may be used to endorse or promote products derived from this software +dnl without specific prior written permission. +dnl +dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +dnl \file compiler-flags.m4 +dnl +dnl Macros to check for the existence of compiler flags. The macros in this +dnl file support both C and C++. +dnl +dnl Be aware that, in order to detect a flag accurately, we may need to enable +dnl strict warning checking in the compiler (i.e. enable -Werror). Some +dnl compilers, e.g. Clang, report unknown -W flags as warnings unless -Werror is +dnl selected. This fact would confuse the flag checks below because we would +dnl conclude that a flag is valid while in reality it is not. To resolve this, +dnl the macros below will pass -Werror to the compiler along with any other flag +dnl being checked. + + +dnl Checks for a compiler flag and sets a result variable. +dnl +dnl This is an auxiliary macro for the implementation of _KYUA_FLAG. +dnl +dnl \param 1 The shell variable containing the compiler name. Used for +dnl reporting purposes only. C or CXX. +dnl \param 2 The shell variable containing the flags for the compiler. +dnl CFLAGS or CXXFLAGS. +dnl \param 3 The name of the compiler flag to check for. +dnl \param 4 The shell variable to set with the result of the test. Will +dnl be set to 'yes' if the flag is valid, 'no' otherwise. +dnl \param 5 Additional, optional flags to pass to the C compiler while +dnl looking for the flag in $3. We use this here to pass -Werror to the +dnl flag checks (unless we are checking for -Werror already). +AC_DEFUN([_KYUA_FLAG_AUX], [ + if test x"${$4-unset}" = xunset; then + AC_MSG_CHECKING(whether ${$1} supports $3) + saved_flags="${$2}" + $4=no + $2="${$2} $5 $3" + AC_LINK_IFELSE([AC_LANG_PROGRAM([], [return 0;])], + AC_MSG_RESULT(yes) + $4=yes, + AC_MSG_RESULT(no)) + $2="${saved_flags}" + fi +]) + + +dnl Checks for a compiler flag and appends it to a result variable. +dnl +dnl \param 1 The shell variable containing the compiler name. Used for +dnl reporting purposes only. CC or CXX. +dnl \param 2 The shell variable containing the flags for the compiler. +dnl CFLAGS or CXXFLAGS. +dnl \param 3 The name of the compiler flag to check for. +dnl \param 4 The shell variable to which to append $3 if the flag is valid. +AC_DEFUN([_KYUA_FLAG], [ + _KYUA_FLAG_AUX([$1], [$2], [-Werror], [kyua_$1_has_werror]) + if test "$3" = "-Werror"; then + found=${kyua_$1_has_werror} + else + found=unset + if test ${kyua_$1_has_werror} = yes; then + _KYUA_FLAG_AUX([$1], [$2], [$3], [found], [-Werror]) + else + _KYUA_FLAG_AUX([$1], [$2], [$3], [found], []) + fi + fi + if test ${found} = yes; then + $4="${$4} $3" + fi +]) + + +dnl Checks for a C compiler flag and appends it to a variable. +dnl +dnl \pre The current language is C. +dnl +dnl \param 1 The name of the compiler flag to check for. +dnl \param 2 The shell variable to which to append $1 if the flag is valid. +AC_DEFUN([KYUA_CC_FLAG], [ + AC_LANG_ASSERT([C]) + _KYUA_FLAG([CC], [CFLAGS], [$1], [$2]) +]) + + +dnl Checks for a C++ compiler flag and appends it to a variable. +dnl +dnl \pre The current language is C++. +dnl +dnl \param 1 The name of the compiler flag to check for. +dnl \param 2 The shell variable to which to append $1 if the flag is valid. +AC_DEFUN([KYUA_CXX_FLAG], [ + AC_LANG_ASSERT([C++]) + _KYUA_FLAG([CXX], [CXXFLAGS], [$1], [$2]) +]) + + +dnl Checks for a set of C compiler flags and appends them to CFLAGS. +dnl +dnl The checks are performed independently and only when all the checks are +dnl done, the output variable is modified. +dnl +dnl \param 1 Whitespace-separated list of C flags to check. +AC_DEFUN([KYUA_CC_FLAGS], [ + AC_LANG_PUSH([C]) + valid_cflags= + for f in $1; do + KYUA_CC_FLAG(${f}, valid_cflags) + done + if test -n "${valid_cflags}"; then + CFLAGS="${CFLAGS} ${valid_cflags}" + fi + AC_LANG_POP([C]) +]) + + +dnl Checks for a set of C++ compiler flags and appends them to CXXFLAGS. +dnl +dnl The checks are performed independently and only when all the checks are +dnl done, the output variable is modified. +dnl +dnl \pre The current language is C++. +dnl +dnl \param 1 Whitespace-separated list of C flags to check. +AC_DEFUN([KYUA_CXX_FLAGS], [ + AC_LANG_PUSH([C++]) + valid_cxxflags= + for f in $1; do + KYUA_CXX_FLAG(${f}, valid_cxxflags) + done + if test -n "${valid_cxxflags}"; then + CXXFLAGS="${CXXFLAGS} ${valid_cxxflags}" + fi + AC_LANG_POP([C++]) +]) diff --git a/m4/developer-mode.m4 b/m4/developer-mode.m4 new file mode 100644 index 000000000000..9c9118bf1f86 --- /dev/null +++ b/m4/developer-mode.m4 @@ -0,0 +1,112 @@ +dnl Copyright 2010 Google Inc. +dnl All rights reserved. +dnl +dnl Redistribution and use in source and binary forms, with or without +dnl modification, are permitted provided that the following conditions are +dnl met: +dnl +dnl * Redistributions of source code must retain the above copyright +dnl notice, this list of conditions and the following disclaimer. +dnl * Redistributions in binary form must reproduce the above copyright +dnl notice, this list of conditions and the following disclaimer in the +dnl documentation and/or other materials provided with the distribution. +dnl * Neither the name of Google Inc. nor the names of its contributors +dnl may be used to endorse or promote products derived from this software +dnl without specific prior written permission. +dnl +dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +dnl \file developer-mode.m4 +dnl +dnl "Developer mode" is a mode in which the build system reports any +dnl build-time warnings as fatal errors. This helps in minimizing the +dnl amount of trivial coding problems introduced in the code. +dnl Unfortunately, this is not bullet-proof due to the wide variety of +dnl compilers available and their different warning diagnostics. +dnl +dnl When developer mode support is added to a package, the compilation will +dnl gain a bunch of extra warning diagnostics. These will NOT be enforced +dnl unless developer mode is enabled. +dnl +dnl Developer mode is enabled when the user requests it through the +dnl configure command line, or when building from the repository. The +dnl latter is to minimize the risk of committing new code with warnings +dnl into the tree. + + +dnl Adds "developer mode" support to the package. +dnl +dnl This macro performs the actual definition of the --enable-developer +dnl flag and implements all of its logic. See the file-level comment for +dnl details as to what this implies. +AC_DEFUN([KYUA_DEVELOPER_MODE], [ + m4_foreach([language], [$1], [m4_set_add([languages], language)]) + + AC_ARG_ENABLE( + [developer], + AS_HELP_STRING([--enable-developer], [enable developer features]),, + [if test -d ${srcdir}/.git; then + AC_MSG_NOTICE([building from HEAD; developer mode autoenabled]) + enable_developer=yes + else + enable_developer=no + fi]) + + # + # The following warning flags should also be enabled but cannot be. + # Reasons given below. + # + # -Wold-style-cast: Raises errors when using TIOCGWINSZ, at least under + # Mac OS X. This is due to the way _IOR is defined. + # + + try_c_cxx_flags="-D_FORTIFY_SOURCE=2 \ + -Wall \ + -Wcast-qual \ + -Wextra \ + -Wpointer-arith \ + -Wredundant-decls \ + -Wreturn-type \ + -Wshadow \ + -Wsign-compare \ + -Wswitch \ + -Wwrite-strings" + + try_c_flags="-Wmissing-prototypes \ + -Wno-traditional \ + -Wstrict-prototypes" + + try_cxx_flags="-Wabi \ + -Wctor-dtor-privacy \ + -Wno-deprecated \ + -Wno-non-template-friend \ + -Wno-pmf-conversions \ + -Wnon-virtual-dtor \ + -Woverloaded-virtual \ + -Wreorder \ + -Wsign-promo \ + -Wsynth" + + if test ${enable_developer} = yes; then + try_werror=yes + try_c_cxx_flags="${try_c_cxx_flags} -g -Werror" + else + try_werror=no + try_c_cxx_flags="${try_c_cxx_flags} -DNDEBUG" + fi + + m4_set_contains([languages], [C], + [KYUA_CC_FLAGS(${try_c_cxx_flags} ${try_c_flags})]) + m4_set_contains([languages], [C++], + [KYUA_CXX_FLAGS(${try_c_cxx_flags} ${try_cxx_flags})]) +]) diff --git a/m4/doxygen.m4 b/m4/doxygen.m4 new file mode 100644 index 000000000000..a9b7222a5b42 --- /dev/null +++ b/m4/doxygen.m4 @@ -0,0 +1,62 @@ +dnl Copyright 2010 Google Inc. +dnl All rights reserved. +dnl +dnl Redistribution and use in source and binary forms, with or without +dnl modification, are permitted provided that the following conditions are +dnl met: +dnl +dnl * Redistributions of source code must retain the above copyright +dnl notice, this list of conditions and the following disclaimer. +dnl * Redistributions in binary form must reproduce the above copyright +dnl notice, this list of conditions and the following disclaimer in the +dnl documentation and/or other materials provided with the distribution. +dnl * Neither the name of Google Inc. nor the names of its contributors +dnl may be used to endorse or promote products derived from this software +dnl without specific prior written permission. +dnl +dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +dnl +dnl KYUA_DOXYGEN +dnl +dnl Adds a --with-doxygen flag to the configure script and, when Doxygen support +dnl is requested by the user, sets DOXYGEN to the path of the Doxygen binary and +dnl enables the WITH_DOXYGEN Automake conditional. +dnl +AC_DEFUN([KYUA_DOXYGEN], [ + AC_ARG_WITH([doxygen], + AS_HELP_STRING([--with-doxygen], + [build documentation for internal APIs]), + [], + [with_doxygen=auto]) + + if test "${with_doxygen}" = yes; then + AC_PATH_PROG([DOXYGEN], [doxygen], []) + if test -z "${DOXYGEN}"; then + AC_MSG_ERROR([Doxygen explicitly requested but not found]) + fi + elif test "${with_doxygen}" = auto; then + AC_PATH_PROG([DOXYGEN], [doxygen], []) + elif test "${with_doxygen}" = no; then + DOXYGEN= + else + AC_MSG_CHECKING([for doxygen]) + DOXYGEN="${with_doxygen}" + AC_MSG_RESULT([${DOXYGEN}]) + if test ! -x "${DOXYGEN}"; then + AC_MSG_ERROR([Doxygen binary ${DOXYGEN} is not executable]) + fi + fi + AM_CONDITIONAL([WITH_DOXYGEN], [test -n "${DOXYGEN}"]) + AC_SUBST([DOXYGEN]) +]) diff --git a/m4/lua.m4 b/m4/lua.m4 new file mode 100644 index 000000000000..0d075c576210 --- /dev/null +++ b/m4/lua.m4 @@ -0,0 +1,69 @@ +dnl Copyright 2011 Google Inc. +dnl All rights reserved. +dnl +dnl Redistribution and use in source and binary forms, with or without +dnl modification, are permitted provided that the following conditions are +dnl met: +dnl +dnl * Redistributions of source code must retain the above copyright +dnl notice, this list of conditions and the following disclaimer. +dnl * Redistributions in binary form must reproduce the above copyright +dnl notice, this list of conditions and the following disclaimer in the +dnl documentation and/or other materials provided with the distribution. +dnl * Neither the name of Google Inc. nor the names of its contributors +dnl may be used to endorse or promote products derived from this software +dnl without specific prior written permission. +dnl +dnl THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +dnl "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +dnl LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +dnl A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +dnl OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +dnl SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +dnl LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +dnl DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +dnl THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +dnl (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +dnl OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +dnl +dnl KYUA_LUA +dnl +dnl Helper macro to detect Lua in a variety of systems. +dnl +AC_DEFUN([KYUA_LUA], [ + lua_found=no + + for lua_release in 5.2 5.1; do + if test "${lua_found}" = no; then + PKG_CHECK_MODULES([LUA], [lua${lua_release} >= ${lua_release}], + [lua_found=yes], [true]) + fi + if test "${lua_found}" = no; then + PKG_CHECK_MODULES([LUA], [lua-${lua_release} >= ${lua_release}], + [lua_found=yes], [true]) + fi + if test "${lua_found}" = no; then + PKG_CHECK_MODULES([LUA], [lua >= ${lua_release}], + [lua_found=yes], [true]) + fi + + test "${lua_found}" = no || break + done + + if test "${lua_found}" = no; then + AC_PATH_PROGS([LUA_CONFIG], [lua-config], [unset]) + if test "${LUA_CONFIG}" != unset; then + AC_SUBST([LUA_CFLAGS], [$(${LUA_CONFIG} --include)]) + AC_SUBST([LUA_LIBS], [$(${LUA_CONFIG} --libs)]) + lua_found=yes + fi + fi + + if test "${lua_found}" = no; then + AC_MSG_ERROR([lua (5.1 or newer) is required]) + else + AC_MSG_NOTICE([using LUA_CFLAGS = ${LUA_CFLAGS}]) + AC_MSG_NOTICE([using LUA_LIBS = ${LUA_LIBS}]) + fi +]) diff --git a/operations.cpp b/operations.cpp new file mode 100644 index 000000000000..1b70dc4ed7a4 --- /dev/null +++ b/operations.cpp @@ -0,0 +1,153 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include + +#include "exceptions.hpp" +#include "operations.hpp" +#include "stack_cleaner.hpp" +#include "state.hpp" + + +/// Creates a module: i.e. a table with a set of methods in it. +/// +/// \param s The Lua state. +/// \param name The name of the module to create. +/// \param members The list of member functions to add to the module. +void +lutok::create_module(state& s, const std::string& name, + const std::map< std::string, cxx_function >& members) +{ + stack_cleaner cleaner(s); + s.new_table(); + for (std::map< std::string, cxx_function >::const_iterator + iter = members.begin(); iter != members.end(); iter++) { + s.push_string((*iter).first); + s.push_cxx_function((*iter).second); + s.set_table(-3); + } + s.set_global(name); +} + + +/// Loads and processes a Lua file. +/// +/// This is a replacement for luaL_dofile but with proper error reporting +/// and stack control. +/// +/// \param s The Lua state. +/// \param file The file to load. +/// \param nargs The number of arguments on the stack to pass to the file. +/// \param nresults The number of results to expect; -1 for any. +/// \param errfunc If not 0, index of a function in the stack to act as an +/// error handler. +/// +/// \return The number of results left on the stack. +/// +/// \throw error If there is a problem processing the file. +unsigned int +lutok::do_file(state& s, const std::string& file, const int nargs, + const int nresults, const int errfunc) +{ + assert(nresults >= -1); + const int height = s.get_top() - nargs; + + try { + s.load_file(file); + if (nargs > 0) + s.insert(-nargs - 1); + s.pcall(nargs, nresults == -1 ? LUA_MULTRET : nresults, + errfunc == 0 ? 0 : errfunc - 1); + } catch (const lutok::api_error& e) { + throw lutok::error("Failed to load Lua file '" + file + "': " + + e.what()); + } + + const int actual_results = s.get_top() - height; + assert(nresults == -1 || actual_results == nresults); + assert(actual_results >= 0); + return static_cast< unsigned int >(actual_results); +} + + +/// Processes a Lua script. +/// +/// This is a replacement for luaL_dostring but with proper error reporting +/// and stack control. +/// +/// \param s The Lua state. +/// \param str The string to process. +/// \param nargs The number of arguments on the stack to pass to the chunk. +/// \param nresults The number of results to expect; -1 for any. +/// \param errfunc If not 0, index of a function in the stack to act as an +/// error handler. +/// +/// \return The number of results left on the stack. +/// +/// \throw error If there is a problem processing the string. +unsigned int +lutok::do_string(state& s, const std::string& str, const int nargs, + const int nresults, const int errfunc) +{ + assert(nresults >= -1); + const int height = s.get_top() - nargs; + + try { + s.load_string(str); + if (nargs > 0) + s.insert(-nargs - 1); + s.pcall(nargs, nresults == -1 ? LUA_MULTRET : nresults, + errfunc == 0 ? 0 : errfunc - 1); + } catch (const lutok::api_error& e) { + throw lutok::error("Failed to process Lua string '" + str + "': " + + e.what()); + } + + const int actual_results = s.get_top() - height; + assert(nresults == -1 || actual_results == nresults); + assert(actual_results >= 0); + return static_cast< unsigned int >(actual_results); +} + + +/// Convenience function to evaluate a Lua expression. +/// +/// \param s The Lua state. +/// \param expression The textual expression to evaluate. +/// \param nresults The number of results to leave on the stack. Must be +/// positive. +/// +/// \throw api_error If there is a problem evaluating the expression. +void +lutok::eval(state& s, const std::string& expression, const int nresults) +{ + assert(nresults > 0); + do_string(s, "return " + expression, 0, nresults, 0); +} diff --git a/operations.hpp b/operations.hpp new file mode 100644 index 000000000000..ead7c77d33bb --- /dev/null +++ b/operations.hpp @@ -0,0 +1,55 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file operations.hpp +/// Extra generic functions to interact with Lua. + +#if !defined(LUTOK_OPERATIONS_HPP) +#define LUTOK_OPERATIONS_HPP + +#include +#include +#include + +#include + +namespace lutok { + + +void create_module(state&, const std::string&, + const std::map< std::string, cxx_function >&); +unsigned int do_file(state&, const std::string&, const int, const int, + const int); +unsigned int do_string(state&, const std::string&, const int, const int, + const int); +void eval(state&, const std::string&, const int); + + +} // namespace lutok + +#endif // !defined(LUTOK_OPERATIONS_HPP) diff --git a/operations_test.cpp b/operations_test.cpp new file mode 100644 index 000000000000..72800f7d9bab --- /dev/null +++ b/operations_test.cpp @@ -0,0 +1,372 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "operations.hpp" + +#include + +#include + +#include "exceptions.hpp" +#include "state.ipp" +#include "test_utils.hpp" + + +namespace { + + +/// Addition function for injection into Lua. +/// +/// \pre stack(-2) The first summand. +/// \pre stack(-1) The second summand. +/// \post stack(-1) The result of the sum. +/// +/// \param state The Lua state. +/// +/// \return The number of results (1). +static int +hook_add(lutok::state& state) +{ + state.push_integer(state.to_integer(-1) + state.to_integer(-2)); + return 1; +} + + +/// Multiplication function for injection into Lua. +/// +/// \pre stack(-2) The first factor. +/// \pre stack(-1) The second factor. +/// \post stack(-1) The product. +/// +/// \param state The Lua state. +/// +/// \return The number of results (1). +static int +hook_multiply(lutok::state& state) +{ + state.push_integer(state.to_integer(-1) * state.to_integer(-2)); + return 1; +} + + +} // anonymous namespace + + +ATF_TEST_CASE_WITHOUT_HEAD(create_module__empty); +ATF_TEST_CASE_BODY(create_module__empty) +{ + lutok::state state; + std::map< std::string, lutok::cxx_function > members; + lutok::create_module(state, "my_math", members); + + state.open_base(); + lutok::do_string(state, "return next(my_math) == nil", 0, 1, 0); + ATF_REQUIRE(state.to_boolean(-1)); + state.pop(1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(create_module__one); +ATF_TEST_CASE_BODY(create_module__one) +{ + lutok::state state; + std::map< std::string, lutok::cxx_function > members; + members["add"] = hook_add; + lutok::create_module(state, "my_math", members); + + lutok::do_string(state, "return my_math.add(10, 20)", 0, 1, 0); + ATF_REQUIRE_EQ(30, state.to_integer(-1)); + state.pop(1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(create_module__many); +ATF_TEST_CASE_BODY(create_module__many) +{ + lutok::state state; + std::map< std::string, lutok::cxx_function > members; + members["add"] = hook_add; + members["multiply"] = hook_multiply; + members["add2"] = hook_add; + lutok::create_module(state, "my_math", members); + + lutok::do_string(state, "return my_math.add(10, 20)", 0, 1, 0); + ATF_REQUIRE_EQ(30, state.to_integer(-1)); + lutok::do_string(state, "return my_math.multiply(10, 20)", 0, 1, 0); + ATF_REQUIRE_EQ(200, state.to_integer(-1)); + lutok::do_string(state, "return my_math.add2(20, 30)", 0, 1, 0); + ATF_REQUIRE_EQ(50, state.to_integer(-1)); + state.pop(3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__some_args); +ATF_TEST_CASE_BODY(do_file__some_args) +{ + std::ofstream output("test.lua"); + output << "local a1, a2 = ...\nreturn a1 * 2, a2 * 2\n"; + output.close(); + + lutok::state state; + state.push_integer(456); + state.push_integer(3); + state.push_integer(5); + state.push_integer(123); + ATF_REQUIRE_EQ(2, lutok::do_file(state, "test.lua", 3, -1, 0)); + ATF_REQUIRE_EQ(3, state.get_top()); + ATF_REQUIRE_EQ(456, state.to_integer(-3)); + ATF_REQUIRE_EQ(6, state.to_integer(-2)); + ATF_REQUIRE_EQ(10, state.to_integer(-1)); + state.pop(3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__any_results); +ATF_TEST_CASE_BODY(do_file__any_results) +{ + std::ofstream output("test.lua"); + output << "return 10, 20, 30\n"; + output.close(); + + lutok::state state; + ATF_REQUIRE_EQ(3, lutok::do_file(state, "test.lua", 0, -1, 0)); + ATF_REQUIRE_EQ(3, state.get_top()); + ATF_REQUIRE_EQ(10, state.to_integer(-3)); + ATF_REQUIRE_EQ(20, state.to_integer(-2)); + ATF_REQUIRE_EQ(30, state.to_integer(-1)); + state.pop(3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__no_results); +ATF_TEST_CASE_BODY(do_file__no_results) +{ + std::ofstream output("test.lua"); + output << "return 10, 20, 30\n"; + output.close(); + + lutok::state state; + ATF_REQUIRE_EQ(0, lutok::do_file(state, "test.lua", 0, 0, 0)); + ATF_REQUIRE_EQ(0, state.get_top()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__many_results); +ATF_TEST_CASE_BODY(do_file__many_results) +{ + std::ofstream output("test.lua"); + output << "return 10, 20, 30\n"; + output.close(); + + lutok::state state; + ATF_REQUIRE_EQ(2, lutok::do_file(state, "test.lua", 0, 2, 0)); + ATF_REQUIRE_EQ(2, state.get_top()); + ATF_REQUIRE_EQ(10, state.to_integer(-2)); + ATF_REQUIRE_EQ(20, state.to_integer(-1)); + state.pop(2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__not_found); +ATF_TEST_CASE_BODY(do_file__not_found) +{ + lutok::state state; + stack_balance_checker checker(state); + ATF_REQUIRE_THROW_RE(lutok::file_not_found_error, "missing.lua", + lutok::do_file(state, "missing.lua", 0, 0, 0)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__error); +ATF_TEST_CASE_BODY(do_file__error) +{ + std::ofstream output("test.lua"); + output << "a b c\n"; + output.close(); + + lutok::state state; + stack_balance_checker checker(state); + ATF_REQUIRE_THROW_RE(lutok::error, "Failed to load Lua file 'test.lua'", + lutok::do_file(state, "test.lua", 0, 0, 0)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_file__error_with_errfunc); +ATF_TEST_CASE_BODY(do_file__error_with_errfunc) +{ + std::ofstream output("test.lua"); + output << "unknown_function()\n"; + output.close(); + + lutok::state state; + lutok::eval(state, "function(message) return 'This is an error!' end", 1); + { + stack_balance_checker checker(state); + ATF_REQUIRE_THROW_RE(lutok::error, "This is an error!", + lutok::do_file(state, "test.lua", 0, 0, -2)); + } + state.pop(1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_string__some_args); +ATF_TEST_CASE_BODY(do_string__some_args) +{ + lutok::state state; + state.push_integer(456); + state.push_integer(3); + state.push_integer(5); + state.push_integer(123); + ATF_REQUIRE_EQ(2, lutok::do_string( + state, "local a1, a2 = ...\nreturn a1 * 2, a2 * 2\n", 3, -1, 0)); + ATF_REQUIRE_EQ(3, state.get_top()); + ATF_REQUIRE_EQ(456, state.to_integer(-3)); + ATF_REQUIRE_EQ(6, state.to_integer(-2)); + ATF_REQUIRE_EQ(10, state.to_integer(-1)); + state.pop(3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_string__any_results); +ATF_TEST_CASE_BODY(do_string__any_results) +{ + lutok::state state; + ATF_REQUIRE_EQ(3, lutok::do_string(state, "return 10, 20, 30", 0, -1, 0)); + ATF_REQUIRE_EQ(3, state.get_top()); + ATF_REQUIRE_EQ(10, state.to_integer(-3)); + ATF_REQUIRE_EQ(20, state.to_integer(-2)); + ATF_REQUIRE_EQ(30, state.to_integer(-1)); + state.pop(3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_string__no_results); +ATF_TEST_CASE_BODY(do_string__no_results) +{ + lutok::state state; + ATF_REQUIRE_EQ(0, lutok::do_string(state, "return 10, 20, 30", 0, 0, 0)); + ATF_REQUIRE_EQ(0, state.get_top()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_string__many_results); +ATF_TEST_CASE_BODY(do_string__many_results) +{ + lutok::state state; + ATF_REQUIRE_EQ(2, lutok::do_string(state, "return 10, 20, 30", 0, 2, 0)); + ATF_REQUIRE_EQ(2, state.get_top()); + ATF_REQUIRE_EQ(10, state.to_integer(-2)); + ATF_REQUIRE_EQ(20, state.to_integer(-1)); + state.pop(2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_string__error); +ATF_TEST_CASE_BODY(do_string__error) +{ + lutok::state state; + stack_balance_checker checker(state); + ATF_REQUIRE_THROW_RE(lutok::error, "Failed to process Lua string 'a b c'", + lutok::do_string(state, "a b c", 0, 0, 0)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(do_string__error_with_errfunc); +ATF_TEST_CASE_BODY(do_string__error_with_errfunc) +{ + lutok::state state; + lutok::eval(state, "function(message) return 'This is an error!' end", 1); + { + stack_balance_checker checker(state); + ATF_REQUIRE_THROW_RE(lutok::error, "This is an error!", + lutok::do_string(state, "unknown_function()", + 0, 0, -2)); + } + state.pop(1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(eval__one_result); +ATF_TEST_CASE_BODY(eval__one_result) +{ + lutok::state state; + stack_balance_checker checker(state); + lutok::eval(state, "3 + 10", 1); + ATF_REQUIRE_EQ(13, state.to_integer(-1)); + state.pop(1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(eval__many_results); +ATF_TEST_CASE_BODY(eval__many_results) +{ + lutok::state state; + stack_balance_checker checker(state); + lutok::eval(state, "5, 8, 10", 3); + ATF_REQUIRE_EQ(5, state.to_integer(-3)); + ATF_REQUIRE_EQ(8, state.to_integer(-2)); + ATF_REQUIRE_EQ(10, state.to_integer(-1)); + state.pop(3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(eval__error); +ATF_TEST_CASE_BODY(eval__error) +{ + lutok::state state; + stack_balance_checker checker(state); + ATF_REQUIRE_THROW(lutok::error, + lutok::eval(state, "non_existent.method()", 1)); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, create_module__empty); + ATF_ADD_TEST_CASE(tcs, create_module__one); + ATF_ADD_TEST_CASE(tcs, create_module__many); + + ATF_ADD_TEST_CASE(tcs, do_file__some_args); + ATF_ADD_TEST_CASE(tcs, do_file__any_results); + ATF_ADD_TEST_CASE(tcs, do_file__no_results); + ATF_ADD_TEST_CASE(tcs, do_file__many_results); + ATF_ADD_TEST_CASE(tcs, do_file__not_found); + ATF_ADD_TEST_CASE(tcs, do_file__error); + ATF_ADD_TEST_CASE(tcs, do_file__error_with_errfunc); + + ATF_ADD_TEST_CASE(tcs, do_string__some_args); + ATF_ADD_TEST_CASE(tcs, do_string__any_results); + ATF_ADD_TEST_CASE(tcs, do_string__no_results); + ATF_ADD_TEST_CASE(tcs, do_string__many_results); + ATF_ADD_TEST_CASE(tcs, do_string__error); + ATF_ADD_TEST_CASE(tcs, do_string__error_with_errfunc); + + ATF_ADD_TEST_CASE(tcs, eval__one_result); + ATF_ADD_TEST_CASE(tcs, eval__many_results); + ATF_ADD_TEST_CASE(tcs, eval__error); +} diff --git a/stack_cleaner.cpp b/stack_cleaner.cpp new file mode 100644 index 000000000000..419e55a12981 --- /dev/null +++ b/stack_cleaner.cpp @@ -0,0 +1,91 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include "stack_cleaner.hpp" +#include "state.ipp" + + +/// Internal implementation for lutok::stack_cleaner. +struct lutok::stack_cleaner::impl { + /// Reference to the Lua state this stack_cleaner refers to. + state& state_ref; + + /// The depth of the Lua stack to be restored. + unsigned int original_depth; + + /// Constructor. + /// + /// \param state_ref_ Reference to the Lua state. + /// \param original_depth_ The depth of the Lua stack. + impl(state& state_ref_, const unsigned int original_depth_) : + state_ref(state_ref_), + original_depth(original_depth_) + { + } +}; + + +/// Creates a new stack cleaner. +/// +/// This gathers the current height of the stack so that extra elements can be +/// popped during destruction. +/// +/// \param state_ The Lua state. +lutok::stack_cleaner::stack_cleaner(state& state_) : + _pimpl(new impl(state_, state_.get_top())) +{ +} + + +/// Pops any values from the stack not known at construction time. +/// +/// \pre The current height of the stack must be equal or greater to the height +/// of the stack when this object was instantiated. +lutok::stack_cleaner::~stack_cleaner(void) +{ + const unsigned int current_depth = _pimpl->state_ref.get_top(); + assert(current_depth >= _pimpl->original_depth); + const unsigned int diff = current_depth - _pimpl->original_depth; + if (diff > 0) + _pimpl->state_ref.pop(diff); +} + + +/// Forgets about any elements currently in the stack. +/// +/// This allows a function to return values on the stack because all the +/// elements that are currently in the stack will not be touched during +/// destruction when the function is called. +void +lutok::stack_cleaner::forget(void) +{ + _pimpl->original_depth = _pimpl->state_ref.get_top(); +} diff --git a/stack_cleaner.hpp b/stack_cleaner.hpp new file mode 100644 index 000000000000..cd3e1468656f --- /dev/null +++ b/stack_cleaner.hpp @@ -0,0 +1,93 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file stack_cleaner.hpp +/// Provides the stack_cleaner class. + +#if !defined(LUTOK_STACK_CLEANER_HPP) +#define LUTOK_STACK_CLEANER_HPP + +#include + +#include + +namespace lutok { + + +/// A RAII model for values on the Lua stack. +/// +/// At creation time, the object records the current depth of the Lua stack and, +/// during destruction, restores the recorded depth by popping as many stack +/// entries as required. As a corollary, the stack can only grow during the +/// lifetime of a stack_cleaner object (or shrink, but cannot become shorter +/// than the depth recorded at creation time). +/// +/// Use this class as follows: +/// +/// state s; +/// { +/// stack_cleaner cleaner1(s); +/// s.push_integer(3); +/// s.push_integer(5); +/// ... do stuff here ... +/// for (...) { +/// stack_cleaner cleaner2(s); +/// s.load_string("..."); +/// s.pcall(0, 1, 0); +/// ... do stuff here ... +/// } +/// // cleaner2 destroyed; the result of pcall is gone. +/// } +/// // cleaner1 destroyed; the integers 3 and 5 are gone. +/// +/// You must give a name to the instantiated objects even if they cannot be +/// accessed later. Otherwise, the instance will be destroyed right away and +/// will not have the desired effect. +class stack_cleaner { + struct impl; + + /// Pointer to the shared internal implementation. + std::auto_ptr< impl > _pimpl; + + /// Disallow copies. + stack_cleaner(const stack_cleaner&); + + /// Disallow assignment. + stack_cleaner& operator=(const stack_cleaner&); + +public: + stack_cleaner(state&); + ~stack_cleaner(void); + + void forget(void); +}; + + +} // namespace lutok + +#endif // !defined(LUTOK_STACK_CLEANER_HPP) diff --git a/stack_cleaner_test.cpp b/stack_cleaner_test.cpp new file mode 100644 index 000000000000..2aee28baa361 --- /dev/null +++ b/stack_cleaner_test.cpp @@ -0,0 +1,108 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "stack_cleaner.hpp" + +#include + + +ATF_TEST_CASE_WITHOUT_HEAD(empty); +ATF_TEST_CASE_BODY(empty) +{ + lutok::state state; + { + lutok::stack_cleaner cleaner(state); + ATF_REQUIRE_EQ(0, state.get_top()); + } + ATF_REQUIRE_EQ(0, state.get_top()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(some); +ATF_TEST_CASE_BODY(some) +{ + lutok::state state; + { + lutok::stack_cleaner cleaner(state); + state.push_integer(15); + ATF_REQUIRE_EQ(1, state.get_top()); + state.push_integer(30); + ATF_REQUIRE_EQ(2, state.get_top()); + } + ATF_REQUIRE_EQ(0, state.get_top()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(nested); +ATF_TEST_CASE_BODY(nested) +{ + lutok::state state; + { + lutok::stack_cleaner cleaner1(state); + state.push_integer(10); + ATF_REQUIRE_EQ(1, state.get_top()); + ATF_REQUIRE_EQ(10, state.to_integer(-1)); + { + lutok::stack_cleaner cleaner2(state); + state.push_integer(20); + ATF_REQUIRE_EQ(2, state.get_top()); + ATF_REQUIRE_EQ(20, state.to_integer(-1)); + ATF_REQUIRE_EQ(10, state.to_integer(-2)); + } + ATF_REQUIRE_EQ(1, state.get_top()); + ATF_REQUIRE_EQ(10, state.to_integer(-1)); + } + ATF_REQUIRE_EQ(0, state.get_top()); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(forget); +ATF_TEST_CASE_BODY(forget) +{ + lutok::state state; + { + lutok::stack_cleaner cleaner(state); + state.push_integer(15); + state.push_integer(30); + cleaner.forget(); + state.push_integer(60); + ATF_REQUIRE_EQ(3, state.get_top()); + } + ATF_REQUIRE_EQ(2, state.get_top()); + ATF_REQUIRE_EQ(30, state.to_integer(-1)); + state.pop(2); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, empty); + ATF_ADD_TEST_CASE(tcs, some); + ATF_ADD_TEST_CASE(tcs, nested); + ATF_ADD_TEST_CASE(tcs, forget); +} diff --git a/state.cpp b/state.cpp new file mode 100644 index 000000000000..3c140388e0cb --- /dev/null +++ b/state.cpp @@ -0,0 +1,904 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +extern "C" { +#include +} + +#include +#include + +#include "c_gate.hpp" +#include "exceptions.hpp" +#include "state.ipp" + + +namespace { + + +/// Wrapper around lua_getglobal to run in a protected environment. +/// +/// \pre stack(-1) is the name of the global to get. +/// \post stack(-1) is the value of the global. +/// +/// \param state The Lua C API state. +/// +/// \return The number of return values pushed onto the stack. +static int +protected_getglobal(lua_State* state) +{ + lua_getglobal(state, lua_tostring(state, -1)); + return 1; +} + + +/// Wrapper around lua_gettable to run in a protected environment. +/// +/// \pre stack(-2) is the table to get the element from. +/// \pre stack(-1) is the table index. +/// \post stack(-1) is the value of stack(-2)[stack(-1)]. +/// +/// \param state The Lua C API state. +/// +/// \return The number of return values pushed onto the stack. +static int +protected_gettable(lua_State* state) +{ + lua_gettable(state, -2); + return 1; +} + + +/// Wrapper around lua_next to run in a protected environment. +/// +/// \pre stack(-2) is the table to get the next element from. +/// \pre stack(-1) is the last processed key. +/// \post stack(-1) is the value of next(stack(-2), stack(-1)). +/// +/// \param state The Lua C API state. +/// +/// \return The number of return values pushed onto the stack. +static int +protected_next(lua_State* state) +{ + const int more = lua_next(state, -2) != 0; + lua_pushboolean(state, more); + return more ? 3 : 1; +} + + +/// Wrapper around lua_setglobal to run in a protected environment. +/// +/// \pre stack(-2) is the name of the global to set. +/// \pre stack(-1) is the value to set the global to. +/// +/// \param state The Lua C API state. +/// +/// \return The number of return values pushed onto the stack. +static int +protected_setglobal(lua_State* state) +{ + lua_setglobal(state, lua_tostring(state, -2)); + return 0; +} + + +/// Wrapper around lua_settable to run in a protected environment. +/// +/// \pre stack(-3) is the table to set the element into. +/// \pre stack(-2) is the table index. +/// \pre stack(-1) is the value to set. +/// +/// \param state The Lua C API state. +/// +/// \return The number of return values pushed onto the stack. +static int +protected_settable(lua_State* state) +{ + lua_settable(state, -3); + return 0; +} + + +/// Calls a C++ Lua function from a C calling environment. +/// +/// Any errors reported by the C++ function are caught and reported to the +/// caller as Lua errors. +/// +/// \param function The C++ function to call. +/// \param raw_state The raw Lua state. +/// +/// \return The number of return values pushed onto the Lua stack by the +/// function. +static int +call_cxx_function_from_c(lutok::cxx_function function, + lua_State* raw_state) throw() +{ + char error_buf[1024]; + + try { + lutok::state state = lutok::state_c_gate::connect(raw_state); + return function(state); + } catch (const std::exception& e) { + std::strncpy(error_buf, e.what(), sizeof(error_buf)); + } catch (...) { + std::strncpy(error_buf, "Unhandled exception in Lua C++ hook", + sizeof(error_buf)); + } + error_buf[sizeof(error_buf) - 1] = '\0'; + // We raise the Lua error from outside the try/catch context and we use + // a stack-based buffer to hold the message to ensure that we do not leak + // any C++ objects (and, as a likely result, memory) when Lua performs its + // longjmp. + return luaL_error(raw_state, "%s", error_buf); +} + + +/// Lua glue to call a C++ closure. +/// +/// This Lua binding is actually a closure that we have constructed from the +/// state.push_cxx_closure() method. The closure contains the same upvalues +/// provided by the user plus an extra upvalue that contains the address of the +/// C++ function we have to call. All we do here is safely delegate the +/// execution to the wrapped C++ closure. +/// +/// \param raw_state The Lua C API state. +/// +/// \return The number of return values of the called closure. +static int +cxx_closure_trampoline(lua_State* raw_state) +{ + lutok::state state = lutok::state_c_gate::connect(raw_state); + + int nupvalues; + { + lua_Debug debug; + lua_getstack(raw_state, 0, &debug); + lua_getinfo(raw_state, "u", &debug); + nupvalues = debug.nups; + } + + lutok::cxx_function* function = state.to_userdata< lutok::cxx_function >( + state.upvalue_index(nupvalues)); + return call_cxx_function_from_c(*function, raw_state); +} + + +/// Lua glue to call a C++ function. +/// +/// This Lua binding is actually a closure that we have constructed from the +/// state.push_cxx_function() method. The closure has a single upvalue that +/// contains the address of the C++ function we have to call. All we do here is +/// safely delegate the execution to the wrapped C++ function. +/// +/// \param raw_state The Lua C API state. +/// +/// \return The number of return values of the called function. +static int +cxx_function_trampoline(lua_State* raw_state) +{ + lutok::state state = lutok::state_c_gate::connect(raw_state); + lutok::cxx_function* function = state.to_userdata< lutok::cxx_function >( + state.upvalue_index(1)); + return call_cxx_function_from_c(*function, raw_state); +} + + +} // anonymous namespace + + +const int lutok::registry_index = LUA_REGISTRYINDEX; + + +/// Internal implementation for lutok::state. +struct lutok::state::impl { + /// The Lua internal state. + lua_State* lua_state; + + /// Whether we own the state or not (to decide if we close it). + bool owned; + + /// Constructor. + /// + /// \param lua_ The Lua internal state. + /// \param owned_ Whether we own the state or not. + impl(lua_State* lua_, bool owned_) : + lua_state(lua_), + owned(owned_) + { + } +}; + + +/// Initializes the Lua state. +/// +/// You must share the same state object alongside the lifetime of your Lua +/// session. As soon as the object is destroyed, the session is terminated. +lutok::state::state(void) +{ + lua_State* lua = luaL_newstate(); + if (lua == NULL) + throw lutok::error("lua open failed"); + _pimpl.reset(new impl(lua, true)); +} + + +/// Initializes the Lua state from an existing raw state. +/// +/// Instances constructed using this method do NOT own the raw state. This +/// means that, on exit, the state will not be destroyed. +/// +/// \param raw_state_ The raw Lua state to wrap. +lutok::state::state(void* raw_state_) : + _pimpl(new impl(reinterpret_cast< lua_State* >(raw_state_), false)) +{ +} + + +/// Destructor for the Lua state. +/// +/// Closes the session unless it has already been closed by calling the +/// close() method. It is recommended to explicitly close the session in the +/// code. +lutok::state::~state(void) +{ + if (_pimpl->owned && _pimpl->lua_state != NULL) + close(); +} + + +/// Terminates this Lua session. +/// +/// It is recommended to call this instead of relying on the destructor to do +/// the cleanup, but it is not a requirement to use close(). +/// +/// \pre close() has not yet been called. +/// \pre The Lua stack is empty. This is not truly necessary but ensures that +/// our code is consistent and clears the stack explicitly. +void +lutok::state::close(void) +{ + assert(_pimpl->lua_state != NULL); + assert(lua_gettop(_pimpl->lua_state) == 0); + lua_close(_pimpl->lua_state); + _pimpl->lua_state = NULL; +} + + +/// Wrapper around lua_getglobal. +/// +/// \param name The second parameter to lua_getglobal. +/// +/// \throw api_error If lua_getglobal fails. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +void +lutok::state::get_global(const std::string& name) +{ + lua_pushcfunction(_pimpl->lua_state, protected_getglobal); + lua_pushstring(_pimpl->lua_state, name.c_str()); + if (lua_pcall(_pimpl->lua_state, 1, 1, 0) != 0) + throw lutok::api_error::from_stack(*this, "lua_getglobal"); +} + + +/// Pushes a reference to the global table onto the stack. +/// +/// This is a wrapper around the incompatible differences between Lua 5.1 and +/// 5.2 to access to the globals table. +/// +/// \post state(-1) Contains the reference to the globals table. +void +lutok::state::get_global_table(void) +{ +#if LUA_VERSION_NUM >= 502 + lua_pushvalue(_pimpl->lua_state, registry_index); + lua_pushinteger(_pimpl->lua_state, LUA_RIDX_GLOBALS); + lua_gettable(_pimpl->lua_state, -2); + lua_remove(_pimpl->lua_state, -2); +#else + lua_pushvalue(_pimpl->lua_state, LUA_GLOBALSINDEX); +#endif +} + + +/// Wrapper around luaL_getmetafield. +/// +/// \param index The second parameter to luaL_getmetafield. +/// \param name The third parameter to luaL_getmetafield. +/// +/// \return The return value of luaL_getmetafield. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +bool +lutok::state::get_metafield(const int index, const std::string& name) +{ + return luaL_getmetafield(_pimpl->lua_state, index, name.c_str()) != 0; +} + + +/// Wrapper around lua_getmetatable. +/// +/// \param index The second parameter to lua_getmetatable. +/// +/// \return The return value of lua_getmetatable. +bool +lutok::state::get_metatable(const int index) +{ + return lua_getmetatable(_pimpl->lua_state, index) != 0; +} + + +/// Wrapper around lua_gettable. +/// +/// \param index The second parameter to lua_gettable. +/// +/// \throw api_error If lua_gettable fails. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +void +lutok::state::get_table(const int index) +{ + assert(lua_gettop(_pimpl->lua_state) >= 2); + lua_pushcfunction(_pimpl->lua_state, protected_gettable); + lua_pushvalue(_pimpl->lua_state, index < 0 ? index - 1 : index); + lua_pushvalue(_pimpl->lua_state, -3); + if (lua_pcall(_pimpl->lua_state, 2, 1, 0) != 0) + throw lutok::api_error::from_stack(*this, "lua_gettable"); + lua_remove(_pimpl->lua_state, -2); +} + + +/// Wrapper around lua_gettop. +/// +/// \return The return value of lua_gettop. +int +lutok::state::get_top(void) +{ + return lua_gettop(_pimpl->lua_state); +} + + +/// Wrapper around lua_insert. +/// +/// \param index The second parameter to lua_insert. +void +lutok::state::insert(const int index) +{ + lua_insert(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_isboolean. +/// +/// \param index The second parameter to lua_isboolean. +/// +/// \return The return value of lua_isboolean. +bool +lutok::state::is_boolean(const int index) +{ + return lua_isboolean(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_isfunction. +/// +/// \param index The second parameter to lua_isfunction. +/// +/// \return The return value of lua_isfunction. +bool +lutok::state::is_function(const int index) +{ + return lua_isfunction(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_isnil. +/// +/// \param index The second parameter to lua_isnil. +/// +/// \return The return value of lua_isnil. +bool +lutok::state::is_nil(const int index) +{ + return lua_isnil(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_isnumber. +/// +/// \param index The second parameter to lua_isnumber. +/// +/// \return The return value of lua_isnumber. +bool +lutok::state::is_number(const int index) +{ + return lua_isnumber(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_isstring. +/// +/// \param index The second parameter to lua_isstring. +/// +/// \return The return value of lua_isstring. +bool +lutok::state::is_string(const int index) +{ + return lua_isstring(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_istable. +/// +/// \param index The second parameter to lua_istable. +/// +/// \return The return value of lua_istable. +bool +lutok::state::is_table(const int index) +{ + return lua_istable(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_isuserdata. +/// +/// \param index The second parameter to lua_isuserdata. +/// +/// \return The return value of lua_isuserdata. +bool +lutok::state::is_userdata(const int index) +{ + return lua_isuserdata(_pimpl->lua_state, index); +} + + +/// Wrapper around luaL_loadfile. +/// +/// \param file The second parameter to luaL_loadfile. +/// +/// \throw api_error If luaL_loadfile returns an error. +/// \throw file_not_found_error If the file cannot be accessed. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::load_file(const std::string& file) +{ + if (::access(file.c_str(), R_OK) == -1) + throw lutok::file_not_found_error(file); + if (luaL_loadfile(_pimpl->lua_state, file.c_str()) != 0) + throw lutok::api_error::from_stack(*this, "luaL_loadfile"); +} + + +/// Wrapper around luaL_loadstring. +/// +/// \param str The second parameter to luaL_loadstring. +/// +/// \throw api_error If luaL_loadstring returns an error. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::load_string(const std::string& str) +{ + if (luaL_loadstring(_pimpl->lua_state, str.c_str()) != 0) + throw lutok::api_error::from_stack(*this, "luaL_loadstring"); +} + + +/// Wrapper around lua_newtable. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::new_table(void) +{ + lua_newtable(_pimpl->lua_state); +} + + +/// Wrapper around lua_newuserdata. +/// +/// This is internal. The public type-safe interface of this method should be +/// used instead. +/// +/// \param size The second parameter to lua_newuserdata. +/// +/// \return The return value of lua_newuserdata. +/// +/// \warning Terminates execution if there is not enough memory. +void* +lutok::state::new_userdata_voidp(const size_t size) +{ + return lua_newuserdata(_pimpl->lua_state, size); +} + + +/// Wrapper around lua_next. +/// +/// \param index The second parameter to lua_next. +/// +/// \return True if there are more elements to process; false otherwise. +/// +/// \warning Terminates execution if there is not enough memory. +bool +lutok::state::next(const int index) +{ + assert(lua_istable(_pimpl->lua_state, index)); + assert(lua_gettop(_pimpl->lua_state) >= 1); + lua_pushcfunction(_pimpl->lua_state, protected_next); + lua_pushvalue(_pimpl->lua_state, index < 0 ? index - 1 : index); + lua_pushvalue(_pimpl->lua_state, -3); + if (lua_pcall(_pimpl->lua_state, 2, LUA_MULTRET, 0) != 0) + throw lutok::api_error::from_stack(*this, "lua_next"); + const bool more = lua_toboolean(_pimpl->lua_state, -1); + lua_pop(_pimpl->lua_state, 1); + if (more) + lua_remove(_pimpl->lua_state, -3); + else + lua_pop(_pimpl->lua_state, 1); + return more; +} + + +/// Wrapper around luaL_openlibs. +/// +/// \throw api_error If luaL_openlibs fails. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::open_all(void) +{ + luaL_openlibs(_pimpl->lua_state); +} + + +/// Wrapper around luaopen_base. +/// +/// \throw api_error If luaopen_base fails. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::open_base(void) +{ + lua_pushcfunction(_pimpl->lua_state, luaopen_base); + if (lua_pcall(_pimpl->lua_state, 0, 0, 0) != 0) + throw lutok::api_error::from_stack(*this, "luaopen_base"); +} + + +/// Wrapper around luaopen_string. +/// +/// \throw api_error If luaopen_string fails. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::open_string(void) +{ +#if LUA_VERSION_NUM >= 502 + luaL_requiref(_pimpl->lua_state, LUA_STRLIBNAME, luaopen_string, 1); + lua_pop(_pimpl->lua_state, 1); +#else + lua_pushcfunction(_pimpl->lua_state, luaopen_string); + if (lua_pcall(_pimpl->lua_state, 0, 0, 0) != 0) + throw lutok::api_error::from_stack(*this, "luaopen_string"); +#endif +} + + +/// Wrapper around luaopen_table. +/// +/// \throw api_error If luaopen_table fails. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::open_table(void) +{ +#if LUA_VERSION_NUM >= 502 + luaL_requiref(_pimpl->lua_state, LUA_TABLIBNAME, luaopen_table, 1); + lua_pop(_pimpl->lua_state, 1); +#else + lua_pushcfunction(_pimpl->lua_state, luaopen_table); + if (lua_pcall(_pimpl->lua_state, 0, 0, 0) != 0) + throw lutok::api_error::from_stack(*this, "luaopen_table"); +#endif +} + + +/// Wrapper around lua_pcall. +/// +/// \param nargs The second parameter to lua_pcall. +/// \param nresults The third parameter to lua_pcall. +/// \param errfunc The fourth parameter to lua_pcall. +/// +/// \throw api_error If lua_pcall returns an error. +void +lutok::state::pcall(const int nargs, const int nresults, const int errfunc) +{ + if (lua_pcall(_pimpl->lua_state, nargs, nresults, errfunc) != 0) + throw lutok::api_error::from_stack(*this, "lua_pcall"); +} + + +/// Wrapper around lua_pop. +/// +/// \param count The second parameter to lua_pop. +void +lutok::state::pop(const int count) +{ + assert(count <= lua_gettop(_pimpl->lua_state)); + lua_pop(_pimpl->lua_state, count); + assert(lua_gettop(_pimpl->lua_state) >= 0); +} + + +/// Wrapper around lua_pushboolean. +/// +/// \param value The second parameter to lua_pushboolean. +void +lutok::state::push_boolean(const bool value) +{ + lua_pushboolean(_pimpl->lua_state, value ? 1 : 0); +} + + +/// Wrapper around lua_pushcclosure. +/// +/// This is not a pure wrapper around lua_pushcclosure because this has to do +/// extra magic to allow passing C++ functions instead of plain C functions. +/// +/// \param function The C++ function to be pushed as a closure. +/// \param nvalues The number of upvalues that the function receives. +void +lutok::state::push_cxx_closure(cxx_function function, const int nvalues) +{ + cxx_function *data = static_cast< cxx_function* >( + lua_newuserdata(_pimpl->lua_state, sizeof(cxx_function))); + *data = function; + lua_pushcclosure(_pimpl->lua_state, cxx_closure_trampoline, nvalues + 1); +} + + +/// Wrapper around lua_pushcfunction. +/// +/// This is not a pure wrapper around lua_pushcfunction because this has to do +/// extra magic to allow passing C++ functions instead of plain C functions. +/// +/// \param function The C++ function to be pushed. +void +lutok::state::push_cxx_function(cxx_function function) +{ + cxx_function *data = static_cast< cxx_function* >( + lua_newuserdata(_pimpl->lua_state, sizeof(cxx_function))); + *data = function; + lua_pushcclosure(_pimpl->lua_state, cxx_function_trampoline, 1); +} + + +/// Wrapper around lua_pushinteger. +/// +/// \param value The second parameter to lua_pushinteger. +void +lutok::state::push_integer(const int value) +{ + lua_pushinteger(_pimpl->lua_state, value); +} + + +/// Wrapper around lua_pushnil. +void +lutok::state::push_nil(void) +{ + lua_pushnil(_pimpl->lua_state); +} + + +/// Wrapper around lua_pushstring. +/// +/// \param str The second parameter to lua_pushstring. +/// +/// \warning Terminates execution if there is not enough memory. +void +lutok::state::push_string(const std::string& str) +{ + lua_pushstring(_pimpl->lua_state, str.c_str()); +} + + +/// Wrapper around lua_pushvalue. +/// +/// \param index The second parameter to lua_pushvalue. +void +lutok::state::push_value(const int index) +{ + lua_pushvalue(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_rawget. +/// +/// \param index The second parameter to lua_rawget. +void +lutok::state::raw_get(const int index) +{ + lua_rawget(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_rawset. +/// +/// \param index The second parameter to lua_rawset. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +void +lutok::state::raw_set(const int index) +{ + lua_rawset(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_setglobal. +/// +/// \param name The second parameter to lua_setglobal. +/// +/// \throw api_error If lua_setglobal fails. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +void +lutok::state::set_global(const std::string& name) +{ + lua_pushcfunction(_pimpl->lua_state, protected_setglobal); + lua_pushstring(_pimpl->lua_state, name.c_str()); + lua_pushvalue(_pimpl->lua_state, -3); + if (lua_pcall(_pimpl->lua_state, 2, 0, 0) != 0) + throw lutok::api_error::from_stack(*this, "lua_setglobal"); + lua_pop(_pimpl->lua_state, 1); +} + + +/// Wrapper around lua_setmetatable. +/// +/// \param index The second parameter to lua_setmetatable. +void +lutok::state::set_metatable(const int index) +{ + lua_setmetatable(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_settable. +/// +/// \param index The second parameter to lua_settable. +/// +/// \throw api_error If lua_settable fails. +/// +/// \warning Terminates execution if there is not enough memory to manipulate +/// the Lua stack. +void +lutok::state::set_table(const int index) +{ + lua_pushcfunction(_pimpl->lua_state, protected_settable); + lua_pushvalue(_pimpl->lua_state, index < 0 ? index - 1 : index); + lua_pushvalue(_pimpl->lua_state, -4); + lua_pushvalue(_pimpl->lua_state, -4); + if (lua_pcall(_pimpl->lua_state, 3, 0, 0) != 0) + throw lutok::api_error::from_stack(*this, "lua_settable"); + lua_pop(_pimpl->lua_state, 2); +} + + +/// Wrapper around lua_toboolean. +/// +/// \param index The second parameter to lua_toboolean. +/// +/// \return The return value of lua_toboolean. +bool +lutok::state::to_boolean(const int index) +{ + assert(is_boolean(index)); + return lua_toboolean(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_tointeger. +/// +/// \param index The second parameter to lua_tointeger. +/// +/// \return The return value of lua_tointeger. +long +lutok::state::to_integer(const int index) +{ + assert(is_number(index)); + return lua_tointeger(_pimpl->lua_state, index); +} + + +/// Wrapper around lua_touserdata. +/// +/// This is internal. The public type-safe interface of this method should be +/// used instead. +/// +/// \param index The second parameter to lua_touserdata. +/// +/// \return The return value of lua_touserdata. +/// +/// \warning Terminates execution if there is not enough memory. +void* +lutok::state::to_userdata_voidp(const int index) +{ + return lua_touserdata(_pimpl->lua_state, index); +} + + + +/// Wrapper around lua_tostring. +/// +/// \param index The second parameter to lua_tostring. +/// +/// \return The return value of lua_tostring. +/// +/// \warning Terminates execution if there is not enough memory. +std::string +lutok::state::to_string(const int index) +{ + assert(is_string(index)); + const char *raw_string = lua_tostring(_pimpl->lua_state, index); + // Note that the creation of a string object below (explicit for clarity) + // implies that the raw string is duplicated and, henceforth, the string is + // safe even if the corresponding element is popped from the Lua stack. + return std::string(raw_string); +} + + +/// Wrapper around lua_upvalueindex. +/// +/// \param index The first parameter to lua_upvalueindex. +/// +/// \return The return value of lua_upvalueindex. +int +lutok::state::upvalue_index(const int index) +{ + return lua_upvalueindex(index); +} + + +/// Gets the internal lua_State object. +/// +/// \return The raw Lua state. This is returned as a void pointer to prevent +/// including the lua.hpp header file from our public interface. The only way +/// to call this method is by using the c_gate module, and c_gate takes care of +/// casting this object to the appropriate type. +void* +lutok::state::raw_state(void) +{ + return _pimpl->lua_state; +} diff --git a/state.hpp b/state.hpp new file mode 100644 index 000000000000..377aa062d81c --- /dev/null +++ b/state.hpp @@ -0,0 +1,145 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file state.hpp +/// Provides the state wrapper class for the Lua C state. + +#if !defined(LUTOK_STATE_HPP) +#define LUTOK_STATE_HPP + +#include + +#if defined(_LIBCPP_VERSION) || __cplusplus >= 201103L +#include +#else +#include +#endif + +namespace lutok { + + +class debug; +class state; + + +/// The type of a C++ function that can be bound into Lua. +/// +/// Functions of this type are free to raise exceptions. These will not +/// propagate into the Lua C API. However, any such exceptions will be reported +/// as a Lua error and their type will be lost. +typedef int (*cxx_function)(state&); + + +/// Stack index constant pointing to the registry table. +extern const int registry_index; + + +/// A RAII model for the Lua state. +/// +/// This class holds the state of the Lua interpreter during its existence and +/// provides wrappers around several Lua library functions that operate on such +/// state. +/// +/// These wrapper functions differ from the C versions in that they use the +/// implicit state hold by the class, they use C++ types where appropriate and +/// they use exceptions to report errors. +/// +/// The wrappers intend to be as lightweight as possible but, in some +/// situations, they are pretty complex because they need to do extra work to +/// capture the errors reported by the Lua C API. We prefer having fine-grained +/// error control rather than efficiency, so this is OK. +class state { + struct impl; + + /// Pointer to the shared internal implementation. +#if defined(_LIBCPP_VERSION) || __cplusplus >= 201103L + std::shared_ptr< impl > _pimpl; +#else + std::tr1::shared_ptr< impl > _pimpl; +#endif + + void* new_userdata_voidp(const size_t); + void* to_userdata_voidp(const int); + + friend class state_c_gate; + explicit state(void*); + void* raw_state(void); + +public: + state(void); + ~state(void); + + void close(void); + void get_global(const std::string&); + void get_global_table(void); + bool get_metafield(const int, const std::string&); + bool get_metatable(const int); + void get_table(const int); + int get_top(void); + void insert(const int); + bool is_boolean(const int); + bool is_function(const int); + bool is_nil(const int); + bool is_number(const int); + bool is_string(const int); + bool is_table(const int); + bool is_userdata(const int); + void load_file(const std::string&); + void load_string(const std::string&); + void new_table(void); + template< typename Type > Type* new_userdata(void); + bool next(const int); + void open_all(void); + void open_base(void); + void open_string(void); + void open_table(void); + void pcall(const int, const int, const int); + void pop(const int); + void push_boolean(const bool); + void push_cxx_closure(cxx_function, const int); + void push_cxx_function(cxx_function); + void push_integer(const int); + void push_nil(void); + void push_string(const std::string&); + void push_value(const int); + void raw_get(const int); + void raw_set(const int); + void set_global(const std::string&); + void set_metatable(const int); + void set_table(const int); + bool to_boolean(const int); + long to_integer(const int); + template< typename Type > Type* to_userdata(const int); + std::string to_string(const int); + int upvalue_index(const int); +}; + + +} // namespace lutok + +#endif // !defined(LUTOK_STATE_HPP) diff --git a/state.ipp b/state.ipp new file mode 100644 index 000000000000..76f47a643194 --- /dev/null +++ b/state.ipp @@ -0,0 +1,67 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#if !defined(LUTOK_STATE_IPP) +#define LUTOK_STATE_IPP + +#include + +namespace lutok { + + +/// Wrapper around lua_newuserdata. +/// +/// This allocates an object as big as the size of the provided Type. +/// +/// \return The pointer to the allocated userdata object. +/// +/// \warning Terminates execution if there is not enough memory. +template< typename Type > +Type* +state::new_userdata(void) +{ + return static_cast< Type* >(new_userdata_voidp(sizeof(Type))); +} + + +/// Wrapper around lua_touserdata. +/// +/// \param index The second parameter to lua_touserdata. +/// +/// \return The return value of lua_touserdata. +template< typename Type > +Type* +state::to_userdata(const int index) +{ + return static_cast< Type* >(to_userdata_voidp(index)); +} + + +} // namespace lutok + +#endif // !defined(LUTOK_STATE_IPP) diff --git a/state_test.cpp b/state_test.cpp new file mode 100644 index 000000000000..40c70a6d4b38 --- /dev/null +++ b/state_test.cpp @@ -0,0 +1,1164 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "state.ipp" + +#include +#include +#include +#include + +#include +#include + +#include "c_gate.hpp" +#include "exceptions.hpp" +#include "test_utils.hpp" + + +// A note about the lutok::state tests. +// +// The methods of lutok::state are, in general, thin wrappers around the +// corresponding Lua C API methods. The tests below are simple unit tests that +// ensure that these functions just delegate the calls to the Lua library. We +// do not intend to test the validity of the methods themselves (that's the +// job of the Lua authors). That said, we test those conditions we rely on, +// such as the reporting of errors and the default values to the API. +// +// Lastly, for every test case that stresses a single lutok::state method, we +// only call that method directly. All other Lua state manipulation operations +// are performed by means of direct calls to the Lua C API. This is to ensure +// that the wrapped methods are really talking to Lua. + + +namespace { + + +/// Checks if a symbol is available. +/// +/// \param state The Lua state. +/// \param symbol The symbol to check for. +/// +/// \return True if the symbol is defined, false otherwise. +static bool +is_available(lutok::state& state, const char* symbol) +{ + luaL_loadstring(raw(state), (std::string("return ") + symbol).c_str()); + const bool ok = (lua_pcall(raw(state), 0, 1, 0) == 0 && + !lua_isnil(raw(state), -1)); + lua_pop(raw(state), 1); + std::cout << "Symbol " << symbol << (ok ? " found\n" : " not found\n"); + return ok; +} + + +/// Checks that no modules are present or that only one has been loaded. +/// +/// \post The test case terminates if there is any module present when expected +/// is empty or if there two modules loaded when expected is defined. +/// +/// \param state The Lua state. +/// \param expected The module to expect. Empty if no modules are allowed. +static void +check_modules(lutok::state& state, const std::string& expected) +{ + std::cout << "Checking loaded modules" << + (expected.empty() ? "" : (" (" + expected + " expected)")) << "\n"; + ATF_REQUIRE(!((expected == "base") ^ (is_available(state, "assert")))); + ATF_REQUIRE(!((expected == "string") ^ + (is_available(state, "string.byte")))); + ATF_REQUIRE(!((expected == "table") ^ + (is_available(state, "table.concat")))); +} + + +/// A C closure that returns its two integral upvalues. +/// +/// \post stack(-2) contains the first upvalue. +/// \post stack(-1) contains the second upvalue. +/// +/// \param raw_state The raw Lua state. +/// +/// \return The number of result values, i.e. 2. +static int +c_get_upvalues(lua_State* raw_state) +{ + lutok::state state = lutok::state_c_gate::connect(raw_state); + const int i1 = lua_tointeger(raw_state, state.upvalue_index(1)); + const int i2 = lua_tointeger(raw_state, state.upvalue_index(2)); + lua_pushinteger(raw_state, i1); + lua_pushinteger(raw_state, i2); + return 2; +} + + +/// A custom C++ multiply function with one of its factors on its closure. +/// +/// \pre stack(-1) contains the second factor. +/// \post stack(-1) contains the product of the two input factors. +/// +/// \param state The Lua state. +/// +/// \return The number of result values, i.e. 1. +static int +cxx_multiply_closure(lutok::state& state) +{ + const int f1 = lua_tointeger(raw(state), lua_upvalueindex(1)); + const int f2 = lua_tointeger(raw(state), -1); + lua_pushinteger(raw(state), f1 * f2); + return 1; +} + + +/// A custom C++ integral division function for Lua. +/// +/// \pre stack(-2) contains the dividend. +/// \pre stack(-1) contains the divisor. +/// \post stack(-2) contains the quotient of the division. +/// \post stack(-1) contains the remainder of the division. +/// +/// \param state The Lua state. +/// +/// \return The number of result values, i.e. 1. +/// +/// \throw std::runtime_error If the divisor is zero. +/// \throw std::string If the dividend or the divisor are negative. This is an +/// exception not derived from std::exception on purpose to ensure that the +/// C++ wrapping correctly captures any exception regardless of its type. +static int +cxx_divide(lutok::state& state) +{ + const int dividend = state.to_integer(-2); + const int divisor = state.to_integer(-1); + if (divisor == 0) + throw std::runtime_error("Divisor is 0"); + if (dividend < 0 || divisor < 0) + throw std::string("Cannot divide negative numbers"); + state.push_integer(dividend / divisor); + state.push_integer(dividend % divisor); + return 2; +} + + +/// A Lua function that raises a very long error message. +/// +/// \pre stack(-1) contains the length of the message to construct. +/// +/// \param state The Lua state. +/// +/// \return Never returns. +/// +/// \throw std::runtime_error Unconditionally, with an error message formed by +/// the repetition of 'A' as many times as requested. +static int +raise_long_error(lutok::state& state) +{ + const int length = state.to_integer(-1); + throw std::runtime_error(std::string(length, 'A').c_str()); +} + + +} // anonymous namespace + + +ATF_TEST_CASE_WITHOUT_HEAD(close); +ATF_TEST_CASE_BODY(close) +{ + lutok::state state; + state.close(); + // The destructor for state will run now. If it does a second close, we may + // crash, so let's see if we don't. +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_global__ok); +ATF_TEST_CASE_BODY(get_global__ok) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "test_variable = 3") == 0); + state.get_global("test_variable"); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_global__undefined); +ATF_TEST_CASE_BODY(get_global__undefined) +{ + lutok::state state; + state.get_global("test_variable"); + ATF_REQUIRE(lua_isnil(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_global_table); +ATF_TEST_CASE_BODY(get_global_table) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "global_variable = 'hello'") == 0); + state.get_global_table(); + lua_pushstring(raw(state), "global_variable"); + lua_gettable(raw(state), -2); + ATF_REQUIRE(lua_isstring(raw(state), -1)); + ATF_REQUIRE(std::strcmp("hello", lua_tostring(raw(state), -1)) == 0); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_metafield__ok); +ATF_TEST_CASE_BODY(get_metafield__ok) +{ + lutok::state state; + luaL_openlibs(raw(state)); + ATF_REQUIRE(luaL_dostring(raw(state), "meta = { foo = 567 }; " + "t = {}; setmetatable(t, meta)") == 0); + lua_getglobal(raw(state), "t"); + ATF_REQUIRE(state.get_metafield(-1, "foo")); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(567, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_metafield__undefined); +ATF_TEST_CASE_BODY(get_metafield__undefined) +{ + lutok::state state; + luaL_openlibs(raw(state)); + ATF_REQUIRE(luaL_dostring(raw(state), "meta = { foo = 567 }; " + "t = {}; setmetatable(t, meta)") == 0); + lua_getglobal(raw(state), "t"); + ATF_REQUIRE(!state.get_metafield(-1, "bar")); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_metatable__ok); +ATF_TEST_CASE_BODY(get_metatable__ok) +{ + lutok::state state; + luaL_openlibs(raw(state)); + ATF_REQUIRE(luaL_dostring(raw(state), "meta = { foo = 567 }; " + "t = {}; setmetatable(t, meta)") == 0); + lua_getglobal(raw(state), "t"); + lua_pushinteger(raw(state), 5555); + ATF_REQUIRE(state.get_metatable(-2)); + ATF_REQUIRE(lua_istable(raw(state), -1)); + lua_pushstring(raw(state), "foo"); + lua_gettable(raw(state), -2); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(567, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 4); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_metatable__undefined); +ATF_TEST_CASE_BODY(get_metatable__undefined) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "t = {}") == 0); + lua_getglobal(raw(state), "t"); + ATF_REQUIRE(!state.get_metatable(-1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_table__ok); +ATF_TEST_CASE_BODY(get_table__ok) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "t = { a = 1, bar = 234 }") == 0); + lua_getglobal(raw(state), "t"); + lua_pushstring(raw(state), "bar"); + state.get_table(-2); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(234, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_table__nil); +ATF_TEST_CASE_BODY(get_table__nil) +{ + lutok::state state; + lua_pushnil(raw(state)); + lua_pushinteger(raw(state), 1); + REQUIRE_API_ERROR("lua_gettable", state.get_table(-2)); + ATF_REQUIRE_EQ(2, lua_gettop(raw(state))); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_table__unknown_index); +ATF_TEST_CASE_BODY(get_table__unknown_index) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), + "the_table = { foo = 1, bar = 2 }") == 0); + lua_getglobal(raw(state), "the_table"); + lua_pushstring(raw(state), "baz"); + state.get_table(-2); + ATF_REQUIRE(lua_isnil(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(get_top); +ATF_TEST_CASE_BODY(get_top) +{ + lutok::state state; + ATF_REQUIRE_EQ(0, state.get_top()); + lua_pushinteger(raw(state), 3); + ATF_REQUIRE_EQ(1, state.get_top()); + lua_pushinteger(raw(state), 3); + ATF_REQUIRE_EQ(2, state.get_top()); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(insert); +ATF_TEST_CASE_BODY(insert) +{ + lutok::state state; + lua_pushinteger(raw(state), 1); + lua_pushinteger(raw(state), 2); + lua_pushinteger(raw(state), 3); + lua_pushinteger(raw(state), 4); + state.insert(-3); + ATF_REQUIRE_EQ(3, lua_tointeger(raw(state), -1)); + ATF_REQUIRE_EQ(2, lua_tointeger(raw(state), -2)); + ATF_REQUIRE_EQ(4, lua_tointeger(raw(state), -3)); + ATF_REQUIRE_EQ(1, lua_tointeger(raw(state), -4)); + lua_pop(raw(state), 4); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_boolean__empty); +ATF_TEST_CASE_BODY(is_boolean__empty) +{ + lutok::state state; + ATF_REQUIRE(!state.is_boolean(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_boolean__ok); +ATF_TEST_CASE_BODY(is_boolean__ok) +{ + lutok::state state; + lua_pushboolean(raw(state), 1); + ATF_REQUIRE(state.is_boolean(-1)); + lua_pushinteger(raw(state), 5); + ATF_REQUIRE(!state.is_boolean(-1)); + ATF_REQUIRE(state.is_boolean(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_function__empty); +ATF_TEST_CASE_BODY(is_function__empty) +{ + lutok::state state; + ATF_REQUIRE(!state.is_function(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_function__ok); +ATF_TEST_CASE_BODY(is_function__ok) +{ + lutok::state state; + luaL_dostring(raw(state), "function my_func(a, b) return a + b; end"); + + lua_getglobal(raw(state), "my_func"); + ATF_REQUIRE(state.is_function(-1)); + lua_pushinteger(raw(state), 5); + ATF_REQUIRE(!state.is_function(-1)); + ATF_REQUIRE(state.is_function(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_nil__empty); +ATF_TEST_CASE_BODY(is_nil__empty) +{ + lutok::state state; + ATF_REQUIRE(state.is_nil(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_nil__ok); +ATF_TEST_CASE_BODY(is_nil__ok) +{ + lutok::state state; + lua_pushnil(raw(state)); + ATF_REQUIRE(state.is_nil(-1)); + lua_pushinteger(raw(state), 5); + ATF_REQUIRE(!state.is_nil(-1)); + ATF_REQUIRE(state.is_nil(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_number__empty); +ATF_TEST_CASE_BODY(is_number__empty) +{ + lutok::state state; + ATF_REQUIRE(!state.is_number(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_number__ok); +ATF_TEST_CASE_BODY(is_number__ok) +{ + lutok::state state; + lua_pushnil(raw(state)); + ATF_REQUIRE(!state.is_number(-1)); + lua_pushinteger(raw(state), 5); + ATF_REQUIRE(state.is_number(-1)); + ATF_REQUIRE(!state.is_number(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_string__empty); +ATF_TEST_CASE_BODY(is_string__empty) +{ + lutok::state state; + ATF_REQUIRE(!state.is_string(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_string__ok); +ATF_TEST_CASE_BODY(is_string__ok) +{ + lutok::state state; + lua_pushinteger(raw(state), 3); + ATF_REQUIRE(state.is_string(-1)); + lua_pushnil(raw(state)); + ATF_REQUIRE(!state.is_string(-1)); + ATF_REQUIRE(state.is_string(-2)); + lua_pushstring(raw(state), "foo"); + ATF_REQUIRE(state.is_string(-1)); + ATF_REQUIRE(!state.is_string(-2)); + ATF_REQUIRE(state.is_string(-3)); + lua_pop(raw(state), 3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_table__empty); +ATF_TEST_CASE_BODY(is_table__empty) +{ + lutok::state state; + ATF_REQUIRE(!state.is_table(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_table__ok); +ATF_TEST_CASE_BODY(is_table__ok) +{ + lutok::state state; + luaL_dostring(raw(state), "t = {3, 4, 5}"); + + lua_pushstring(raw(state), "foo"); + ATF_REQUIRE(!state.is_table(-1)); + lua_getglobal(raw(state), "t"); + ATF_REQUIRE(state.is_table(-1)); + ATF_REQUIRE(!state.is_table(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_userdata__empty); +ATF_TEST_CASE_BODY(is_userdata__empty) +{ + lutok::state state; + ATF_REQUIRE(!state.is_userdata(-1)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(is_userdata__ok); +ATF_TEST_CASE_BODY(is_userdata__ok) +{ + lutok::state state; + + lua_pushstring(raw(state), "foo"); + ATF_REQUIRE(!state.is_userdata(-1)); + lua_newuserdata(raw(state), 543); + ATF_REQUIRE(state.is_userdata(-1)); + ATF_REQUIRE(!state.is_userdata(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(load_file__ok); +ATF_TEST_CASE_BODY(load_file__ok) +{ + std::ofstream output("test.lua"); + output << "in_the_file = \"oh yes\"\n"; + output.close(); + + lutok::state state; + state.load_file("test.lua"); + ATF_REQUIRE(lua_pcall(raw(state), 0, 0, 0) == 0); + lua_getglobal(raw(state), "in_the_file"); + ATF_REQUIRE(std::strcmp("oh yes", lua_tostring(raw(state), -1)) == 0); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(load_file__api_error); +ATF_TEST_CASE_BODY(load_file__api_error) +{ + std::ofstream output("test.lua"); + output << "I have a bad syntax! Wohoo!\n"; + output.close(); + + lutok::state state; + REQUIRE_API_ERROR("luaL_loadfile", state.load_file("test.lua")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(load_file__file_not_found_error); +ATF_TEST_CASE_BODY(load_file__file_not_found_error) +{ + lutok::state state; + ATF_REQUIRE_THROW_RE(lutok::file_not_found_error, "missing.lua", + state.load_file("missing.lua")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(load_string__ok); +ATF_TEST_CASE_BODY(load_string__ok) +{ + lutok::state state; + state.load_string("return 2 + 3"); + ATF_REQUIRE(lua_pcall(raw(state), 0, 1, 0) == 0); + ATF_REQUIRE_EQ(5, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(load_string__fail); +ATF_TEST_CASE_BODY(load_string__fail) +{ + lutok::state state; + REQUIRE_API_ERROR("luaL_loadstring", state.load_string("-")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(new_table); +ATF_TEST_CASE_BODY(new_table) +{ + lutok::state state; + state.new_table(); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE(lua_istable(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(new_userdata); +ATF_TEST_CASE_BODY(new_userdata) +{ + lutok::state state; + int* pointer = state.new_userdata< int >(); + *pointer = 1234; + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE(lua_isuserdata(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(next__empty); +ATF_TEST_CASE_BODY(next__empty) +{ + lutok::state state; + luaL_dostring(raw(state), "t = {}"); + + lua_getglobal(raw(state), "t"); + lua_pushstring(raw(state), "this is a dummy value"); + lua_pushnil(raw(state)); + ATF_REQUIRE(!state.next(-3)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(next__many); +ATF_TEST_CASE_BODY(next__many) +{ + lutok::state state; + luaL_dostring(raw(state), "t = {}; t[1] = 100; t[2] = 200"); + + lua_getglobal(raw(state), "t"); + lua_pushnil(raw(state)); + + ATF_REQUIRE(state.next(-2)); + ATF_REQUIRE_EQ(3, lua_gettop(raw(state))); + ATF_REQUIRE(lua_isnumber(raw(state), -2)); + ATF_REQUIRE_EQ(1, lua_tointeger(raw(state), -2)); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(100, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); + + ATF_REQUIRE(state.next(-2)); + ATF_REQUIRE_EQ(3, lua_gettop(raw(state))); + ATF_REQUIRE(lua_isnumber(raw(state), -2)); + ATF_REQUIRE_EQ(2, lua_tointeger(raw(state), -2)); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(200, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); + + ATF_REQUIRE(!state.next(-2)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(open_base); +ATF_TEST_CASE_BODY(open_base) +{ + lutok::state state; + check_modules(state, ""); + state.open_base(); + check_modules(state, "base"); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(open_all); +ATF_TEST_CASE_BODY(open_all) +{ + lutok::state state; + check_modules(state, ""); + state.open_all(); + // Best-effort attempt at looking for a bunch of possible modules. + ATF_REQUIRE(is_available(state, "assert")); + ATF_REQUIRE(is_available(state, "debug.getinfo")); + ATF_REQUIRE(is_available(state, "package.path")); + ATF_REQUIRE(is_available(state, "string.byte")); + ATF_REQUIRE(is_available(state, "table.concat")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(open_string); +ATF_TEST_CASE_BODY(open_string) +{ + lutok::state state; + check_modules(state, ""); + state.open_string(); + check_modules(state, "string"); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(open_table); +ATF_TEST_CASE_BODY(open_table) +{ + lutok::state state; + check_modules(state, ""); + state.open_table(); + check_modules(state, "table"); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(pcall__ok); +ATF_TEST_CASE_BODY(pcall__ok) +{ + lutok::state state; + luaL_loadstring(raw(state), "function mul(a, b) return a * b; end"); + state.pcall(0, 0, 0); + state.get_global_table(); + lua_pushstring(raw(state), "mul"); + lua_gettable(raw(state), -2); + lua_pushinteger(raw(state), 3); + lua_pushinteger(raw(state), 5); + state.pcall(2, 1, 0); + ATF_REQUIRE_EQ(15, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(pcall__fail); +ATF_TEST_CASE_BODY(pcall__fail) +{ + lutok::state state; + lua_pushnil(raw(state)); + REQUIRE_API_ERROR("lua_pcall", state.pcall(0, 0, 0)); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(pop__one); +ATF_TEST_CASE_BODY(pop__one) +{ + lutok::state state; + lua_pushinteger(raw(state), 10); + lua_pushinteger(raw(state), 20); + lua_pushinteger(raw(state), 30); + state.pop(1); + ATF_REQUIRE_EQ(2, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(20, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(pop__many); +ATF_TEST_CASE_BODY(pop__many) +{ + lutok::state state; + lua_pushinteger(raw(state), 10); + lua_pushinteger(raw(state), 20); + lua_pushinteger(raw(state), 30); + state.pop(2); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(10, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_boolean); +ATF_TEST_CASE_BODY(push_boolean) +{ + lutok::state state; + state.push_boolean(true); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE(lua_toboolean(raw(state), -1)); + state.push_boolean(false); + ATF_REQUIRE_EQ(2, lua_gettop(raw(state))); + ATF_REQUIRE(!lua_toboolean(raw(state), -1)); + ATF_REQUIRE(lua_toboolean(raw(state), -2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_cxx_closure); +ATF_TEST_CASE_BODY(push_cxx_closure) +{ + lutok::state state; + state.push_integer(15); + state.push_cxx_closure(cxx_multiply_closure, 1); + lua_setglobal(raw(state), "cxx_multiply_closure"); + + ATF_REQUIRE(luaL_dostring(raw(state), + "return cxx_multiply_closure(10)") == 0); + ATF_REQUIRE_EQ(150, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_cxx_function__ok); +ATF_TEST_CASE_BODY(push_cxx_function__ok) +{ + lutok::state state; + state.push_cxx_function(cxx_divide); + lua_setglobal(raw(state), "cxx_divide"); + + ATF_REQUIRE(luaL_dostring(raw(state), "return cxx_divide(17, 3)") == 0); + ATF_REQUIRE_EQ(5, lua_tointeger(raw(state), -2)); + ATF_REQUIRE_EQ(2, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_cxx_function__fail_exception); +ATF_TEST_CASE_BODY(push_cxx_function__fail_exception) +{ + lutok::state state; + state.push_cxx_function(cxx_divide); + lua_setglobal(raw(state), "cxx_divide"); + + ATF_REQUIRE(luaL_dostring(raw(state), "return cxx_divide(15, 0)") != 0); + ATF_REQUIRE_MATCH("Divisor is 0", lua_tostring(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_cxx_function__fail_anything); +ATF_TEST_CASE_BODY(push_cxx_function__fail_anything) +{ + lutok::state state; + state.push_cxx_function(cxx_divide); + lua_setglobal(raw(state), "cxx_divide"); + + ATF_REQUIRE(luaL_dostring(raw(state), "return cxx_divide(-3, -1)") != 0); + ATF_REQUIRE_MATCH("Unhandled exception", lua_tostring(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_cxx_function__fail_overflow); +ATF_TEST_CASE_BODY(push_cxx_function__fail_overflow) +{ + lutok::state state; + state.push_cxx_function(raise_long_error); + lua_setglobal(raw(state), "fail"); + + ATF_REQUIRE(luaL_dostring(raw(state), "return fail(900)") != 0); + ATF_REQUIRE_MATCH(std::string(900, 'A'), lua_tostring(raw(state), -1)); + lua_pop(raw(state), 1); + + ATF_REQUIRE(luaL_dostring(raw(state), "return fail(8192)") != 0); + ATF_REQUIRE_MATCH(std::string(900, 'A'), lua_tostring(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_integer); +ATF_TEST_CASE_BODY(push_integer) +{ + lutok::state state; + state.push_integer(12); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(12, lua_tointeger(raw(state), -1)); + state.push_integer(34); + ATF_REQUIRE_EQ(2, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(34, lua_tointeger(raw(state), -1)); + ATF_REQUIRE_EQ(12, lua_tointeger(raw(state), -2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_nil); +ATF_TEST_CASE_BODY(push_nil) +{ + lutok::state state; + state.push_nil(); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE(lua_isnil(raw(state), -1)); + state.push_integer(34); + ATF_REQUIRE_EQ(2, lua_gettop(raw(state))); + ATF_REQUIRE(!lua_isnil(raw(state), -1)); + ATF_REQUIRE(lua_isnil(raw(state), -2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_string); +ATF_TEST_CASE_BODY(push_string) +{ + lutok::state state; + + { + std::string str = "first"; + state.push_string(str); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(std::string("first"), lua_tostring(raw(state), -1)); + str = "second"; + state.push_string(str); + } + ATF_REQUIRE_EQ(2, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(std::string("second"), lua_tostring(raw(state), -1)); + ATF_REQUIRE_EQ(std::string("first"), lua_tostring(raw(state), -2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(push_value); +ATF_TEST_CASE_BODY(push_value) +{ + lutok::state state; + + lua_pushinteger(raw(state), 10); + lua_pushinteger(raw(state), 20); + state.push_value(-2); + ATF_REQUIRE_EQ(3, lua_gettop(raw(state))); + ATF_REQUIRE_EQ(10, lua_tointeger(raw(state), -1)); + ATF_REQUIRE_EQ(20, lua_tointeger(raw(state), -2)); + ATF_REQUIRE_EQ(10, lua_tointeger(raw(state), -3)); + lua_pop(raw(state), 3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(raw_get); +ATF_TEST_CASE_BODY(raw_get) +{ + lutok::state state; + + luaL_openlibs(raw(state)); + ATF_REQUIRE(luaL_dostring( + raw(state), "t = {foo=123} ; setmetatable(t, {__index=1})") == 0); + lua_getglobal(raw(state), "t"); + lua_pushinteger(raw(state), 9876); + lua_pushstring(raw(state), "foo"); + state.raw_get(-3); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(123, lua_tointeger(raw(state), -1)); + ATF_REQUIRE_EQ(9876, lua_tointeger(raw(state), -2)); + lua_pop(raw(state), 3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(raw_set); +ATF_TEST_CASE_BODY(raw_set) +{ + lutok::state state; + + luaL_openlibs(raw(state)); + ATF_REQUIRE(luaL_dostring( + raw(state), "t = {} ; setmetatable(t, {__newindex=1})") == 0); + lua_getglobal(raw(state), "t"); + lua_pushinteger(raw(state), 876); + lua_pushstring(raw(state), "foo"); + lua_pushinteger(raw(state), 345); + state.raw_set(-4); + ATF_REQUIRE(luaL_dostring(raw(state), "return t.foo") == 0); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(345, lua_tointeger(raw(state), -1)); + ATF_REQUIRE_EQ(876, lua_tointeger(raw(state), -2)); + lua_pop(raw(state), 3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(registry_index); +ATF_TEST_CASE_BODY(registry_index) +{ + lutok::state state; + lua_pushvalue(raw(state), lutok::registry_index); + lua_pushstring(raw(state), "custom_variable"); + lua_pushstring(raw(state), "custom value"); + lua_settable(raw(state), -3); + lua_pop(raw(state), 1); + ATF_REQUIRE(luaL_dostring(raw(state), + "return custom_variable == nil") == 0); + ATF_REQUIRE(lua_isboolean(raw(state), -1)); + ATF_REQUIRE(lua_toboolean(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_global); +ATF_TEST_CASE_BODY(set_global) +{ + lutok::state state; + lua_pushinteger(raw(state), 3); + state.set_global("test_variable"); + ATF_REQUIRE(luaL_dostring(raw(state), "return test_variable + 1") == 0); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(4, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_metatable); +ATF_TEST_CASE_BODY(set_metatable) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring( + raw(state), + "mt = {}\n" + "mt.__add = function(a, b) return a[1] + b end\n" + "numbers = {}\n" + "numbers[1] = 5\n") == 0); + + lua_getglobal(raw(state), "numbers"); + lua_pushinteger(raw(state), 1234); + lua_getglobal(raw(state), "mt"); + state.set_metatable(-3); + lua_pop(raw(state), 2); + + ATF_REQUIRE(luaL_dostring(raw(state), "return numbers + 2") == 0); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(7, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_table__ok); +ATF_TEST_CASE_BODY(set_table__ok) +{ + lutok::state state; + ATF_REQUIRE(luaL_dostring(raw(state), "t = { a = 1, bar = 234 }") == 0); + lua_getglobal(raw(state), "t"); + + lua_pushstring(raw(state), "bar"); + lua_pushstring(raw(state), "baz"); + state.set_table(-3); + ATF_REQUIRE_EQ(1, lua_gettop(raw(state))); + + lua_pushstring(raw(state), "a"); + lua_gettable(raw(state), -2); + ATF_REQUIRE(lua_isnumber(raw(state), -1)); + ATF_REQUIRE_EQ(1, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 1); + + lua_pushstring(raw(state), "bar"); + lua_gettable(raw(state), -2); + ATF_REQUIRE(lua_isstring(raw(state), -1)); + ATF_REQUIRE_EQ(std::string("baz"), lua_tostring(raw(state), -1)); + lua_pop(raw(state), 1); + + lua_pop(raw(state), 1); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_table__nil); +ATF_TEST_CASE_BODY(set_table__nil) +{ + lutok::state state; + lua_pushnil(raw(state)); + lua_pushinteger(raw(state), 1); + lua_pushinteger(raw(state), 2); + REQUIRE_API_ERROR("lua_settable", state.set_table(-3)); + lua_pop(raw(state), 3); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(to_boolean); +ATF_TEST_CASE_BODY(to_boolean) +{ + lutok::state state; + lua_pushboolean(raw(state), 0); + lua_pushboolean(raw(state), 1); + ATF_REQUIRE(!state.to_boolean(-2)); + ATF_REQUIRE(state.to_boolean(-1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(to_integer); +ATF_TEST_CASE_BODY(to_integer) +{ + lutok::state state; + lua_pushinteger(raw(state), 12); + lua_pushstring(raw(state), "foobar"); + ATF_REQUIRE_EQ(12, state.to_integer(-2)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(to_string); +ATF_TEST_CASE_BODY(to_string) +{ + lutok::state state; + lua_pushstring(raw(state), "foobar"); + lua_pushinteger(raw(state), 12); + ATF_REQUIRE_EQ("foobar", state.to_string(-2)); + ATF_REQUIRE_EQ("12", state.to_string(-1)); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(to_userdata); +ATF_TEST_CASE_BODY(to_userdata) +{ + lutok::state state; + { + int* pointer = static_cast< int* >( + lua_newuserdata(raw(state), sizeof(int))); + *pointer = 987; + } + + lua_pushinteger(raw(state), 3); + int* pointer = state.to_userdata< int >(-2); + ATF_REQUIRE_EQ(987, *pointer); + lua_pop(raw(state), 2); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(upvalue_index); +ATF_TEST_CASE_BODY(upvalue_index) +{ + lutok::state state; + lua_pushinteger(raw(state), 25); + lua_pushinteger(raw(state), 30); + lua_pushcclosure(raw(state), c_get_upvalues, 2); + lua_setglobal(raw(state), "c_get_upvalues"); + + ATF_REQUIRE(luaL_dostring(raw(state), + "return c_get_upvalues()") == 0); + ATF_REQUIRE_EQ(25, lua_tointeger(raw(state), -2)); + ATF_REQUIRE_EQ(30, lua_tointeger(raw(state), -1)); + lua_pop(raw(state), 2); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, close); + ATF_ADD_TEST_CASE(tcs, get_global__ok); + ATF_ADD_TEST_CASE(tcs, get_global__undefined); + ATF_ADD_TEST_CASE(tcs, get_global_table); + ATF_ADD_TEST_CASE(tcs, get_metafield__ok); + ATF_ADD_TEST_CASE(tcs, get_metafield__undefined); + ATF_ADD_TEST_CASE(tcs, get_metatable__ok); + ATF_ADD_TEST_CASE(tcs, get_metatable__undefined); + ATF_ADD_TEST_CASE(tcs, get_table__ok); + ATF_ADD_TEST_CASE(tcs, get_table__nil); + ATF_ADD_TEST_CASE(tcs, get_table__unknown_index); + ATF_ADD_TEST_CASE(tcs, get_top); + ATF_ADD_TEST_CASE(tcs, insert); + ATF_ADD_TEST_CASE(tcs, is_boolean__empty); + ATF_ADD_TEST_CASE(tcs, is_boolean__ok); + ATF_ADD_TEST_CASE(tcs, is_function__empty); + ATF_ADD_TEST_CASE(tcs, is_function__ok); + ATF_ADD_TEST_CASE(tcs, is_nil__empty); + ATF_ADD_TEST_CASE(tcs, is_nil__ok); + ATF_ADD_TEST_CASE(tcs, is_number__empty); + ATF_ADD_TEST_CASE(tcs, is_number__ok); + ATF_ADD_TEST_CASE(tcs, is_string__empty); + ATF_ADD_TEST_CASE(tcs, is_string__ok); + ATF_ADD_TEST_CASE(tcs, is_table__empty); + ATF_ADD_TEST_CASE(tcs, is_table__ok); + ATF_ADD_TEST_CASE(tcs, is_userdata__empty); + ATF_ADD_TEST_CASE(tcs, is_userdata__ok); + ATF_ADD_TEST_CASE(tcs, load_file__ok); + ATF_ADD_TEST_CASE(tcs, load_file__api_error); + ATF_ADD_TEST_CASE(tcs, load_file__file_not_found_error); + ATF_ADD_TEST_CASE(tcs, load_string__ok); + ATF_ADD_TEST_CASE(tcs, load_string__fail); + ATF_ADD_TEST_CASE(tcs, new_table); + ATF_ADD_TEST_CASE(tcs, new_userdata); + ATF_ADD_TEST_CASE(tcs, next__empty); + ATF_ADD_TEST_CASE(tcs, next__many); + ATF_ADD_TEST_CASE(tcs, open_all); + ATF_ADD_TEST_CASE(tcs, open_base); + ATF_ADD_TEST_CASE(tcs, open_string); + ATF_ADD_TEST_CASE(tcs, open_table); + ATF_ADD_TEST_CASE(tcs, pcall__ok); + ATF_ADD_TEST_CASE(tcs, pcall__fail); + ATF_ADD_TEST_CASE(tcs, pop__one); + ATF_ADD_TEST_CASE(tcs, pop__many); + ATF_ADD_TEST_CASE(tcs, push_boolean); + ATF_ADD_TEST_CASE(tcs, push_cxx_closure); + ATF_ADD_TEST_CASE(tcs, push_cxx_function__ok); + ATF_ADD_TEST_CASE(tcs, push_cxx_function__fail_exception); + ATF_ADD_TEST_CASE(tcs, push_cxx_function__fail_anything); + ATF_ADD_TEST_CASE(tcs, push_cxx_function__fail_overflow); + ATF_ADD_TEST_CASE(tcs, push_integer); + ATF_ADD_TEST_CASE(tcs, push_nil); + ATF_ADD_TEST_CASE(tcs, push_string); + ATF_ADD_TEST_CASE(tcs, push_value); + ATF_ADD_TEST_CASE(tcs, raw_get); + ATF_ADD_TEST_CASE(tcs, raw_set); + ATF_ADD_TEST_CASE(tcs, registry_index); + ATF_ADD_TEST_CASE(tcs, set_global); + ATF_ADD_TEST_CASE(tcs, set_metatable); + ATF_ADD_TEST_CASE(tcs, set_table__ok); + ATF_ADD_TEST_CASE(tcs, set_table__nil); + ATF_ADD_TEST_CASE(tcs, to_boolean); + ATF_ADD_TEST_CASE(tcs, to_integer); + ATF_ADD_TEST_CASE(tcs, to_string); + ATF_ADD_TEST_CASE(tcs, to_userdata); + ATF_ADD_TEST_CASE(tcs, upvalue_index); +} diff --git a/test_utils.hpp b/test_utils.hpp new file mode 100644 index 000000000000..9cbb8edee729 --- /dev/null +++ b/test_utils.hpp @@ -0,0 +1,141 @@ +// Copyright 2011 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file test_utils.hpp +/// Utilities for tests of the lua modules. +/// +/// This file is intended to be included once, and only once, for every test +/// program that needs it. All the code is herein contained to simplify the +/// dependency chain in the build rules. + +#if !defined(LUTOK_TEST_UTILS_HPP) +# define LUTOK_TEST_UTILS_HPP +#else +# error "test_utils.hpp can only be included once" +#endif + +#include + +#include "c_gate.hpp" +#include "exceptions.hpp" +#include "state.hpp" + + +namespace { + + +/// Checks that a given expression raises a particular lutok::api_error. +/// +/// We cannot make any assumptions regarding the error text provided by Lua, so +/// we resort to checking only which API function raised the error (because our +/// code is the one hardcoding these strings). +/// +/// \param exp_api_function The name of the Lua C API function that causes the +/// error. +/// \param statement The statement to execute. +#define REQUIRE_API_ERROR(exp_api_function, statement) \ + do { \ + try { \ + statement; \ + ATF_FAIL("api_error not raised by " #statement); \ + } catch (const lutok::api_error& api_error) { \ + ATF_REQUIRE_EQ(exp_api_function, api_error.api_function()); \ + } \ + } while (0) + + +/// Gets the pointer to the internal lua_State of a state object. +/// +/// This is pure syntactic sugar to simplify typing in the test cases. +/// +/// \param state The Lua state. +/// +/// \return The internal lua_State of the input Lua state. +static inline lua_State* +raw(lutok::state& state) +{ + return lutok::state_c_gate(state).c_state(); +} + + +/// Ensures that the Lua stack maintains its original height upon exit. +/// +/// Use an instance of this class to check that a piece of code does not have +/// side-effects on the Lua stack. +/// +/// To be used within a test case only. +class stack_balance_checker { + /// The Lua state. + lutok::state& _state; + + /// Whether to install a sentinel on the stack for balance enforcement. + bool _with_sentinel; + + /// The height of the stack on creation. + unsigned int _old_count; + +public: + /// Constructs a new stack balance checker. + /// + /// \param state_ The Lua state to validate. + /// \param with_sentinel_ If true, insert a sentinel item into the stack and + /// validate upon exit that the item is still there. This is an attempt + /// to ensure that already-existing items are not removed from the stack + /// by the code under test. + stack_balance_checker(lutok::state& state_, + const bool with_sentinel_ = true) : + _state(state_), + _with_sentinel(with_sentinel_), + _old_count(_state.get_top()) + { + if (_with_sentinel) + _state.push_integer(987654321); + } + + /// Destructor for the object. + /// + /// If the stack height does not match the height when the instance was + /// created, this fails the test case. + ~stack_balance_checker(void) + { + if (_with_sentinel) { + if (!_state.is_number(-1) || _state.to_integer(-1) != 987654321) + ATF_FAIL("Stack corrupted: sentinel not found"); + _state.pop(1); + } + + unsigned int new_count = _state.get_top(); + if (_old_count != new_count) + //ATF_FAIL(F("Stack not balanced: before %d, after %d") % + // _old_count % new_count); + ATF_FAIL("Stack not balanced"); + } +}; + + +} // anonymous namespace