/* ** Copyright (C) 2025 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the ** Free Software Foundation; either version 3, or (at your option) any ** later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software Foundation, ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #ifndef MU_SCM_HH #define MU_SCM_HH #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" #include #pragma GCC diagnostic pop #include "utils/mu-result.hh" #include "mu/mu-options.hh" #include "mu-store.hh" /** * Namespace for the Scm (Guile) subsystem * */ namespace Mu::Scm { /** * Start a guile REPL or program * * Initialize the Scm sub-system, then start a REPL or run a script, * based on the configuration. * * Unless 'blocking' is false or there is some pre-guile error, this * method never returns. If blocking is false, it runs in the * background. * * @param store a Store object * @param opts options * @param blocking whether to block (or run in the background) * * @return Ok() or some error */ Result run(const Store& store, const Options& opts, bool blocking=true); /** * Helpers * * @{*/ // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2593r0.html template struct always_false : std::false_type {}; template constexpr bool is_char_array_v = std::is_array_v && std::is_same_v, char>; /** * C++ Exception to capture an SCM exception * * An ScmError is a C++ exception that captures the information for a * Scm exception to be thrown later at some convenient moment (_after_ * an orderly finishing of the block, so DTORs etc can run.) */ struct ScmError { enum struct Id { WrongType, WrongArg, Error }; ScmError(Id id, const char *subr, int pos, SCM bad_val, const char *expected): id{Id::WrongType}, subr{subr}, pos(pos), bad_val{bad_val}, expected{expected} {} ScmError(const char *subr, const char *msg): id{Id::Error}, subr{subr}, msg{msg} {} /** * This will do a "non-local exit", ie. does not return. * */ [[noreturn]] void throw_scm() const { /* Enforce exhaustive switch (do _not_ add a default case) */ #pragma GCC diagnostic push #pragma GCC diagnostic error "-Wswitch" switch(id) { case Id::WrongType: case Id::WrongArg: scm_wrong_type_arg_msg(subr, pos, bad_val, expected); break; case Id::Error: scm_misc_error(subr, msg, SCM_BOOL_F); break; #pragma GCC diagnostic ignored "-Wswitch-default" #pragma GCC diagnostic pop } throw; // never reached, just to appease compiler. } private: const Id id; const char *subr{}; int pos; SCM bad_val; const char *expected; const char *msg{}; }; /** * Make SCM symbol from string-like value * * @param val some value * * @return an SCM symbol */ template SCM make_symbol(const T& val){ using Type = std::remove_const_t; // *not* std::remove_const if constexpr (std::is_same_v || std::is_same_v) return scm_from_utf8_symboln(val.data(), val.size()); else if constexpr (is_char_array_v|| std::is_same_v) return scm_from_utf8_symbol(val); else { static_assert(always_false::value, "source type not supported"); return SCM_UNSPECIFIED; } } /** * Get some C++ value from an SCM object, generically. * * This throws ScmError in case of any errors. * * @param ARG some SCM object * @param func function where this is used * @param pos argument number (starting from 1) * @param expected the expected type * * @return C++ value */ template T from_scm(SCM ARG, const char *func, int pos) { const auto ensure=[&](bool pred, SCM ARG, const char *expected) { if (!pred) throw ScmError{ScmError::Id::WrongType, func, pos, ARG, expected}; }; using Type = std::remove_const_t; // *not* std::remove_const if constexpr (std::is_same_v) { ensure(scm_string_p(ARG), ARG, "string"); auto str{scm_to_utf8_string(ARG)}; std::string res{str}; ::free(str); return res; } else if constexpr (std::is_same_v) { ensure(scm_char_p(ARG), ARG, "character"); return scm_to_char(ARG); } else if constexpr (std::is_same_v) { ensure(scm_boolean_p(ARG), ARG, "bool"); return scm_to_bool(ARG); } else if constexpr (std::is_same_v) { ensure(scm_is_signed_integer(ARG, std::numeric_limits::min(), std::numeric_limits::max()), ARG, "integer"); return scm_to_int(ARG); } else if constexpr (std::is_same_v) { ensure(scm_is_unsigned_integer(ARG, std::numeric_limits::min(), std::numeric_limits::max()), ARG, "unsigned"); return scm_to_uint(ARG); } else if constexpr (std::is_same_v) { return ARG; } else { static_assert(always_false::value, "target type not supported"); return {}; } } /** * Like from_SCM, but if ARG is boolean false, return default value. * * @param ARG argument * @param default_value default value * * @return value */ template T from_scm_with_default(SCM ARG, const T default_value, const char *func, int pos) { return (scm_is_bool(ARG) && scm_is_false(ARG)) ? default_value : from_scm(ARG, func, pos); } /** * Get some SCM from a C++ value, generically. * * @param val some C++ object * * @return an SCM */ template SCM to_scm(const T& val) { using Type = std::remove_const_t; if constexpr (std::is_same_v || std::is_same_v) return scm_from_utf8_stringn(val.data(), val.size()); else if constexpr (is_char_array_v|| std::is_same_v) return scm_from_utf8_string(val); else if constexpr (std::is_same_v>) { SCM lst{SCM_EOL}; for (auto it = val.end(); it-- != val.begin();) lst = scm_cons(to_scm(*it), lst); return lst; } else if constexpr (std::is_same_v) return scm_from_bool(val); else if constexpr (std::is_same_v) return scm_from_size_t(val); else if constexpr (std::is_same_v) return scm_from_int(val); else if constexpr (std::is_same_v) return scm_from_int(static_cast(val)); else if constexpr (std::is_same_v) return scm_from_int64(val); else if constexpr (std::is_same_v) return scm_from_uint64(val); else if constexpr (std::is_same_v) return val; else { static_assert(always_false::value, "source type not supported"); return SCM_UNSPECIFIED; } } // base case. static inline SCM alist_add(SCM alist) { return alist; } /** * Add key-value pair to an alist * * This assumes that keys are unique ("acons") * * @param alist some alist * @param key key * @param val value * @param keyvals... 0 or more key, value parmeters * * @return the updated alist */ template static inline SCM alist_add(SCM alist, const Key& key, const Value& val, KeyVals... keyvals) { SCM res = scm_acons(to_scm(key), to_scm(val), alist); return alist_add(res, std::forward(keyvals)...); } //template static inline SCM try_scm(scm_t_catch_body func, scm_t_catch_handler handler) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" return scm_internal_catch( SCM_BOOL_F, func, SCM_BOOL_F, handler, SCM_BOOL_F); #pragma GCC diagnostic pop } /**@}*/ } #endif /*MU_SCM_HH*/