extension.h
Go to the documentation of this file.
/* Copyright (C) 2014, 2016 and 2020 Chris Vine
The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License, version 2.1, for more details.
You should have received a copy of the GNU Lesser General Public
License, version 2.1, along with this library (see the file LGPL.TXT
which came with this source code package in the c++-gtk-utils
sub-directory); if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License. With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence. This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).
NOTE: If you incorporate this header file in your code, you will have
to link with libguile. libguile is released under the LGPL version 3
or later. By linking with libguile your code will therefore be
governed by the LPGL version 3 or later, not the LGPL version 2.1 or
later.
*/
#ifndef CGU_EXTENSION_H
#define CGU_EXTENSION_H
/**
* @namespace Cgu::Extension
* @brief This namespace provides functions to execute scheme code on the guile VM.
*
* \#include <c++-gtk-utils/extension.h>
*
* The Extension::exec() and Extension::exec_shared() functions
* provided by this library allow any C++ program to execute files
* written in the scheme language on the guile VM as part of the C++
* runtime. There are a number of reasons why this might be useful:
*
* @par
* - to enable the dynamic behaviour of the program to be altered
* without recompilation
*
* @par
* - to provide a plugin system
*
* @par
* - because some things are easier or quicker to do when done in
* a dynamically typed language such as scheme
*
* @par
* - because scheme is a nice language to use and highly
* extensible
*
* @par
* - with Extension::exec() and Extension::exec_shared(), it is
* trivial to do (see the example below)
*
* To call Extension::exec() or Extension::exec_shared(), guile-2.0 >=
* 2.0.2, guile-2.2 >= 2.1.3 or guile-3.0 >= 3.0.0 is required.
*
* Usage
* -----
*
* Extension::exec() and Extension::exec_shared() take three
* arguments. The first is a preamble string, which sets out any top
* level definitions which the script file needs to see. This is
* mainly intended for argument passing to the script file, but can
* comprise any scheme code. It can also be an empty string. The
* second is the name of the scheme script file to be executed, with
* path. This file can contain scheme code of arbitrary complexity
* and length, and can incorporate guile modules and other scheme
* files.
*
* The third argument is a translator. This is a function or callable
* object which takes the value to which the scheme file evaluates (in
* C++ terms, its return value) as an opaque SCM guile type (it is
* actually a pointer to a struct in the guile VM), and converts it to
* a suitable C++ representation using functions provided by libguile.
* The return value of the translator function comprises the return
* value of Extension::exec() and Extension::exec_shared().
*
* Translators
* -----------
*
* Preformed translators are provided by this library to translate
* from scheme's integers, real numbers and strings to C++ longs,
* doubles and strings respectively (namely
* Extension::integer_to_long(), Extension::real_to_double() and
* Extension::string_to_string()), and from any uniform lists of these
* to C++ vectors of the corresponding type
* (Extension::list_to_vector_long(),
* Extension::list_to_vector_double() and
* Extension::list_to_vector_string(). There is also a translator for
* void return types (Extension::any_to_void()), where the scheme
* script is executed for its side effects, perhaps I/O, where the
* return value is ignored and any communication necessary is done by
* guile exceptions.
*
* Any guile exception thrown by the code in the scheme file is
* trapped by the preformed translators and will be rethrown as an
* Extension::GuileException C++ exception. The preformed translators
* should suffice for most purposes, but custom translators can be
* provided by the user - see further below.
*
* Example
* -------
*
* Assume the following code is in a file called 'myip.scm'.
*
* @code
* ;; myip.scm
*
* ;; the following code assumes a top level definition of 'web-ip' is
* ;; passed, giving the URL of an IP address reflector
*
* (use-modules (ice-9 regex)(web uri)(web client))
* (let ([uri (build-uri 'http
* #:host web-ip
* #:port 80
* #:path "/")])
* (call-with-values
* (lambda () (http-get uri))
* (lambda (request body)
* (match:substring
* (string-match "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"
* body)))))
* @endcode
*
* This code requires guile >= 2.0.3, and makes a http request to the
* reflector, reads the body of the reply and then does a regex search
* on it to obtain the address. Courtesy of the good folks at DynDNS
* we can obtain our address with this:
*
* @code
* using namespace Cgu;
* std::cout << "IP address is: "
* << Extension::exec_shared("(define web-ip \"checkip.dyndns.com\")",
* "./myip.scm",
* &Extension::string_to_string)
* << std::endl;
* @endcode
*
* This is easier than doing the same in C++ using, say, libsoup and
* std::regex. However it is unsatisfying where we do not want the
* code to block waiting for the reply. There are a number of
* possible approaches to this, but one is to provide the result to a
* glib main loop asynchronously via a Thread::TaskManager object. If
* that is done, it is necessary to deal with a case where guile
* throws an exception (say because the url does not resolve).
* Thread::TaskManager::make_task_packaged_compose() would be suitable
* for this because any thrown Extension::GuileException would be
* stored in the shared state of a std::packaged_task object:
*
* @code
* using namespace Cgu;
* Thread::TaskManager tm{1};
* tm.make_task_packaged_compose(
* [] () {
* return Extension::exec_shared("(define web-ip \"checkip.dyndns.com\")",
* "./myip.scm",
* &Extension::string_to_string);
* },
* 0, // supply result to default glib main loop
* [] (std::future<std::string>& res) { // the 'when' callback
* try {
* std:string ip{res.get()};
* // publish result in some GTK widget
* }
* catch (Extension::GuileException& e) {
* // display GtkMessageDialog object indicating failure
* }
* }
* );
* @endcode
*
* Extension::exec() and Extension::exec_shared()
* ----------------------------------------------
*
* Extension::exec() isolates the top level definitions of a task,
* including definitions in the preamble of a task or imported by
* guile's 'use-modules' or 'load' procedures, from the top level
* definitions of other tasks started by calls to Extension::exec(),
* by calling guile's 'make-fresh-user-module' procedure.
* Extension::exec_shared() does not do so: with
* Extension::exec_shared(), all scheme tasks executed by calls to
* that function will share the same top level. In addition,
* Extension::exec() loads the file passed to the function using the
* guile 'load' procedure, so that the first time the file is executed
* it is compiled into bytecode, whereas Extension::exec_shared()
* calls the 'primitive-load' procedure instead, which runs the file
* through the guile interpreter without converting it to bytecode.
*
* The reason for this different behaviour of Extension::exec_shared()
* is that, as currently implemented in guile both the
* 'make-fresh-user-module' and 'load' procedures leak small amounts
* of memory. If a particular program is likely to call
* Extension::exec() more than about 5,000 or 10,000 times, it would
* be better to use Extension::exec_shared() instead.
*
* From guile-2.0.2, Extension::exec() and Extension::exec_shared() do
* not need to be called only in the main program thread - and in the
* above example using a Thread::TaskManager object
* Extension::exec_shared() was not. However, one of the consequences
* of the behaviour mentioned above is that if
* Extension::exec_shared() is to be used instead of
* Extension::exec(), either concurrent calls to the function from
* different threads should be avoided, or (i) the preambles in calls
* to Extension::exec_shared() should be empty and (ii) tasks should
* not make clashing top level definitions in some other way,
* including by importing clashing definitions using 'use-modules' or
* 'load'. The need for Extension::exec_shared() to be called only in
* one thread in the example above was the reason why the TaskManager
* object in that example was set to have a maximum thread count of 1.
* In effect the TaskManager object was a dedicated serial dispatcher
* for all scheme tasks.
*
* The calling by Extension::exec_shared() of 'primitive-load' instead
* of 'load' may have some small effect on efficiency. It it best for
* the file passed to that function to hand off any complex code to
* modules prepared using guile's modules interface (which will be
* compiled into bytecode), and which are then loaded using
* 'use-modules' the first time Extension::exec_shared() is called, or
* by having the first call to Extension::exec_shared() (and only the
* first call) import any needed files into the top level using
* 'load'.
*
* Note that some guile global state may be shared between tasks
* whether Extension::exec() or Extension::exec_shared() is used. For
* example, if the guile 'add-to-load-path' procedure is called to add
* a local directory to the search path used by 'use-modules' or
* 'load', that will have effect for all other tasks.
*
* Other thread safety points
* --------------------------
*
* Leaving aside what has been said above, there are a few other
* issues to keep in mind if executing scheme code in more than one
* thread.
*
* First, the initialization of guile < 2.0.10 is not thread safe.
* One thread needs to have called Extension::exec() or
* Extension::exec_shared() once and returned before any other threads
* call the function. This can be achieved by the simple expedient of
* executing the statement:
*
* @code
* Extension::exec_shared("", "", &Extension::any_to_void);
* @endcode
*
* and waiting for it to return before any tasks are added to a
* TaskManager object running more than one thread. This issue is
* fixed in guile-2.0.10. However there is a further snag. Certain
* aspects of guile module loading are not thread safe. One way
* around this is to load all the modules that tasks may use in
* advance, by loading the modules in the preamble of the above
* statement (or to have that statement execute a file which loads the
* modules). If that is done, it should be fine afterwards to run
* Extension::exec() (or Extension::exec_shared() if clashing top
* level definitions are avoided as mentioned above) on a TaskManager
* object running any number of threads, or on a Thread::Future object
* or std::async() task. (However, note that if using
* Extension::exec() the modules would need to be reloaded in each
* task in order to make them visible to the task, but that would be
* done safely if they have previously been loaded in another task.)
*
* If a C++ program is to run guile tasks on a TaskManager object
* having a maximum thread count greater than one (or in more than one
* thread in some other way), one other point should be noted. When a
* scheme file is executed for the first time by a user by being
* passed as the second argument of Extension::exec() (or by having
* 'load' applied to it in some other way), it will be compiled into
* byte code, the byte code will then be cached on the file system for
* that and subsequent calls, and the byte code then executed. Bad
* things might happen if concurrent calls to Extension::exec(), or to
* the 'load' or 'use-modules' procedures, are made in respect of the
* same scheme file for the "first" time, if there might be a race as
* to which of them is the "first" call in respect of the file: that
* is, if it causes two or more threads to try to compile the same
* file into byte code concurrently. This is only an issue the first
* time a particular user executes a scheme file, and can be avoided
* (amongst other ways) by having the C++ program concerned
* pre-compile the relevant scheme file before Extension::exec() or
* Extension::exec_shared() is first called, by means of the 'guild
* compile [filename]' command. The following gives more information
* about compilation: <A
* HREF="http://www.gnu.org/software/guile/manual/html_node/Compilation.html#Compilation">
* Compiling Scheme Code</A>
*
* Licence
* -------
*
* The c++-gtk-utils library (and this c++-gtk-utils/extension.h
* header file) follows glib and GTK by being released under the LGPL
* version 2.1 or later. libguile is released under the LGPL version
* 3 or later. The c++-gtk-utils library object code does not link to
* libguile, nor does it incorporate anything in the
* c++-gtk-utils/extension.h header. Instead
* c++-gtk-utils/extension.h contains all its code as a separate
* unlinked header for any program which wants to include it (this is
* partly because some of it comprises template functions).
*
* There are two consequences. If you want to use Extension::exec()
* or Extension::exec_shared(), the program which calls it will need
* to link itself explicitly with libguile as well as c++-gtk-utils,
* and to do that will need to use pkg-config to obtain libguile's
* cflags and libs particulars (its pkg-config file is guile-2.0.pc,
* guile-2.2.pc or guile-3.0.pc). This library does NOT do that for
* you. Secondly, by linking with libguile you will be governed by
* the LGPL version 3 or later, instead of the LGPL version 2.1 or
* later, with respect to that linking. That's fine (there is nothing
* wrong with the LGPL version 3 and this library permits that) but
* you should be aware of it. The scheme code in guile's scheme level
* modules is also in the main released under the LGPL version 3 or
* later ("in the main" because readline.scm, comprised in the
* readline module, is released under the GPL version 3 or later).
*
* Configuration
* -------------
*
* By default, when the c++-gtk-utils library is configured,
* configuration will first look for guile-3.0 >= 3.0.0, then if it
* does not find that it will look for guile-2.2 >= 2.1.3, then if it
* does not find that it will look for guile-2.0 >= 2.0.2, and then if
* it finds none it will disable guile support; and the library header
* files will then be set up appropriately. guile-3.0, guile-2.2 or
* guile-2.0 can be specifically picked with the \--with-guile=3.0,
* \--with-guile=2.2 or \--with-guile=2.0 configuration options
* respectively. Guile support can be omitted with the
* \--with-guile=no option.
* However, as mentioned under "Licence" above, any program using
* Extension::exec() or Extension::exec_shared() must link itself
* explicitly with libguile via either guile-2.0.pc (for guile-2.0),
* guile-2.2.pc (for guile-2.2) or guile-3.0.pc (for guile-3.0).
* Programs should use whichever of those is the one for which
* c++-gtk-utils was configured. If you get link-time messages from a
* program about being unable to link to scm_dynwind_block_asyncs(),
* then there has been a version mismatch. If you get link-time
* messages about being unable to link to Cgu::Extension::init_mutex()
* or Cgu::Extension::get_user_module_mutex() then c++-gtk-utils has
* not been configured to offer guile support.
*
* Custom translators
* ------------------
*
* Any function or callable object which translates from an opaque SCM
* value to a suitable C++ representation can be passed as the third
* argument of Extension::exec() or Extension::exec_shared(). C++
* type deduction on template resolution will take care of everything
* else. The translator can execute any functions offered by
* libguile, because when the translator is run the program is still
* in guile mode. The fulsome guile documentation sets out the
* libguile functions which are callable in C/C++ code.
*
* The first call in a custom translator should normally be to the
* Extension::rethrow_guile_exception() function. This function tests
* whether a guile exception arose in executing the scheme file, and
* throws a C++ exception if it did. The preformed translators in
* extension.h provide worked examples of how a custom translator
* might be written.
*
* If something done in a custom translator were to raise a guile
* exception, the library implementation would handle it and a C++
* exception would be generated in its place in Extension::exec() or
* Extension::exec_shared(). However, a custom translator should not
* allow a guile exception arising from calls to libguile made by it
* to exit a C++ scope in which the translator has constructed a local
* C++ object which is not trivially destructible: that would give
* rise to undefined behaviour, with the likely result that the C++
* object's destructor would not be called. One approach to this
* (adopted in the preformed translators) is to allocate on free store
* all local C++ objects to be constructed in the translator which are
* not trivially destructible, and to manage their lifetimes manually
* using local C++ try/catch blocks rather than RAII, with dynwind
* unwind handlers to release memory were there to be a guile
* exception (but note that no C++ exception should transit out of a
* scm_dynwind_begin()/scm_dynwind_end() pair). It is also a good
* idea to test for any condition which might cause a guile exception
* to be raised in the translator in the first place, and throw a C++
* exception beforehand. Then the only condition which might cause a
* guile exception to occur in the translator is an out-of-memory
* condition, which is highly improbable in a translator as the
* translator is run after the guile task has completed. Heap
* exhaustion in such a case probably spells doom for the program
* concerned anyway, if it has other work to do.
*
* Note also that code in a custom translator should not store guile
* SCM objects (which are pointers to guile scheme objects) in memory
* blocks allocated by malloc() or the new expression, or they will
* not be seen by the garbage collector used by libguile (which is the
* gc library) and therefore may be prematurely deallocated. To keep
* such items alive in custom translators, SCM variables should be
* kept as local variables or parameter names in functions (and so
* stored on the stack or in registers, where they will be seen by the
* garbage collector), or in memory allocated with scm_gc_malloc(),
* where they will also be seen by the garbage collector.
*
* Scheme
* ------
* If you want to learn more about scheme, these are useful sources:
* <P>
* <A HREF="http://www.gnu.org/software/guile/manual/html_node/Hello-Scheme_0021.html"> Chapter 3 of the Guile Manual</A>
* (the rest of the manual is also good reading).</P>
* <P>
* <A HREF="http://www.scheme.com/tspl4/">The Scheme Programming Language, 4th edition</A></P>
*/
#include <string>
#include <vector>
#include <exception>
#include <memory> // for std::unique_ptr
#include <type_traits> // for std::result_of
#include <limits> // for std::numeric_limits
#include <utility> // for std::forward and std::move
#include <new> // for std::bad_alloc
#include <stddef.h> // for size_t
#include <stdlib.h> // for free()
#include <string.h> // for strlen() and strncmp()
#include <glib.h>
#include <c++-gtk-utils/shared_handle.h>
#include <c++-gtk-utils/callback.h>
#include <c++-gtk-utils/thread.h>
#include <c++-gtk-utils/mutex.h>
#include <c++-gtk-utils/cgu_config.h>
#include <libguile.h>
#ifndef DOXYGEN_PARSING
namespace Cgu {
namespace Extension {
struct FormatArgs {
SCM text;
SCM rest;
};
enum VectorDeleteType {Long, Double, String};
struct VectorDeleteArgs {
VectorDeleteType type;
void* vec;
};
// defined in extension_helper.cpp
extern Cgu::Thread::Mutex* get_user_module_mutex() noexcept;
extern bool init_mutex() noexcept;
} // namespace Extension
} // namespace Cgu
namespace {
extern "C" {
inline SCM cgu_format_try_handler(void* data) {
using Cgu::Extension::FormatArgs;
FormatArgs* format_args = static_cast<FormatArgs*>(data);
return scm_simple_format(SCM_BOOL_F, format_args->text, format_args->rest);
}
inline SCM cgu_format_catch_handler(void*, SCM, SCM) {
return SCM_BOOL_F;
}
inline void* cgu_guile_wrapper(void* data) {
try {
static_cast<Cgu::Callback::Callback*>(data)->dispatch();
}
// an elipsis catch block is fine as thread cancellation is
// blocked. We can only enter this block if assigning to one of
// the exception strings in the callback has thrown
// std::bad_alloc. For that case we return a non-NULL pointer to
// indicate error (the 'data' argument is convenient and
// guaranteed to be standard-conforming for this).
catch (...) {
return data;
}
return 0;
}
inline void cgu_delete_vector(void* data) {
using Cgu::Extension::VectorDeleteArgs;
VectorDeleteArgs* args = static_cast<VectorDeleteArgs*>(data);
switch (args->type) {
case Cgu::Extension::Long:
delete static_cast<std::vector<long>*>(args->vec);
break;
case Cgu::Extension::Double:
delete static_cast<std::vector<double>*>(args->vec);
break;
case Cgu::Extension::String:
delete static_cast<std::vector<std::string>*>(args->vec);
break;
default:
g_critical("Incorrect argument passed to cgu_delete_vector");
}
delete args;
}
inline void cgu_unlock_module_mutex(void*) {
// this cannot give rise to an allocation or mutex error -
// we must have been called init_mutex() first
Cgu::Extension::get_user_module_mutex()->unlock();
}
} // extern "C"
} // unnamed namespace
#endif // DOXYGEN_PARSING
namespace Cgu {
namespace Extension {
class GuileException: public std::exception {
Cgu::GcharSharedHandle message;
Cgu::GcharSharedHandle guile_message;
public:
virtual const char* what() const throw() {return (const char*)message.get();}
const char* guile_text() const throw() {return (const char*)guile_message.get();}
GuileException(const char* msg):
message(g_strdup_printf(u8"Cgu::Extension::GuileException: %s", msg)),
guile_message(g_strdup(msg)) {}
~GuileException() throw() {}
};
class ReturnValueError: public std::exception {
Cgu::GcharSharedHandle message;
Cgu::GcharSharedHandle err_message;
public:
virtual const char* what() const throw() {return (const char*)message.get();}
const char* err_text() const throw() {return (const char*)err_message.get();}
ReturnValueError(const char* msg):
message(g_strdup_printf(u8"Cgu::Extension::ReturnValueError: %s", msg)),
err_message(g_strdup(msg)) {}
~ReturnValueError() throw() {}
};
class WrapperError: public std::exception {
Cgu::GcharSharedHandle message;
public:
virtual const char* what() const throw() {return (const char*)message.get();}
WrapperError(const char* msg):
message(g_strdup_printf(u8"Cgu::Extension::WrapperError: %s", msg)) {}
~WrapperError() throw() {}
};
#ifndef DOXYGEN_PARSING
// we might as well take 'translator' by collapsible reference. If it
// is handed a rvalue function object, it will be implicitly converted
// to lvalue (and if necessary constructed as such) in order to take a
// lvalue reference on lambda construction. If it is already a
// lvalue, it will be passed through seamlessly.
template <class Ret, class Translator>
Ret exec_impl(const std::string& preamble,
const std::string& file,
Translator&& translator,
bool shared) {
Cgu::Thread::CancelBlock b;
std::string loader;
loader += preamble;
if (!file.empty()) {
if (shared)
loader += u8"((lambda ()";
loader += u8"(catch "
"#t"
"(lambda ()"
"(";
if (shared)
loader += u8"primitive-load \"";
else
loader += u8"load \"";
loader += file;
loader += u8"\"))"
"(lambda (key . details)"
"(cons \"***cgu-guile-exception***\" (cons key details))))";
if (shared)
loader += u8"))";
}
Ret retval;
bool result = false;
std::string guile_except;
std::string guile_ret_val_err;
std::string gen_err;
// we construct a Callback::Callback object here to perform type
// erasure. Otherwise we would have to pass scm_with_guile() a
// function pointer to a function templated on Translator and Ret,
// which must have C++ language linkage (§14/4 of C++ standard).
// Technically this would give undefined behaviour as
// scm_with_guile() expects a function pointer with C language
// linkage, although gcc and clang would accept it. The whole of
// this callback will be executed in guile mode via
// cgu_guile_wrapper(), so it can safely call libguile functions
// (provided that a translator does not allow any guile exceptions
// to escape a C++ scope with local objects which are not trivially
// destructible). It is also safe to pass 'translator', 'retval',
// 'loader', 'result' and the exception strings to it by reference,
// because scm_with_guile() will block until it has completed
// executing. cgu_guile_wrapper() will trap any std::bad_alloc
// exception thrown by the string assignments in the catch blocks in
// this lambda. This lambda is safe against a jump to an exit from
// scm_with_guile() arising from a native guile exception in
// translator, because its body has no objects in local scope
// requiring destruction.
std::unique_ptr<Cgu::Callback::Callback> cb(Cgu::Callback::lambda<>([&] () -> void {
SCM scm;
if (shared) {
scm = scm_eval_string_in_module(scm_from_utf8_string(loader.c_str()),
scm_c_resolve_module("guile-user"));
}
else {
if (!init_mutex())
throw std::bad_alloc(); // this will be caught in cgu_guile_wrapper()
scm_dynwind_begin(scm_t_dynwind_flags(0));
scm_dynwind_unwind_handler(&cgu_unlock_module_mutex, 0, SCM_F_WIND_EXPLICITLY);
get_user_module_mutex()->lock(); // won't throw
// neither make-fresh-user-module nor
// set-module-declarative?! are part of the publicly
// documented guile API, but are made available by
// ice-9/boot-9.scm. set-module-declarative?! is the
// modifier of the declarative? field of the module record
// type in guile-3.0. There is no workable way of avoiding
// their use, but they should be safe until at least the
// next API-breaking version of guile, and probably long
// after that.
SCM new_mod = scm_call_0(scm_c_public_ref("guile", "make-fresh-user-module"));
#if SCM_MAJOR_VERSION >= 3
scm_call_2(scm_c_public_ref("guile", "set-module-declarative?!"),
new_mod, SCM_BOOL_F);
#endif
scm_dynwind_end();
scm = scm_eval_string_in_module(scm_from_utf8_string(loader.c_str()),
new_mod);
}
// have a dynwind context and async block while translator is
// executing. This is to cater for the pathological case of
// the scheme script having set up a signal handler which
// might throw a guile exception, or having chained a series
// of system asyncs which are still queued for action, which
// might otherwise cause the translator to trigger a guile
// exception while a non-trivially destructible object is in
// the local scope of translator. (Highly unlikely, but easy
// to deal with.) This is entirely safe as no C++ exception
// arising from the translator can transit across the dynamic
// context in this function - see below. So we have (i) no
// C++ exception can transit across a guile dynamic context,
// and (ii) no guile exception can escape out of a local C++
// scope by virtue of an async executing (it might still
// escape with a non-trivially destructible object in
// existence if a custom translator has not been written
// correctly, but there is nothing we can do about that).
// Note that some guile installations do not link
// scm_dynwind_block_asyncs() correctly - this is tested at
// configuration time.
#ifndef CGU_GUILE_HAS_BROKEN_LINKING
scm_dynwind_begin(scm_t_dynwind_flags(0));
scm_dynwind_block_asyncs();
#endif
// we cannot use std::exception_ptr here to store a C++
// exception object, because std::exception_ptr is not
// trivially destructible and a guile exception in
// 'translator' could jump out of this scope to its
// continuation in scm_with_guile()
bool badalloc = false;
try {
retval = translator(scm);
result = true; // this will detect any guile exception in
// the preamble or 'translator' which causes
// scm_with_guile() to return prematurely.
// We could have done it instead by reversing
// the return values of cgu_guile_wrapper
// (non-NULL for success and NULL for
// failure) because scm_with_guile() returns
// NULL if it exits on an uncaught guile
// exception, but this approach enables us to
// discriminate between a C++ memory
// exception in the wrapper and a guile
// exception in the preamble or 'translator',
// at a minimal cost of one assignment to a
// bool.
}
catch (GuileException& e) {
try {
guile_except = e.guile_text();
}
catch (...) {
badalloc = true;
}
}
catch (ReturnValueError& e) {
try {
guile_ret_val_err = e.err_text();
}
catch (...) {
badalloc = true;
}
}
catch (std::exception& e) {
try {
gen_err = e.what();
}
catch (...) {
badalloc = true;
}
}
catch (...) {
try {
gen_err = u8"C++ exception thrown in cgu_guile_wrapper()";
}
catch (...) {
badalloc = true;
}
}
#ifndef CGU_GUILE_HAS_BROKEN_LINKING
scm_dynwind_end();
#endif
if (badalloc) throw std::bad_alloc(); // this will be caught in cgu_guile_wrapper()
}));
// cgu_guile_wrapper(), and so scm_with_guile() will return a
// non-NULL value if assigning to one of the exception description
// strings above threw std::bad_alloc
if (scm_with_guile(&cgu_guile_wrapper, cb.get()))
throw WrapperError(u8"cgu_guile_wrapper() has trapped std::bad_alloc");
if (!guile_except.empty())
throw GuileException(guile_except.c_str());
if (!guile_ret_val_err.empty())
throw ReturnValueError(guile_ret_val_err.c_str());
if (!gen_err.empty())
throw WrapperError(gen_err.c_str());
if (!result)
throw WrapperError(u8"the preamble or translator threw a native guile exception");
return retval;
}
#endif // DOXYGEN_PARSING
/**
* This function is called by Extension::rethrow_guile_exception()
* where the scheme code executed by Extension::exec() or
* Extension::exec_shared() has exited with a guile exception. It
* converts the raw guile exception information represented by the
* 'key' and 'args' arguments of a guile catch handler to a more
* readable form. It is made available as part of the public
* interface so that any custom translators can also use it if they
* choose to provide their own catch expressions. This function does
* not throw any C++ exceptions. No native guile exception will arise
* in this function and so cause guile to jump out of it assuming no
* guile out-of-memory condition occurs (and given that this function
* is called after a guile extension task has completed, such a
* condition is very improbable). It is thread safe, but see the
* comments above about the thread safety of Extension::exec() and
* Extension::exec_shared().
*
* @param key An opaque guile SCM object representing a symbol
* comprising the 'key' argument of the exception handler of a guile
* catch expression.
* @param args An opaque guile SCM object representing a list
* comprising the 'args' argument of the exception handler of a guile
* catch expression.
* @return An opaque guile SCM object representing a guile string.
*
* Since 2.0.22 and 2.2.5
*/
inline SCM exception_to_string(SCM key, SCM args) noexcept {
// The args of most exceptions thrown by guile are in the following format:
// (car args) - a string comprising the name of the procedure generating the exception
// (cadr args) - a string containing text, possibly with format directives (escape sequences)
// (caddr args) - a list containing items matching the format directives, or #f if none
// (cadddr args) - (not used here) a list of additional objects (eg the errno for some errors),
// or #f if none
SCM ret = SCM_BOOL_F;
int length = scm_to_int(scm_length(args));
if (length) {
SCM first = scm_car(args);
if (scm_is_true(scm_string_p(first))) {
// if a single user string, output it
if (length == 1) {
ret = scm_string_append(scm_list_4(scm_from_utf8_string(u8"Exception "),
scm_symbol_to_string(key),
scm_from_utf8_string(u8": "),
first));
}
else { // length > 1
SCM second = scm_cadr(args);
if (scm_is_true(scm_string_p(second))) {
// we should have a standard guile exception string, as above
SCM text = scm_string_append(scm_list_n(scm_from_utf8_string(u8"Exception "),
scm_symbol_to_string(key),
scm_from_utf8_string(u8" in procedure "),
first,
scm_from_utf8_string(u8": "),
second,
SCM_UNDEFINED));
if (length == 2)
ret = text;
else { // length > 2
SCM third = scm_caddr(args);
if (scm_is_false(third))
ret = text;
else if (scm_is_true(scm_list_p(third))) {
FormatArgs format_args = {text, third};
ret = scm_internal_catch(SCM_BOOL_T,
&cgu_format_try_handler,
&format_args,
&cgu_format_catch_handler,
0);
}
}
}
}
}
}
// fall back to generic formatting if first or second elements of
// args is not a string or simple-format failed above
if (scm_is_false(ret)) {
// there is no need for a catch block: we know simple-format
// cannot raise an exception here
ret = scm_simple_format(SCM_BOOL_F,
scm_from_utf8_string(u8"Exception ~S: ~S"),
scm_list_2(key, args));
}
return ret;
}
/**
* This function tests whether a guile exception arose in executing a
* scheme extension file, and throws Cgu::Extension::GuileException if
* it did. It is intended for use by custom translators, as the first
* thing the translator does. It is thread safe, but see the comments
* above about the thread safety of Extension::exec() and
* Extension::exec_shared().
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code executed in the extension file passed to
* Extension::exec() or Extension::exec_shared() threw a guile
* exception. Cgu::Extension::GuileException::what() will give
* particulars of the guile exception thrown, in UTF-8 encoding.
* @note No native guile exception will arise in this function and so
* cause guile to jump out of it if no guile out-of-memory condition
* occurs (given that this function is called after a guile extension
* task has completed, such a condition is very improbable).
*
* Since 2.0.22 and 2.2.5
*/
inline void rethrow_guile_exception(SCM scm) {
// guile exceptions are always presented to this function as a
// scheme list
if (scm_is_false(scm_list_p(scm))
|| scm_is_true(scm_null_p(scm))) return;
SCM first = scm_car(scm);
if (scm_is_true(scm_string_p(first))) {
size_t len;
const char* text = 0;
// nothing in this function should throw a guile exception unless
// there is a guile out-of-memory exception (which is extremely
// improbable). However, let's cover ourselves in case
scm_dynwind_begin(scm_t_dynwind_flags(0));
char* car = scm_to_utf8_stringn(first, &len);
// there may be a weakness in guile's implementation here: if
// calling scm_dynwind_unwind_handler() were to give rise to an
// out-of-memory exception before the handler is set up by it,
// then we could leak memory allocated from the preceding call to
// scm_to_utf8_stringn(). Whether that could happen is not
// documented, but because (a) it is so improbable, and (b) once
// we are in out-of-memory land we are already in severe trouble
// and glib is likely to terminate the program at some point
// anyway, it is not worth troubling ourselves over.
scm_dynwind_unwind_handler(&free, car, scm_t_wind_flags(0));
if (len == strlen(u8"***cgu-guile-exception***")
&& !strncmp(car, u8"***cgu-guile-exception***", len)) {
SCM str = exception_to_string(scm_cadr(scm), scm_cddr(scm));
// we don't need a dynwind handler for 'text' because nothing
// after the call to scm_to_utf8_stringn() can cause a guile
// exception to be raised
text = scm_to_utf8_stringn(str, &len);
}
// all done - no more guile exceptions are possible in this
// function after this so end the dynamic context, take control of
// the memory by RAII and if necessary throw a C++ exception
scm_dynwind_end();
std::unique_ptr<char, Cgu::CFree> up_car(car);
std::unique_ptr<const char, Cgu::CFree> up_text(text);
// if 'text' is not NULL, 'len' contains its length in bytes
if (text) throw GuileException(std::string(text, len).c_str());
}
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It converts from a
* homogeneous scheme list of integers to a C++ representation of
* std::vector<long>. It is thread safe, but see the comments above
* about the thread safety of Extension::exec() and
* Extension::exec_shared().
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, where that value is a
* homogeneous list of integers.
* @return The std::vector<long> representation.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case, or if
* the length of the input list exceeds std::vector::max_size().
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() does not evaluate to
* the type expected by the translator, or it is out of range for a
* long. Cgu::Extension::ReturnValueError::what() will give further
* particulars, in UTF-8 encoding.
* @note No native guile exception will arise in this function and so
* cause guile to jump out of it unless a guile out-of-memory
* condition occurs (given that this function is called after a guile
* extension task has completed, such a condition is very improbable)
* or the length of the input list exceeds SIZE_MAX (the maximum value
* of std::size_t). If such an exception were to arise, the
* implementation would handle it and a C++ exception would be
* generated in its place in Extension::exec() or
* Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline std::vector<long> list_to_vector_long(SCM scm) {
rethrow_guile_exception(scm);
if (scm_is_false(scm_list_p(scm)))
throw ReturnValueError(u8"scheme code did not evaluate to a list\n");
// nothing in this function should throw a guile exception unless
// there is a guile out-of-memory exception (which is extremely
// improbable). However, let's cover ourselves in case.
scm_dynwind_begin(scm_t_dynwind_flags(0));
// we cannot have a std::vector object in a scope where a guile
// exception might be raised because it has a non-trivial
// destructor, nor can we use RAII. Instead allocate on free store
// and manage the memory by hand until we can no longer jump on a
// guile exception. In addition we cannot store a C++ exception
// using std::exception_ptr because std::exception_ptr is not
// trivially destructible.
bool badalloc = false;
const char* rv_error = 0;
std::vector<long>* res = 0;
VectorDeleteArgs* args = 0;
try {
// it doesn't matter if the second allocation fails, as we clean
// up at the end if there is no guile exception, and if both
// allocations succeed and there were to be a subsequent guile
// exception, cgu_delete_vector cleans up
res = new std::vector<long>;
// allocate 'args' on free store, because the continuation in
// which it executes will be up the stack
args = new VectorDeleteArgs{Long, res};
}
catch (...) {
badalloc = true;
}
if (!badalloc) {
// there may be a weakness in guile's implementation here: if
// calling scm_dynwind_unwind_handler() were to give rise to a
// guile out-of-memory exception before the handler is set up by
// it, then we could leak memory allocated from the preceding new
// expressions. Whether that could happen is not documented by
// guile, but because (a) it is so improbable, and (b) once we are
// in out-of-memory land we are already in severe trouble and glib
// is likely to terminate the program at some point anyway, it is
// not worth troubling ourselves over.
scm_dynwind_unwind_handler(&cgu_delete_vector, args, scm_t_wind_flags(0));
// convert the list to a guile vector so we can access its items
// efficiently in a for loop. This conversion is reasonably
// efficient, in the sense that an ordinary guile vector is an
// array of pointers, pointing to the same scheme objects that the
// list refers to
SCM guile_vec = scm_vector(scm);
// std::vector::size_type is the same as size_t with the standard
// allocators (but if we were to get a silent narrowing conversion
// on calling std::vector::reserve() below, that doesn't matter -
// instead if 'length' is less than SIZE_MAX but greater than the
// maximum value of std::vector::size_type, at some point a call
// to std::vector::push_back() below would throw and be caught,
// and this function would end up rethrowing it as std::bad_alloc.
// If in a particular implementation SIZE_MAX exceeds
// std::vector::max_size(), a std::length_error exception would be
// thrown by reserve() where max_size() is exceeded. On all
// common implementations, max_size() is equal to SIZE_MAX, but
// were such an exception to arise it would be swallowed (see
// below) and then rethrown by this function as std::bad_alloc.
// If 'length' is greater than SIZE_MAX, a guile out-of-range
// exception would be thrown by scm_to_size_t() which would be
// rethrown by Cgu:Extension::exec() or
// Cgu::Exception::exec_shared as a Cgu::Extension::WrapperError
// C++ exception. This is nice to know but in practice such large
// lists would be unusably slow and a memory exception would be
// reached long before std::vector::max_size() or SIZE_MAX are
// exceeded.
size_t length = scm_to_size_t(scm_vector_length(guile_vec));
try {
res->reserve(length);
}
catch (...) {
badalloc = true;
}
for (size_t count = 0;
count < length && !rv_error && !badalloc;
++count) {
SCM item = scm_vector_ref(guile_vec, scm_from_size_t(count));
if (scm_is_false(scm_integer_p(item)))
rv_error = u8"scheme code did not evaluate to a homogeneous list of integer\n";
else {
SCM min = scm_from_long(std::numeric_limits<long>::min());
SCM max = scm_from_long(std::numeric_limits<long>::max());
if (scm_is_false(scm_leq_p(item, max)) || scm_is_false(scm_geq_p(item, min)))
rv_error = u8"scheme code evaluated out of range for long\n";
else {
try {
res->push_back(scm_to_long(item));
}
catch (...) {
badalloc = true;
}
}
}
}
}
// all done - no more guile exceptions are possible in this function
// after this so end the dynamic context, take control of the memory
// by RAII and if necessary throw a C++ exception
scm_dynwind_end();
std::unique_ptr<std::vector<long>> up_res(res);
std::unique_ptr<VectorDeleteArgs> up_args(args);
if (badalloc) throw std::bad_alloc();
if (rv_error) throw ReturnValueError(rv_error);
// neither gcc-4.9 nor clang-3.4 will optimize with RVO or move
// semantics here, so force it by hand
return std::move(*res);
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It converts from a
* homogeneous scheme list of real numbers to a C++ representation of
* std::vector<double>. It is thread safe, but see the comments above
* about the thread safety of Extension::exec() and
* Extension::exec_shared().
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, where that value is a
* homogeneous list of real numbers.
* @return The std::vector<double> representation.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case, or if
* the length of the input list exceeds std::vector::max_size().
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() does not evaluate to
* the type expected by the translator, or it is out of range for a
* double. Cgu::Extension::ReturnValueError::what() will give further
* particulars, in UTF-8 encoding.
* @note 1. Prior to versions 2.0.25 and 2.2.8, this translator had a
* bug which caused an out-of-range Cgu::Extension::ReturnValueError
* to be thrown if any of the numbers in the list to which the scheme
* code in the extension file evaluated was 0.0 or a negative number.
* This was fixed in versions 2.0.25 and 2.2.8.
* @note 2. No native guile exception will arise in this function and
* so cause guile to jump out of it unless a guile out-of-memory
* condition occurs (given that this function is called after a guile
* extension task has completed, such a condition is very improbable)
* or the length of the input list exceeds SIZE_MAX (the maximum value
* of std::size_t). If such an exception were to arise, the
* implementation would handle it and a C++ exception would be
* generated in its place in Extension::exec() or
* Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline std::vector<double> list_to_vector_double(SCM scm) {
rethrow_guile_exception(scm);
if (scm_is_false(scm_list_p(scm)))
throw ReturnValueError(u8"scheme code did not evaluate to a list\n");
// nothing in this function should throw a guile exception unless
// there is a guile out-of-memory exception (which is extremely
// improbable). However, let's cover ourselves in case.
scm_dynwind_begin(scm_t_dynwind_flags(0));
// we cannot have a std::vector object in a scope where a guile
// exception might be raised because it has a non-trivial
// destructor, nor can we use RAII. Instead allocate on free store
// and manage the memory by hand until we can no longer jump on a
// guile exception. In addition we cannot store a C++ exception
// using std::exception_ptr because std::exception_ptr is not
// trivially destructible.
bool badalloc = false;
const char* rv_error = 0;
std::vector<double>* res = 0;
VectorDeleteArgs* args = 0;
try {
// it doesn't matter if the second allocation fails, as we clean
// up at the end if there is no guile exception, and if both
// allocations succeed and there were to be a subsequent guile
// exception, cgu_delete_vector cleans up
res = new std::vector<double>;
// allocate 'args' on free store, because the continuation in
// which it executes will be up the stack
args = new VectorDeleteArgs{Double, res};
}
catch (...) {
badalloc = true;
}
if (!badalloc) {
// there may be a weakness in guile's implementation here: if
// calling scm_dynwind_unwind_handler() were to give rise to a
// guile out-of-memory exception before the handler is set up by
// it, then we could leak memory allocated from the preceding
// new expressions. Whether that could happen is not documented
// by guile, but because (a) it is so improbable, and (b) once
// we are in out-of-memory land we are already in severe trouble
// and glib is likely to terminate the program at some point
// anyway, it is not worth troubling ourselves over.
scm_dynwind_unwind_handler(&cgu_delete_vector, args, scm_t_wind_flags(0));
// convert the list to a guile vector so we can access its items
// efficiently in a for loop. This conversion is reasonably
// efficient, in the sense that an ordinary guile vector is an
// array of pointers, pointing to the same scheme objects that the
// list refers to
SCM guile_vec = scm_vector(scm);
// std::vector::size_type is the same as size_t with the standard
// allocators (but if we were to get a silent narrowing conversion
// on calling std::vector::reserve() below, that doesn't matter -
// instead if 'length' is less than SIZE_MAX but greater than the
// maximum value of std::vector::size_type, at some point a call
// to std::vector::push_back() below would throw and be caught,
// and this function would end up rethrowing it as std::bad_alloc.
// If in a particular implementation SIZE_MAX exceeds
// std::vector::max_size(), a std::length_error exception would be
// thrown by reserve() where max_size() is exceeded. On all
// common implementations, max_size() is equal to SIZE_MAX, but
// were such an exception to arise it would be swallowed (see
// below) and then rethrown by this function as std::bad_alloc.
// If 'length' is greater than SIZE_MAX, a guile out-of-range
// exception would be thrown by scm_to_size_t() which would be
// rethrown by Cgu:Extension::exec() or
// Cgu::Exception::exec_shared as a Cgu::Extension::WrapperError
// C++ exception. This is nice to know but in practice such large
// lists would be unusably slow and a memory exception would be
// reached long before std::vector::max_size() or SIZE_MAX are
// exceeded.
size_t length = scm_to_size_t(scm_vector_length(guile_vec));
try {
res->reserve(length);
}
catch (...) {
badalloc = true;
}
for (size_t count = 0;
count < length && !rv_error && !badalloc;
++count) {
SCM item = scm_vector_ref(guile_vec, scm_from_size_t(count));
if (scm_is_false(scm_real_p(item)))
rv_error = u8"scheme code did not evaluate to a homogeneous list of real numbers\n";
else {
SCM min = scm_from_double(std::numeric_limits<double>::lowest());
SCM max = scm_from_double(std::numeric_limits<double>::max());
if (scm_is_false(scm_leq_p(item, max)) || scm_is_false(scm_geq_p(item, min)))
rv_error = u8"scheme code evaluated out of range for double\n";
else {
try {
res->push_back(scm_to_double(item));
}
catch (...) {
badalloc = true;
}
}
}
}
}
// all done - no more guile exceptions are possible in this function
// after this so end the dynamic context, take control of the memory
// by RAII and if necessary throw a C++ exception
scm_dynwind_end();
std::unique_ptr<std::vector<double>> up_res(res);
std::unique_ptr<VectorDeleteArgs> up_args(args);
if (badalloc) throw std::bad_alloc();
if (rv_error) throw ReturnValueError(rv_error);
// neither gcc-4.9 nor clang-3.4 will optimize with RVO or move
// semantics here, so force it by hand
return std::move(*res);
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It converts from a
* homogeneous scheme list of strings to a C++ representation of
* std::vector<std::string>. It is thread safe, but see the comments
* above about the thread safety of Extension::exec() and
* Extension::exec_shared().
*
* The returned strings will be in UTF-8 encoding.
*
* Note that the first string in the returned list must not be
* "***cgu-guile-exception***": that string is reserved to the
* implementation.
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, where that value is a
* homogeneous list of strings.
* @return The std::vector<std::string> representation.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case, or if
* the length of the input list exceeds std::vector::max_size().
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() does not evaluate to
* the type expected by the translator.
* Cgu::Extension::ReturnValueError::what() will give further
* particulars, in UTF-8 encoding.
* @note No native guile exception will arise in this function and so
* cause guile to jump out of it unless a guile out-of-memory
* condition occurs (given that this function is called after a guile
* extension task has completed, such a condition is very improbable)
* or the length of the input list exceeds SIZE_MAX (the maximum value
* of std::size_t). If such an exception were to arise, the
* implementation would handle it and a C++ exception would be
* generated in its place in Extension::exec() or
* Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline std::vector<std::string> list_to_vector_string(SCM scm) {
rethrow_guile_exception(scm);
if (scm_is_false(scm_list_p(scm)))
throw ReturnValueError(u8"scheme code did not evaluate to a list\n");
// nothing in this function should throw a guile exception unless
// there is a guile out-of-memory exception (which is extremely
// improbable). However, let's cover ourselves in case.
scm_dynwind_begin(scm_t_dynwind_flags(0));
// we cannot have a std::vector object in a scope where a guile
// exception might be raised because it has a non-trivial
// destructor, nor can we use RAII. Instead allocate on free store
// and manage the memory by hand until we can no longer jump on a
// guile exception. In addition we cannot store a C++ exception
// using std::exception_ptr because std::exception_ptr is not
// trivially destructible.
bool badalloc = false;
const char* rv_error = 0;
std::vector<std::string>* res = 0;
VectorDeleteArgs* args = 0;
try {
// it doesn't matter if the second allocation fails, as we clean
// up at the end if there is no guile exception, and if both
// allocations succeed and there were to be a subsequent guile
// exception, cgu_delete_vector cleans up
res = new std::vector<std::string>;
// allocate 'args' on free store, because the continuation in
// which it executes will be up the stack
args = new VectorDeleteArgs{String, res};
}
catch (...) {
badalloc = true;
}
if (!badalloc) {
// there may be a weakness in guile's implementation here: if
// calling scm_dynwind_unwind_handler() were to give rise to a
// guile out-of-memory exception before the handler is set up by
// it, then we could leak memory allocated from the preceding new
// expressions. Whether that could happen is not documented by
// guile, but because (a) it is so improbable, and (b) once we are
// in out-of-memory land we are already in severe trouble and glib
// is likely to terminate the program at some point anyway, it is
// not worth troubling ourselves over.
scm_dynwind_unwind_handler(&cgu_delete_vector, args, scm_t_wind_flags(0));
// convert the list to a guile vector so we can access its items
// efficiently in a for loop. This conversion is reasonably
// efficient, in the sense that an ordinary guile vector is an
// array of pointers, pointing to the same scheme objects that the
// list refers to
SCM guile_vec = scm_vector(scm);
// std::vector::size_type is the same as size_t with the standard
// allocators (but if we were to get a silent narrowing conversion
// on calling std::vector::reserve() below, that doesn't matter -
// instead if 'length' is less than SIZE_MAX but greater than the
// maximum value of std::vector::size_type, at some point a call
// to std::vector::emplace_back() below would throw and be caught,
// and this function would end up rethrowing it as std::bad_alloc.
// If in a particular implementation SIZE_MAX exceeds
// std::vector::max_size(), a std::length_error exception would be
// thrown by reserve() where max_size() is exceeded. On all
// common implementations, max_size() is equal to SIZE_MAX, but
// were such an exception to arise it would be swallowed (see
// below) and then rethrown by this function as std::bad_alloc.
// If 'length' is greater than SIZE_MAX, a guile out-of-range
// exception would be thrown by scm_to_size_t() which would be
// rethrown by Cgu:Extension::exec() or
// Cgu::Exception::exec_shared as a Cgu::Extension::WrapperError
// C++ exception. This is nice to know but in practice such large
// lists would be unusably slow and a memory exception would be
// reached long before std::vector::max_size() or SIZE_MAX are
// exceeded.
size_t length = scm_to_size_t(scm_vector_length(guile_vec));
try {
res->reserve(length);
}
catch (...) {
badalloc = true;
}
for (size_t count = 0;
count < length && !rv_error && !badalloc;
++count) {
SCM item = scm_vector_ref(guile_vec, scm_from_size_t(count));
if (scm_is_false(scm_string_p(item)))
rv_error = u8"scheme code did not evaluate to a homogeneous list of string\n";
else {
size_t len;
// we don't need a dynwind handler for 'str' because nothing
// after the call to scm_to_utf8_stringn() and before the call
// to free() can cause a guile exception to be raised
char* str = scm_to_utf8_stringn(item, &len);
try {
res->emplace_back(str, len);
}
catch (...) {
badalloc = true;
}
free(str);
}
}
}
// all done - no more guile exceptions are possible in this function
// after this so end the dynamic context, take control of the memory
// by RAII and if necessary throw a C++ exception
scm_dynwind_end();
std::unique_ptr<std::vector<std::string>> up_res(res);
std::unique_ptr<VectorDeleteArgs> up_args(args);
if (badalloc) throw std::bad_alloc();
if (rv_error) throw ReturnValueError(rv_error);
// neither gcc-4.9 nor clang-3.4 will optimize with RVO or move
// semantics here, so force it by hand
return std::move(*res);
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It converts from a
* scheme integer to a C++ representation of long. It is thread safe,
* but see the comments above about the thread safety of
* Extension::exec() and Extension::exec_shared().
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, where that value is an integer.
* @return The C++ long representation.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() does not evaluate to
* the type expected by the translator, or it is out of range for a
* long. Cgu::Extension::ReturnValueError::what() will give further
* particulars, in UTF-8 encoding.
* @note No native guile exception will arise in this function and so
* cause guile to jump out of it if no guile out-of-memory condition
* occurs (given that this function is called after a guile extension
* task has completed, such a condition is very improbable). If such
* an exception were to arise, the implementation would handle it and
* a C++ exception would be generated in its place in
* Extension::exec() or Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline long integer_to_long(SCM scm) {
rethrow_guile_exception(scm);
if (scm_is_false(scm_integer_p(scm)))
throw ReturnValueError(u8"scheme code did not evaluate to an integer\n");
SCM min = scm_from_long(std::numeric_limits<long>::min());
SCM max = scm_from_long(std::numeric_limits<long>::max());
if (scm_is_false(scm_leq_p(scm, max)) || scm_is_false(scm_geq_p(scm, min)))
throw ReturnValueError(u8"scheme code evaluated out of range for long\n");
return scm_to_long(scm);
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It converts from a
* scheme real number to a C++ representation of double. It is thread
* safe, but see the comments above about the thread safety of
* Extension::exec() and Extension::exec_shared().
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, where that value is a real
* number.
* @return The C++ double representation.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() does not evaluate to
* the type expected by the translator, or it is out of range for a
* double. Cgu::Extension::ReturnValueError::what() will give further
* particulars, in UTF-8 encoding.
* @note 1. Prior to versions 2.0.25 and 2.2.8, this translator had a
* bug which caused an out-of-range Cgu::Extension::ReturnValueError
* to be thrown if the scheme code in the extension file evaluated to
* 0.0 or a negative number. This was fixed in versions 2.0.25 and
* 2.2.8.
* @note 2. No native guile exception will arise in this function and
* so cause guile to jump out of it if no guile out-of-memory
* condition occurs (given that this function is called after a guile
* extension task has completed, such a condition is very improbable).
* If such an exception were to arise, the implementation would handle
* it and a C++ exception would be generated in its place in
* Extension::exec() or Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline double real_to_double(SCM scm) {
rethrow_guile_exception(scm);
if (scm_is_false(scm_real_p(scm)))
throw ReturnValueError(u8"scheme code did not evaluate to a real number\n");
SCM min = scm_from_double(std::numeric_limits<double>::lowest());
SCM max = scm_from_double(std::numeric_limits<double>::max());
if (scm_is_false(scm_leq_p(scm, max)) || scm_is_false(scm_geq_p(scm, min)))
throw ReturnValueError(u8"scheme code evaluated out of range for double\n");
return scm_to_double(scm);
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It converts from a
* scheme string to a C++ representation of std::string. It is thread
* safe, but see the comments above about the thread safety of
* Extension::exec() and Extension::exec_shared().
*
* The returned string will be in UTF-8 encoding.
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, where that value is a string.
* @return The std::string representation.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() does not evaluate to
* the type expected by the translator.
* Cgu::Extension::ReturnValueError::what() will give further
* particulars, in UTF-8 encoding.
* @note No native guile exception will arise in this function and so
* cause guile to jump out of it if no guile out-of-memory condition
* occurs (given that this function is called after a guile extension
* task has completed, such a condition is very improbable). If such
* an exception were to arise, the implementation would handle it and
* a C++ exception would be generated in its place in
* Extension::exec() or Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline std::string string_to_string(SCM scm) {
rethrow_guile_exception(scm);
if (scm_is_false(scm_string_p(scm)))
throw ReturnValueError(u8"scheme code did not evaluate to a string\n");
size_t len;
// it is safe to use unique_ptr here. If scm_to_utf8_stringn()
// succeeds then nothing after it in this function can cause a guile
// exception.
std::unique_ptr<const char, Cgu::CFree> s(scm_to_utf8_stringn(scm, &len));
return std::string(s.get(), len);
}
/**
* A translator function which can be passed to the third argument of
* Extension::exec() or Extension::exec_shared(). It disregards the
* scheme value passed to it except to trap any guile exception thrown
* by the scheme task and rethrow it as a C++ exception, and returns a
* NULL void* object. It is thread safe, but see the comments above
* about the thread safety of Extension::exec() and
* Extension::exec_shared().
*
* It is mainly intended for use where the scheme script is executed
* for its side effects, perhaps for I/O, and any communication
* necessary is done by guile exceptions.
*
* @param scm An opaque guile SCM object representing the value to
* which the extension file passed to Extension::exec() or
* Extension::exec_shared() evaluated, which is ignored.
* @return A NULL void* object.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in the extension file passed to
* Extension::exec() or Extension::exec_shared() caused a guile
* exception to be thrown. Cgu::Extension::GuileException::what()
* will give particulars of the guile exception thrown, in UTF-8
* encoding.
* @note No native guile exception will arise in this function and so
* cause guile to jump out of it if no guile out-of-memory condition
* occurs (given that this function is called after a guile extension
* task has completed, such a condition is very improbable). If such
* an exception were to arise, the implementation would handle it and
* a C++ exception would be generated in its place in
* Extension::exec() or Extension::exec_shared().
*
* Since 2.0.22 and 2.2.5
*/
inline void* any_to_void(SCM scm) {
rethrow_guile_exception(scm);
return 0;
}
/**
* This function executes scheme code on the guile VM within a C++
* program using this library. See the introductory remarks above for
* its potential uses, about the thread safety of this function, and
* about the use of the TaskManager::exec_shared() as an alternative.
*
* The first argument to this function is a preamble, which can be
* used to pass top level definitions to the scheme code (in other
* words, for argument passing). It's second argument is the filename
* (with path) of the file containing the scheme code to be executed.
* It's third argument is a translator, which will convert the value
* to which the scheme code evaluates (in C++ terms, its return value)
* to a suitable C++ representation. Preformed translators are
* provided by this library to translate from scheme's integers, real
* numbers and strings to C++ longs, doubles and strings respectively,
* and from any uniform lists of these to C++ vectors of the
* corresponding type. There is also a translator for void return
* types. See the introductory remarks above for more information
* about translators.
*
* Any native guile exceptions thrown by the code executed by this
* function (and by any code which it calls) are converted and
* rethrown as C++ exceptions.
*
* The scheme file can call other scheme code, and load modules, in
* the ordinary way. Thus, this function can execute any scheme code
* which guile can execute as a program, and the programmer can (if
* wanted) act on its return value in the C++ code which invokes it.
*
* Thread cancellation is blocked for the thread in which this
* function executes until this function returns.
*
* @param preamble Scheme code such as top level definitions to be
* seen by the code in the file to be executed. This is mainly
* intended for argument passing, but can comprise any valid scheme
* code. It can also be empty (you can pass ""). Any string literals
* must be in UTF-8 encoding.
* @param file The file which is to be executed on the guile VM. This
* should include the full pathname or a pathname relative to the
* current directory. The contents of the file, and in particular any
* string literals in it, must be in UTF-8 encoding. The filename and
* path must also be given in UTF-8 encoding, even if the local
* filename encoding is something different: guile will convert the
* UTF-8 name which it is given to its own internal string encoding
* using unicode code points, and then convert that to locale encoding
* on looking up the filename. However sticking to ASCII for
* filenames and paths (which is always valid UTF-8) will maximise
* portability. The file name can be empty (you can pass ""), in
* which case only the preamble will be evaluated (but for efficiency
* reasons any complex code not at the top level should be included in
* the file rather than in the preamble).
* @param translator The function or callable object which will
* convert the value to which the scheme code evaluates to a C++
* representation which will be returned by this function. The
* translator should take a single argument comprising an opaque guile
* object of type SCM, and return the C++ representation for it.
* @return The C++ representation returned by the translator.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in 'file' (or called by it) throws a
* guile exception. Cgu::Extension::GuileException::what() will give
* particulars of the guile exception thrown, in UTF-8 encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the code in 'file' does not evaluate to the type expected
* by the translator. Cgu::Extension::ReturnValueError::what() will
* give further particulars, in UTF-8 encoding.
* @exception Cgu::Extension::WrapperError This exception will be
* thrown if a custom translator throws a native guile exception or a
* C++ exception not comprising Extension::GuileException or
* Extension::ReturnValueError, one of the preformed translators
* throws std::bad_alloc or encounters a guile out-of-memory
* exception, one of the preformed list translators encounters an
* input list exceeding SIZE_MAX in length, assigning to an internal
* exception description string throws std::bad_alloc, or evaluation
* of the preamble throws a native guile exception.
*
* Since 2.0.22 and 2.2.5
*/
template <class Translator>
auto exec(const std::string& preamble,
const std::string& file,
Translator&& translator) -> typename std::result_of<Translator(SCM)>::type {
// exec_impl() will fail to compile if Ret is a reference type: that
// is a feature, not a bug, as there is no good reason for a
// translator ever to return a reference
typedef typename std::result_of<Translator(SCM)>::type Ret;
return exec_impl<Ret>(preamble, file, std::forward<Translator>(translator), false);
}
/**
* This function executes scheme code on the guile VM within a C++
* program using this library. See the introductory remarks above for
* its potential uses, about the thread safety of this function, and
* about the use of the TaskManager::exec() as an alternative.
*
* The first argument to this function is a preamble, which can be
* used to pass top level definitions to the scheme code (in other
* words, for argument passing). It's second argument is the filename
* (with path) of the file containing the scheme code to be executed.
* It's third argument is a translator, which will convert the value
* to which the scheme code evaluates (in C++ terms, its return value)
* to a suitable C++ representation. Preformed translators are
* provided by this library to translate from scheme's integers, real
* numbers and strings to C++ longs, doubles and strings respectively,
* and from any uniform lists of these to C++ vectors of the
* corresponding type. There is also a translator for void return
* types. See the introductory remarks above for more information
* about translators.
*
* Any native guile exceptions thrown by the code executed by this
* function (and by any code which it calls) are converted and
* rethrown as C++ exceptions.
*
* The scheme file can call other scheme code, and load modules, in
* the ordinary way. Thus, this function can execute any scheme code
* which guile can execute as a program, and the programmer can (if
* wanted) act on its return value in the C++ code which invokes it.
*
* Thread cancellation is blocked for the thread in which this
* function executes until this function returns.
*
* @param preamble Scheme code such as top level definitions to be
* seen by the code in the file to be executed. This is mainly
* intended for argument passing, but can comprise any valid scheme
* code. It can also be empty (you can pass ""). Any string literals
* must be in UTF-8 encoding.
* @param file The file which is to be executed on the guile VM. This
* should include the full pathname or a pathname relative to the
* current directory. The contents of the file, and in particular any
* string literals in it, must be in UTF-8 encoding. The filename and
* path must also be given in UTF-8 encoding, even if the local
* filename encoding is something different: guile will convert the
* UTF-8 name which it is given to its own internal string encoding
* using unicode code points, and then convert that to locale encoding
* on looking up the filename. However sticking to ASCII for
* filenames and paths (which is always valid UTF-8) will maximise
* portability. The file name can be empty (you can pass ""), in
* which case only the preamble will be evaluated.
* @param translator The function or callable object which will
* convert the value to which the scheme code evaluates to a C++
* representation which will be returned by this function. The
* translator should take a single argument comprising an opaque guile
* object of type SCM, and return the C++ representation for it.
* @return The C++ representation returned by the translator.
* @exception std::bad_alloc This function might throw std::bad_alloc
* if memory is exhausted and the system throws in that case.
* @exception Cgu::Extension::GuileException This exception will be
* thrown if the scheme code in 'file' (or called by it) throws a
* guile exception. Cgu::Extension::GuileException::what() will give
* particulars of the guile exception thrown, in UTF-8 encoding.
* @exception Cgu::Extension::ReturnValueError This exception will be
* thrown if the code in 'file' does not evaluate to the type expected
* by the translator. Cgu::Extension::ReturnValueError::what() will
* give further particulars, in UTF-8 encoding.
* @exception Cgu::Extension::WrapperError This exception will be
* thrown if a custom translator throws a native guile exception or a
* C++ exception not comprising Extension::GuileException or
* Extension::ReturnValueError, one of the preformed translators
* throws std::bad_alloc or encounters a guile out-of-memory
* exception, one of the preformed list translators encounters an
* input list exceeding SIZE_MAX in length, assigning to an internal
* exception description string throws std::bad_alloc, or evaluation
* of the preamble throws a native guile exception.
*
* Since 2.0.24 and 2.2.7
*/
template <class Translator>
auto exec_shared(const std::string& preamble,
const std::string& file,
Translator&& translator) -> typename std::result_of<Translator(SCM)>::type {
// exec_impl() will fail to compile if Ret is a reference type: that
// is a feature, not a bug, as there is no good reason for a
// translator ever to return a reference
typedef typename std::result_of<Translator(SCM)>::type Ret;
return exec_impl<Ret>(preamble, file, std::forward<Translator>(translator), true);
}
} // namespace Extension
} // namespace Cgu
#endif // CGU_EXTENSION_H
Cgu::Extension::list_to_vector_string
std::vector< std::string > list_to_vector_string(SCM scm)
Definition: extension.h:1278
Cgu::Extension::string_to_string
std::string string_to_string(SCM scm)
Definition: extension.h:1527
Cgu::Extension::list_to_vector_double
std::vector< double > list_to_vector_double(SCM scm)
Definition: extension.h:1118
Cgu::Callback::CallbackArg
The callback interface class.
Definition: callback.h:567
Cgu::Extension::WrapperError::what
virtual const char * what() const
Definition: extension.h:560
Cgu
Definition: application.h:44
Cgu::Extension::list_to_vector_long
std::vector< long > list_to_vector_long(SCM scm)
Definition: extension.h:959
Cgu::Extension::ReturnValueError
Definition: extension.h:545
Cgu::Extension::real_to_double
double real_to_double(SCM scm)
Definition: extension.h:1479
Cgu::Extension::WrapperError
Definition: extension.h:557
Cgu::Extension::any_to_void
void * any_to_void(SCM scm)
Definition: extension.h:1574
Cgu::Extension::exec
auto exec(const std::string &preamble, const std::string &file, Translator &&translator) -> typename std::result_of< Translator(SCM)>::type
Definition: extension.h:1659
callback.h
This file provides classes for type erasure.
Cgu::Extension::ReturnValueError::ReturnValueError
ReturnValueError(const char *msg)
Definition: extension.h:551
Cgu::Extension::rethrow_guile_exception
void rethrow_guile_exception(SCM scm)
Definition: extension.h:876
Cgu::Extension::GuileException::GuileException
GuileException(const char *msg)
Definition: extension.h:539
Cgu::Extension::integer_to_long
long integer_to_long(SCM scm)
Definition: extension.h:1427
Cgu::SharedHandle< gchar *, GFree >
Cgu::Extension::GuileException::what
virtual const char * what() const
Definition: extension.h:537
Cgu::Extension::exception_to_string
SCM exception_to_string(SCM key, SCM args) noexcept
Definition: extension.h:790
Cgu::Extension::ReturnValueError::err_text
const char * err_text() const
Definition: extension.h:550
shared_handle.h
Cgu::Extension::GuileException
Definition: extension.h:533
Cgu::Extension::ReturnValueError::~ReturnValueError
~ReturnValueError()
Definition: extension.h:554
Cgu::Thread::CancelBlock
A class enabling the cancellation state of a thread to be controlled.
Definition: thread.h:723
mutex.h
Provides wrapper classes for pthread mutexes and condition variables, and scoped locking classes for ...
Cgu::Extension::WrapperError::~WrapperError
~WrapperError()
Definition: extension.h:563
Cgu::Extension::GuileException::~GuileException
~GuileException()
Definition: extension.h:542
Cgu::Extension::GuileException::guile_text
const char * guile_text() const
Definition: extension.h:538
Cgu::Extension::exec_shared
auto exec_shared(const std::string &preamble, const std::string &file, Translator &&translator) -> typename std::result_of< Translator(SCM)>::type
Definition: extension.h:1747
Cgu::Extension::ReturnValueError::what
virtual const char * what() const
Definition: extension.h:549
Cgu::SharedHandle::get
T get() const noexcept
Definition: shared_handle.h:765
thread.h
Cgu::Thread::Mutex
A wrapper class for pthread mutexes.
Definition: mutex.h:117
cgu_config.h
Cgu::Extension::WrapperError::WrapperError
WrapperError(const char *msg)
Definition: extension.h:561