From 4b2c3eb9d49a797f91eee2be4e41cacb3b8167e7 Mon Sep 17 00:00:00 2001 From: Brooks Davis Date: Tue, 17 Mar 2020 16:48:52 +0000 Subject: [PATCH] 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 --- .gitignore | 21 + .travis.yml | 25 + AUTHORS | 1 + COPYING | 27 + Doxyfile.in | 53 ++ INSTALL | 181 +++++ Kyuafile | 11 + Makefile.am | 221 ++++++ NEWS | 68 ++ README | 27 + admin/.gitignore | 8 + admin/clean-all.sh | 90 +++ admin/travis-build.sh | 51 ++ admin/travis-install-deps.sh | 109 +++ c_gate.cpp | 76 ++ c_gate.hpp | 71 ++ c_gate_test.cpp | 74 ++ configure.ac | 70 ++ debug.cpp | 192 +++++ debug.hpp | 90 +++ debug_test.cpp | 68 ++ examples/Makefile | 67 ++ examples/bindings.cpp | 133 ++++ examples/hello.cpp | 58 ++ examples/interpreter.cpp | 83 +++ examples/raii.cpp | 126 ++++ examples_test.sh | 115 +++ exceptions.cpp | 126 ++++ exceptions.hpp | 83 +++ exceptions_test.cpp | 88 +++ include/lutok/README | 4 + include/lutok/c_gate.hpp | 1 + include/lutok/debug.hpp | 1 + include/lutok/exceptions.hpp | 1 + include/lutok/operations.hpp | 1 + include/lutok/stack_cleaner.hpp | 1 + include/lutok/state.hpp | 1 + include/lutok/state.ipp | 1 + lutok.pc.in | 8 + m4/.gitignore | 5 + m4/compiler-features.m4 | 100 +++ m4/compiler-flags.m4 | 159 +++++ m4/developer-mode.m4 | 112 +++ m4/doxygen.m4 | 62 ++ m4/lua.m4 | 69 ++ operations.cpp | 153 ++++ operations.hpp | 55 ++ operations_test.cpp | 372 ++++++++++ stack_cleaner.cpp | 91 +++ stack_cleaner.hpp | 93 +++ stack_cleaner_test.cpp | 108 +++ state.cpp | 904 ++++++++++++++++++++++++ state.hpp | 145 ++++ state.ipp | 67 ++ state_test.cpp | 1164 +++++++++++++++++++++++++++++++ test_utils.hpp | 141 ++++ 56 files changed, 6232 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 Doxyfile.in create mode 100644 INSTALL create mode 100644 Kyuafile create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 admin/.gitignore create mode 100755 admin/clean-all.sh create mode 100755 admin/travis-build.sh create mode 100755 admin/travis-install-deps.sh create mode 100644 c_gate.cpp create mode 100644 c_gate.hpp create mode 100644 c_gate_test.cpp create mode 100644 configure.ac create mode 100644 debug.cpp create mode 100644 debug.hpp create mode 100644 debug_test.cpp create mode 100644 examples/Makefile create mode 100644 examples/bindings.cpp create mode 100644 examples/hello.cpp create mode 100644 examples/interpreter.cpp create mode 100644 examples/raii.cpp create mode 100755 examples_test.sh create mode 100644 exceptions.cpp create mode 100644 exceptions.hpp create mode 100644 exceptions_test.cpp create mode 100644 include/lutok/README create mode 100644 include/lutok/c_gate.hpp create mode 100644 include/lutok/debug.hpp create mode 100644 include/lutok/exceptions.hpp create mode 100644 include/lutok/operations.hpp create mode 100644 include/lutok/stack_cleaner.hpp create mode 100644 include/lutok/state.hpp create mode 100644 include/lutok/state.ipp create mode 100644 lutok.pc.in create mode 100644 m4/.gitignore create mode 100644 m4/compiler-features.m4 create mode 100644 m4/compiler-flags.m4 create mode 100644 m4/developer-mode.m4 create mode 100644 m4/doxygen.m4 create mode 100644 m4/lua.m4 create mode 100644 operations.cpp create mode 100644 operations.hpp create mode 100644 operations_test.cpp create mode 100644 stack_cleaner.cpp create mode 100644 stack_cleaner.hpp create mode 100644 stack_cleaner_test.cpp create mode 100644 state.cpp create mode 100644 state.hpp create mode 100644 state.ipp create mode 100644 state_test.cpp create mode 100644 test_utils.hpp 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