drop libostd requirement entirely

master
Daniel Kolesa 2021-03-20 08:22:15 +01:00
parent cb926a5750
commit 82d366366e
11 changed files with 95 additions and 96 deletions

View File

@ -27,8 +27,8 @@ advantages, including:
* Independent implementation (can be embedded in any project) * Independent implementation (can be embedded in any project)
* No global state (multiple CubeScripts in a single program) * No global state (multiple CubeScripts in a single program)
* Modern C++17 API (no macros, use of strongly typed enums, lambdas, ranges etc.) * Modern C++20 API
* C++17 lambdas can be used as commands (including captures and type inference) * C++ lambdas can be used as commands (including captures and type inference)
* Error handling including recovery (protected call system similar to Lua) * Error handling including recovery (protected call system similar to Lua)
* Stricter parsing (strings cannot be left unfinished etc.) * Stricter parsing (strings cannot be left unfinished etc.)
* Loop control statements (`break` and `continue`) * Loop control statements (`break` and `continue`)
@ -70,13 +70,8 @@ utilized in the outside native code.
## Building and usage ## Building and usage
The only dependency is libostd: There are no dependencies (other than a suitable compiler and the standard
library).
https://git.octaforge.org/OctaForge/libostd
https://github.com/OctaForge/libostd
If libostd can work on your system, so can libcubescript.
Libostd is built using Meson. Therefore, you need to install Meson and then Libostd is built using Meson. Therefore, you need to install Meson and then
you can compile it as usual. Typically, this will be something like you can compile it as usual. Typically, this will be something like
@ -84,16 +79,17 @@ you can compile it as usual. Typically, this will be something like
~~~ ~~~
mkdir build && cd build mkdir build && cd build
meson .. meson ..
meson compile ninja all
~~~ ~~~
Link the libcubescript library together with your application and everything should just work. Link the libcubescript library together with your application and everything
It also builds the REPL. should just work. It also builds the REPL.
The project also bundles the linenoise line editing library which has been modified The project also bundles the linenoise line editing library which has been
to compile cleanly as C++ (with the same flags as libcubescript). It's used strictly modified to compile cleanly as C++ (with the same flags as libcubescript).
for the REPL only (you don't need it to build libcubescript itself). The version It's used strictly for the REPL only (you don't need it to build libcubescript
in the repository tracks Git revision https://github.com/antirez/linenoise/commit/c894b9e59f02203dbe4e2be657572cf88c4230c3. itself). The version in the repository tracks Git revision
https://github.com/antirez/linenoise/commit/c894b9e59f02203dbe4e2be657572cf88c4230c3.
## Licensing ## Licensing

View File

@ -598,12 +598,12 @@ struct cs_error {
} }
template<typename ...A> template<typename ...A>
cs_error(cs_state &cs, std::string_view msg, A &&...args): cs_error(cs_state &cs, std::string_view msg, A const &...args):
p_errmsg(), p_stack(cs) p_errmsg(), p_stack(cs)
{ {
char fbuf[512]; char fbuf[512];
int written = std::snprintf( int written = std::snprintf(
fbuf, sizeof(fbuf), "%.*s", int(msg.size()), msg.data() fbuf, sizeof(fbuf), msg.data(), args...
); );
if (written >= int(sizeof(fbuf))) { if (written >= int(sizeof(fbuf))) {
written = std::strlen(fbuf); written = std::strlen(fbuf);

View File

@ -110,8 +110,8 @@ cs_stack_state cs_error::save_stack(cs_state &cs) {
std::string_view cs_error::save_msg( std::string_view cs_error::save_msg(
cs_state &cs, std::string_view msg cs_state &cs, std::string_view msg
) { ) {
if (msg.size() > sizeof(cs.p_errbuf)) { if (msg.size() >= sizeof(cs.p_errbuf)) {
msg = msg.substr(0, sizeof(cs.p_errbuf)); msg = msg.substr(0, sizeof(cs.p_errbuf) - 1);
} }
cs_gen_state *gs = cs.p_pstate; cs_gen_state *gs = cs.p_pstate;
if (gs) { if (gs) {
@ -137,6 +137,7 @@ std::string_view cs_error::save_msg(
return std::string_view{cs.p_errbuf, std::size_t(sz)}; return std::string_view{cs.p_errbuf, std::size_t(sz)};
} }
memcpy(cs.p_errbuf, msg.data(), msg.size()); memcpy(cs.p_errbuf, msg.data(), msg.size());
cs.p_errbuf[msg.size()] = '\0';
return std::string_view{cs.p_errbuf, msg.size()}; return std::string_view{cs.p_errbuf, msg.size()};
} }
@ -527,7 +528,7 @@ struct RunDepthRef {
static inline cs_alias *cs_get_lookup_id(cs_state &cs, uint32_t op) { static inline cs_alias *cs_get_lookup_id(cs_state &cs, uint32_t op) {
cs_ident *id = cs.p_state->identmap[op >> 8]; cs_ident *id = cs.p_state->identmap[op >> 8];
if (id->get_flags() & CS_IDF_UNKNOWN) { if (id->get_flags() & CS_IDF_UNKNOWN) {
throw cs_error(cs, "unknown alias lookup: %s", id->get_name()); throw cs_error(cs, "unknown alias lookup: %s", id->get_name().data());
} }
return static_cast<cs_alias *>(id); return static_cast<cs_alias *>(id);
} }
@ -574,7 +575,7 @@ static inline int cs_get_lookupu_type(
return CsIdUnknown; return CsIdUnknown;
} }
} }
throw cs_error(cs, "unknown alias lookup: %s", arg.get_str()); throw cs_error(cs, "unknown alias lookup: %s", arg.get_str().data());
} }
static uint32_t *runcode(cs_state &cs, uint32_t *code, cs_value &result) { static uint32_t *runcode(cs_state &cs, uint32_t *code, cs_value &result) {
@ -1421,7 +1422,7 @@ static uint32_t *runcode(cs_state &cs, uint32_t *code, cs_value &result) {
if (id->get_flags() & CS_IDF_UNKNOWN) { if (id->get_flags() & CS_IDF_UNKNOWN) {
force_arg(result, op & CS_CODE_RET_MASK); force_arg(result, op & CS_CODE_RET_MASK);
throw cs_error( throw cs_error(
cs, "unknown command: %s", id->get_name() cs, "unknown command: %s", id->get_name().data()
); );
} }
cs_call_alias( cs_call_alias(
@ -1471,8 +1472,9 @@ noid:
} }
result.force_none(); result.force_none();
force_arg(result, op & CS_CODE_RET_MASK); force_arg(result, op & CS_CODE_RET_MASK);
std::string_view ids{idn};
throw cs_error( throw cs_error(
cs, "unknown command: %s", std::string_view{idn} cs, "unknown command: %s", ids.data()
); );
} }
result.force_none(); result.force_none();

View File

@ -474,7 +474,7 @@ LIBCUBESCRIPT_EXPORT cs_ident *cs_state::new_ident(std::string_view name, int fl
if (!id) { if (!id) {
if (cs_check_num(name)) { if (cs_check_num(name)) {
throw cs_error( throw cs_error(
*this, "number %s is not a valid identifier name", name *this, "number %s is not a valid identifier name", name.data()
); );
} }
id = add_ident(p_state->create<cs_alias>( id = add_ident(p_state->create<cs_alias>(
@ -560,10 +560,10 @@ LIBCUBESCRIPT_EXPORT cs_svar *cs_state::new_svar(
LIBCUBESCRIPT_EXPORT void cs_state::reset_var(std::string_view name) { LIBCUBESCRIPT_EXPORT void cs_state::reset_var(std::string_view name) {
cs_ident *id = get_ident(name); cs_ident *id = get_ident(name);
if (!id) { if (!id) {
throw cs_error(*this, "variable %s does not exist", name); throw cs_error(*this, "variable %s does not exist", name.data());
} }
if (id->get_flags() & CS_IDF_READONLY) { if (id->get_flags() & CS_IDF_READONLY) {
throw cs_error(*this, "variable %s is read only", name); throw cs_error(*this, "variable %s is read only", name.data());
} }
clear_override(*id); clear_override(*id);
} }
@ -600,11 +600,11 @@ LIBCUBESCRIPT_EXPORT void cs_state::set_alias(std::string_view name, cs_value v)
default: default:
throw cs_error( throw cs_error(
*this, "cannot redefine builtin %s with an alias", *this, "cannot redefine builtin %s with an alias",
id->get_name() id->get_name().data()
); );
} }
} else if (cs_check_num(name)) { } else if (cs_check_num(name)) {
throw cs_error(*this, "cannot alias number %s", name); throw cs_error(*this, "cannot alias number %s", name.data());
} else { } else {
add_ident(p_state->create<cs_alias>( add_ident(p_state->create<cs_alias>(
*this, cs_strref{*p_state, name}, std::move(v), identflags *this, cs_strref{*p_state, name}, std::move(v), identflags
@ -657,7 +657,8 @@ static inline void cs_override_var(cs_state &cs, cs_var *v, int &vflags, SF sf)
if ((cs.identflags & CS_IDF_OVERRIDDEN) || (vflags & CS_IDF_OVERRIDE)) { if ((cs.identflags & CS_IDF_OVERRIDDEN) || (vflags & CS_IDF_OVERRIDE)) {
if (vflags & CS_IDF_PERSIST) { if (vflags & CS_IDF_PERSIST) {
throw cs_error( throw cs_error(
cs, "cannot override persistent variable '%s'", v->get_name() cs, "cannot override persistent variable '%s'",
v->get_name().data()
); );
} }
if (!(vflags & CS_IDF_OVERRIDDEN)) { if (!(vflags & CS_IDF_OVERRIDDEN)) {
@ -825,14 +826,14 @@ cs_int cs_clamp_var(cs_state &cs, cs_ivar *iv, cs_int v) {
: "valid range for '%s' is 0x%X..0x%X" : "valid range for '%s' is 0x%X..0x%X"
) )
: "valid range for '%s' is %d..%d", : "valid range for '%s' is %d..%d",
iv->get_name(), iv->get_val_min(), iv->get_val_max() iv->get_name().data(), iv->get_val_min(), iv->get_val_max()
); );
} }
LIBCUBESCRIPT_EXPORT void cs_state::set_var_int_checked(cs_ivar *iv, cs_int v) { LIBCUBESCRIPT_EXPORT void cs_state::set_var_int_checked(cs_ivar *iv, cs_int v) {
if (iv->get_flags() & CS_IDF_READONLY) { if (iv->get_flags() & CS_IDF_READONLY) {
throw cs_error( throw cs_error(
*this, "variable '%s' is read only", iv->get_name() *this, "variable '%s' is read only", iv->get_name().data()
); );
} }
cs_override_var( cs_override_var(
@ -871,7 +872,7 @@ cs_float cs_clamp_fvar(cs_state &cs, cs_fvar *fv, cs_float v) {
vmin.set_float(fv->get_val_min()); vmin.set_float(fv->get_val_min());
vmax.set_float(fv->get_val_max()); vmax.set_float(fv->get_val_max());
throw cs_error( throw cs_error(
cs, "valid range for '%s' is %s..%s", fv->get_name(), cs, "valid range for '%s' is %s..%s", fv->get_name().data(),
vmin.force_str(), vmax.force_str() vmin.force_str(), vmax.force_str()
); );
return v; return v;
@ -880,7 +881,7 @@ cs_float cs_clamp_fvar(cs_state &cs, cs_fvar *fv, cs_float v) {
LIBCUBESCRIPT_EXPORT void cs_state::set_var_float_checked(cs_fvar *fv, cs_float v) { LIBCUBESCRIPT_EXPORT void cs_state::set_var_float_checked(cs_fvar *fv, cs_float v) {
if (fv->get_flags() & CS_IDF_READONLY) { if (fv->get_flags() & CS_IDF_READONLY) {
throw cs_error( throw cs_error(
*this, "variable '%s' is read only", fv->get_name() *this, "variable '%s' is read only", fv->get_name().data()
); );
} }
cs_override_var( cs_override_var(
@ -899,7 +900,7 @@ LIBCUBESCRIPT_EXPORT void cs_state::set_var_str_checked(
) { ) {
if (sv->get_flags() & CS_IDF_READONLY) { if (sv->get_flags() & CS_IDF_READONLY) {
throw cs_error( throw cs_error(
*this, "variable '%s' is read only", sv->get_name() *this, "variable '%s' is read only", sv->get_name().data()
); );
} }
cs_override_var( cs_override_var(

View File

@ -14,11 +14,8 @@ libcubescript_src = [
'lib_str.cc' 'lib_str.cc'
] ]
libostd_dep = dependency('libostd', fallback: ['libostd', 'libostd_static'])
libcubescript_lib = both_libraries('cubescript', libcubescript_lib = both_libraries('cubescript',
libcubescript_src, libcubescript_src,
dependencies: libostd_dep,
include_directories: libcubescript_includes + [include_directories('.')], include_directories: libcubescript_includes + [include_directories('.')],
cpp_args: extra_cxxflags, cpp_args: extra_cxxflags,
install: true, install: true,

View File

@ -1,4 +0,0 @@
[wrap-git]
directory = libostd
url = https://git.octaforge.org/OctaForge/libostd.git
revision = head

View File

@ -2,15 +2,23 @@
/* use nothing (no line editing support) */ /* use nothing (no line editing support) */
#include <optional> #include <optional>
#include <string>
#include <ostd/string.hh>
inline void init_lineedit(cs_state &, std::string_view) { inline void init_lineedit(cs_state &, std::string_view) {
} }
inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) { inline std::optional<std::string> read_line(cs_state &, cs_svar *pr) {
ostd::write(pr->get_value()); std::string lbuf;
return ostd::cin.get_line(ostd::appender<std::string>()).get(); char buf[512];
printf("%s", pr->get_value().data());
std::fflush(stdout);
while (fgets(buf, sizeof(buf), stdin)) {
lbuf += static_cast<char const *>(buf);
if (strchr(buf, '\n')) {
break;
}
}
return std::move(lbuf);
} }
inline void add_history(cs_state &, std::string_view) { inline void add_history(cs_state &, std::string_view) {

View File

@ -1,5 +1,5 @@
#ifdef CS_REPL_USE_LINENOISE #ifdef CS_REPL_USE_LINENOISE
#ifdef OSTD_PLATFORM_POSIX #ifndef _WIN32
#ifndef CS_REPL_HAS_EDIT #ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT #define CS_REPL_HAS_EDIT
/* use the bundled linenoise library, default */ /* use the bundled linenoise library, default */
@ -10,8 +10,6 @@
#include <optional> #include <optional>
#include <ostd/string.hh>
#include "linenoise.hh" #include "linenoise.hh"
static cs_state *ln_cs = nullptr; static cs_state *ln_cs = nullptr;

View File

@ -3,12 +3,9 @@
#define CS_REPL_HAS_EDIT #define CS_REPL_HAS_EDIT
/* use the GNU readline library */ /* use the GNU readline library */
#include <string.h> #include <cstring>
#include <optional> #include <optional>
#include <ostd/string.hh>
#include <readline/readline.h> #include <readline/readline.h>
#include <readline/history.h> #include <readline/history.h>
@ -16,15 +13,14 @@ static cs_state *rd_cs = nullptr;
inline char *ln_complete_list(char const *buf, int state) { inline char *ln_complete_list(char const *buf, int state) {
static std::string_view cmd; static std::string_view cmd;
static ostd::iterator_range<cs_ident **> itr; static std::span<cs_ident *> itr;
if (!state) { if (!state) {
cmd = get_complete_cmd(buf); cmd = get_complete_cmd(buf);
itr = rd_cs->get_idents(); itr = rd_cs->get_idents();
} }
for (; !itr.empty(); itr.pop_front()) { for (cs_ident *id: itr) {
cs_ident *id = itr.front();
if (!id->is_command()) { if (!id->is_command()) {
continue; continue;
} }
@ -33,7 +29,7 @@ inline char *ln_complete_list(char const *buf, int state) {
continue; continue;
} }
if (idname.substr(0, cmd.size()) == cmd) { if (idname.substr(0, cmd.size()) == cmd) {
itr.pop_front(); ++itr;
return strdup(idname.data()); return strdup(idname.data());
} }
} }

View File

@ -2,7 +2,7 @@ repl_src = [
'repl.cc' 'repl.cc'
] ]
repl_deps = [libostd_dep, libcubescript] repl_deps = [libcubescript]
repl_flags = [] repl_flags = []
if get_option('readline') if get_option('readline')

View File

@ -1,13 +1,12 @@
#include <signal.h> #include <signal.h>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <optional> #include <optional>
#include <memory> #include <memory>
#include <iterator> #include <iterator>
#include <ostd/platform.hh>
#include <ostd/io.hh>
#include <ostd/string.hh>
#include <cubescript/cubescript.hh> #include <cubescript/cubescript.hh>
using namespace cscript; using namespace cscript;
@ -16,7 +15,7 @@ std::string_view version = "CubeScript 0.0.1";
/* util */ /* util */
#ifdef OSTD_PLATFORM_WIN32 #if defined(_WIN32)
#include <io.h> #include <io.h>
static bool stdin_is_tty() { static bool stdin_is_tty() {
return _isatty(_fileno(stdin)); return _isatty(_fileno(stdin));
@ -165,9 +164,9 @@ inline cs_command *get_hint_cmd(cs_state &cs, std::string_view buf) {
/* usage */ /* usage */
void print_usage(std::string_view progname, bool err) { void print_usage(std::string_view progname, bool err) {
auto &s = err ? ostd::cerr : ostd::cout; std::fprintf(
s.writeln( err ? stderr : stdout,
"Usage: ", progname, " [options] [file]\n" "Usage: %s [options] [file]\n"
"Options:\n" "Options:\n"
" -e str run string \"str\"\n" " -e str run string \"str\"\n"
" -i enter interactive mode after the above\n" " -i enter interactive mode after the above\n"
@ -175,12 +174,13 @@ void print_usage(std::string_view progname, bool err) {
" -h show this message\n" " -h show this message\n"
" -- stop handling options\n" " -- stop handling options\n"
" - execute stdin and stop handling options" " - execute stdin and stop handling options"
"\n",
progname.data()
); );
s.flush();
} }
void print_version() { void print_version() {
ostd::writeln(version); printf("%s\n", version.data());
} }
static cs_state *scs = nullptr; static cs_state *scs = nullptr;
@ -200,33 +200,35 @@ static void repl_print_var(cs_state const &cs, cs_var const &var) {
auto &iv = static_cast<cs_ivar const &>(var); auto &iv = static_cast<cs_ivar const &>(var);
auto val = iv.get_value(); auto val = iv.get_value();
if (!(iv.get_flags() & CS_IDF_HEX) || (val < 0)) { if (!(iv.get_flags() & CS_IDF_HEX) || (val < 0)) {
ostd::writefln("%s = %d", iv.get_name(), val); std::printf("%s = %d\n", iv.get_name().data(), val);
} else if (iv.get_val_max() == 0xFFFFFF) { } else if (iv.get_val_max() == 0xFFFFFF) {
ostd::writefln( std::printf(
"%s = 0x%.6X (%d, %d, %d)", iv.get_name(), "%s = 0x%.6X (%d, %d, %d)\n",
iv.get_name().data(),
val, (val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF val, (val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF
); );
} else { } else {
ostd::writefln("%s = 0x%X", iv.get_name(), val); std::printf("%s = 0x%X\n", iv.get_name().data(), val);
} }
break; break;
} }
case cs_ident_type::FVAR: { case cs_ident_type::FVAR: {
auto &fv = static_cast<cs_fvar const &>(var); auto &fv = static_cast<cs_fvar const &>(var);
auto val = fv.get_value(); auto val = fv.get_value();
ostd::writefln( if (std::floor(val) == val) {
(floor(val) == val) ? "%s = %.1f" : "%s = %.7g", std::printf("%s = %.1f", fv.get_name().data(), val);
fv.get_name(), val } else {
); std::printf("%s = %.7g", fv.get_name().data(), val);
}
break; break;
} }
case cs_ident_type::SVAR: { case cs_ident_type::SVAR: {
auto &sv = static_cast<cs_svar const &>(var); auto &sv = static_cast<cs_svar const &>(var);
auto val = std::string_view{sv.get_value()}; auto val = std::string_view{sv.get_value()};
if (val.find('"') == val.npos) { if (val.find('"') == val.npos) {
ostd::writefln("%s = \"%s\"", sv.get_name(), val); std::printf("%s = \"%s\"", sv.get_name().data(), val.data());
} else { } else {
ostd::writefln("%s = [%s]", sv.get_name(), val); std::printf("%s = [%s]", sv.get_name().data(), val.data());
} }
break; break;
} }
@ -236,28 +238,29 @@ static void repl_print_var(cs_state const &cs, cs_var const &var) {
} }
static bool do_run_file(cs_state &cs, std::string_view fname, cs_value &ret) { static bool do_run_file(cs_state &cs, std::string_view fname, cs_value &ret) {
std::unique_ptr<char[]> buf; FILE *f = std::fopen(fname.data(), "rb");
std::size_t len; if (!f) {
ostd::file_stream f{fname, ostd::stream_mode::READ};
if (!f.is_open()) {
return false; return false;
} }
len = f.size(); std::fseek(f, 0, SEEK_END);
buf = std::make_unique<char[]>(len + 1); auto len = std::ftell(f);
std::fseek(f, 0, SEEK_SET);
auto buf = std::make_unique<char[]>(len + 1);
if (!buf) { if (!buf) {
std::fclose(f);
return false; return false;
} }
try { if (std::fread(buf.get(), 1, len, f) != std::size_t(len)) {
f.get(buf.get(), len); std::fclose(f);
} catch (...) {
return false; return false;
} }
buf[len] = '\0'; buf[len] = '\0';
cs.run(std::string_view{buf.get(), len}, ret, fname); cs.run(std::string_view{buf.get(), std::size_t(len)}, ret, fname);
return true; return true;
} }
@ -268,7 +271,7 @@ static bool do_call(cs_state &cs, std::string_view line, bool file = false) {
try { try {
if (file) { if (file) {
if (!do_run_file(cs, line, ret)) { if (!do_run_file(cs, line, ret)) {
ostd::cerr.writeln("cannot read file: ", line); std::fprintf(stderr, "cannot read file: %s\n", line.data());
} }
} else { } else {
cs.run(line, ret); cs.run(line, ret);
@ -291,18 +294,20 @@ static bool do_call(cs_state &cs, std::string_view line, bool file = false) {
if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) { if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) {
return true; return true;
} }
ostd::writeln(!is_lnum ? "stdin: " : "stdin:", e.what()); std::printf(
"%s%s\n", !is_lnum ? "stdin: " : "stdin:", e.what().data()
);
if (e.get_stack().get()) { if (e.get_stack().get()) {
std::string str; std::string str;
cscript::util::print_stack(std::back_inserter(str), e.get_stack()); cscript::util::print_stack(std::back_inserter(str), e.get_stack());
ostd::writeln(str); std::printf("%s\n", str.data());
} }
return false; return false;
} }
signal(SIGINT, SIG_DFL); signal(SIGINT, SIG_DFL);
scs = nullptr; scs = nullptr;
if (ret.get_type() != cs_value_type::NONE) { if (ret.get_type() != cs_value_type::NONE) {
ostd::writeln(std::string_view{ret.get_str()}); std::printf("%s\n", std::string_view{ret.get_str()}.data());
} }
return false; return false;
} }
@ -316,7 +321,7 @@ static void do_tty(cs_state &cs) {
do_exit = true; do_exit = true;
}); });
ostd::writeln(version, " (REPL mode)"); std::printf("%s (REPL mode)\n", version.data());
for (;;) { for (;;) {
auto line = read_line(cs, prompt); auto line = read_line(cs, prompt);
if (!line) { if (!line) {
@ -364,7 +369,7 @@ int main(int argc, char **argv) {
}); });
gcs.new_command("echo", "C", [](auto &, auto args, auto &) { gcs.new_command("echo", "C", [](auto &, auto args, auto &) {
ostd::writeln(std::string_view{args[0].get_str()}); std::printf("%s\n", std::string_view{args[0].get_str()}.data());
}); });
int firstarg = 0; int firstarg = 0;
@ -450,7 +455,7 @@ endargs:
return 0; return 0;
} else { } else {
std::string str; std::string str;
for (signed char c = '\0'; (c = ostd::cin.get_char()) != EOF;) { for (signed char c = '\0'; (c = std::fgetc(stdin)) != EOF;) {
str += c; str += c;
} }
do_call(gcs, str); do_call(gcs, str);