From 51b2dc6b0c969f9297c8464cb91e93d65819e0ae Mon Sep 17 00:00:00 2001 From: "Jay Freeman (saurik)" Date: Wed, 9 Dec 2015 01:20:17 -0800 Subject: [PATCH] Allow multi-line editing and drop libedit support. --- Console.cpp | 336 +++++++++++++++++++++++++++++++++++-------------- Display.cpp | 162 ------------------------ Display.hpp | 27 ---- Highlight.cpp | 4 +- Location.hpp | 4 +- Makefile.am | 2 +- Makefile.in | 8 +- Scanner.lpp.in | 12 +- Syntax.hpp | 6 +- 9 files changed, 257 insertions(+), 304 deletions(-) delete mode 100644 Display.cpp delete mode 100644 Display.hpp diff --git a/Console.cpp b/Console.cpp index 3d0c5de..997168d 100644 --- a/Console.cpp +++ b/Console.cpp @@ -26,11 +26,10 @@ #endif #include +#include #include #include -#include - #ifdef HAVE_READLINE_H #include #else @@ -45,6 +44,7 @@ #include #include +#include #include #include @@ -54,24 +54,154 @@ #include #include +#include #include #include #include #include -#include #include +#include +#include #ifdef __APPLE__ #include #endif -#include "Display.hpp" #include "Driver.hpp" #include "Error.hpp" #include "Highlight.hpp" #include "Syntax.hpp" +extern "C" int rl_display_fixed; +extern "C" int _rl_vis_botlin; +extern "C" int _rl_last_c_pos; +extern "C" int _rl_last_v_pos; + +typedef std::complex CYCursor; + +static CYCursor current_; +static int width_; +static size_t point_; + +unsigned CYDisplayWidth() { + struct winsize info; + if (ioctl(1, TIOCGWINSZ, &info) != -1) + return info.ws_col; + return tgetnum(const_cast("co")); +} + +void CYDisplayOutput_(bool display, const char *&data) { + for (;; ++data) { + char next(*data); + if (next == '\0' || next == CYIgnoreEnd) + return; + if (display) + putchar(next); + } +} + +CYCursor CYDisplayOutput(bool display, int width, const char *data, ssize_t offset = 0) { + CYCursor point(current_); + + for (;;) { + if (offset-- == 0) + point = current_; + switch (char next = *data++) { + case '\0': + return point; + break; + + case CYIgnoreStart: + CYDisplayOutput_(display, data); + case CYIgnoreEnd: + ++offset; + break; + + default: + if (display) + putchar(next); + current_ += CYCursor(0, 1); + if (current_.imag() != width) + break; + current_ = CYCursor(current_.real() + 1, 0); + if (display) + putp(clr_eos); + break; + + case '\n': + current_ = CYCursor(current_.real() + 1, 4); + if (display) { + putp(clr_eol); + putchar('\n'); + putchar(' '); + putchar(' '); + putchar(' '); + putchar(' '); + } + break; + + } + } +} + +void CYDisplayMove_(char *negative, char *positive, int offset) { + if (offset < 0) + putp(tparm(negative, -offset)); + else if (offset > 0) + putp(tparm(positive, offset)); +} + +void CYDisplayMove(CYCursor target) { + CYCursor offset(target - current_); + + CYDisplayMove_(parm_up_cursor, parm_down_cursor, offset.real()); + + if (char *parm = tparm(column_address, target.imag())) + putp(parm); + else + CYDisplayMove_(parm_left_cursor, parm_right_cursor, offset.imag()); + + current_ = target; +} + +void CYDisplayUpdate() { + current_ = CYCursor(_rl_last_v_pos, _rl_last_c_pos); + + const char *prompt(rl_display_prompt); + + std::ostringstream stream; + CYLexerHighlight(rl_line_buffer, rl_end, stream, true); + std::string string(stream.str()); + const char *buffer(string.c_str()); + + int width(CYDisplayWidth()); + if (width_ != width) { + current_ = CYCursor(); + CYDisplayOutput(false, width, prompt); + current_ = CYDisplayOutput(false, width, buffer, point_); + } + + CYDisplayMove(CYCursor()); + CYDisplayOutput(true, width, prompt); + CYCursor target(CYDisplayOutput(true, width, stream.str().c_str(), rl_point)); + + _rl_vis_botlin = current_.real(); + + if (current_.imag() == 0) + CYDisplayOutput(true, width, " "); + putp(clr_eos); + + CYDisplayMove(target); + fflush(stdout); + + _rl_last_v_pos = current_.real(); + _rl_last_c_pos = current_.imag(); + + width_ = width; + point_ = rl_point; +} + static volatile enum { Working, Parsing, @@ -205,7 +335,8 @@ static CYUTF8String Run(CYPool &pool, const std::string &code) { static char **Complete(const char *word, int start, int end) { rl_attempted_completion_over = ~0; std::string line(rl_line_buffer, start); - return CYComplete(word, command_ + line, &Run); + char **values(CYComplete(word, command_ + line, &Run)); + return values; } // need char *, not const char * @@ -223,9 +354,17 @@ class History { histlines_(0) { read_history(histfile_.c_str()); + + for (HIST_ENTRY *history((history_set_pos(0), current_history())); history; history = next_history()) + for (char *character(history->line); *character; ++character) + if (*character == '\x01') *character = '\n'; } ~History() { + for (HIST_ENTRY *history((history_set_pos(0), current_history())); history; history = next_history()) + for (char *character(history->line); *character; ++character) + if (*character == '\n') *character = '\x01'; + if (append_history$ != NULL) { int fd(_syscall(open(histfile_.c_str(), O_CREAT | O_WRONLY, 0600))); _syscall(close(fd)); @@ -235,12 +374,42 @@ class History { } } - void operator +=(const std::string &command) { + void operator +=(std::string command) { add_history(command.c_str()); ++histlines_; } }; +static int CYConsoleKeyReturn(int count, int key) { + rl_insert(count, '\n'); + + if (rl_end != 0 && rl_point == rl_end && rl_line_buffer[0] == '?') + rl_done = 1; + else if (rl_point == rl_end) { + std::string command(rl_line_buffer, rl_end); + std::istringstream stream(command); + + size_t last(std::string::npos); + for (size_t i(0); i != std::string::npos; i = command.find('\n', i + 1)) + ++last; + + CYPool pool; + CYDriver driver(pool, stream); + if (driver.Parse() || !driver.errors_.empty()) + for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) { + if (error->location_.begin.line != last + 1) + rl_done = 1; + break; + } + else + rl_done = 1; + } + + if (rl_done) + std::cout << std::endl; + return 0; +} + static void Console(CYOptions &options) { std::string basedir; if (const char *home = getenv("HOME")) @@ -269,16 +438,11 @@ static void Console(CYOptions &options) { out_ = &std::cout; - // rl_completer_word_break_characters is broken in libedit - rl_basic_word_break_characters = break_; - rl_completer_word_break_characters = break_; rl_attempted_completion_function = &Complete; rl_bind_key('\t', rl_complete); -#if RL_READLINE_VERSION >= 0x0600 rl_redisplay_function = CYDisplayUpdate; -#endif struct sigaction action; sigemptyset(&action.sa_mask); @@ -286,124 +450,104 @@ static void Console(CYOptions &options) { action.sa_flags = 0; sigaction(SIGINT, &action, NULL); - restart: for (;;) { - command_.clear(); - std::vector lines; - - bool extra(false); - const char *prompt("cy# "); - + for (;;) { if (setjmp(ctrlc_) != 0) { mode_ = Working; *out_ << std::endl; - goto restart; + continue; } - read: + if (bypass) + rl_bind_key('\r', &rl_newline); + else + rl_bind_key('\r', &CYConsoleKeyReturn); + mode_ = Parsing; - char *line(readline(prompt)); + char *line(readline("cy# ")); mode_ = Working; if (line == NULL) { *out_ << std::endl; break; - } else if (line[0] == '\0') - goto read; - - if (!extra) { - extra = true; - if (line[0] == '?') { - std::string data(line + 1); - if (data == "bypass") { - bypass = !bypass; - *out_ << "bypass == " << (bypass ? "true" : "false") << std::endl; - } else if (data == "debug") { - debug = !debug; - *out_ << "debug == " << (debug ? "true" : "false") << std::endl; - } else if (data == "destroy") { - CYDestroyContext(); - } else if (data == "gc") { - *out_ << "collecting... " << std::flush; - CYGarbageCollect(CYGetJSContext()); - *out_ << "done." << std::endl; - } else if (data == "exit") { - return; - } else if (data == "expand") { - expand = !expand; - *out_ << "expand == " << (expand ? "true" : "false") << std::endl; - } else if (data == "lower") { - lower = !lower; - *out_ << "lower == " << (lower ? "true" : "false") << std::endl; - } - command_ = line; - history += command_; - goto restart; - } } - command_ += line; - command_ += "\n"; + std::string command(line); + free(line); + _assert(!command.empty()); + _assert(command[command.size() - 1] == '\n'); + command.resize(command.size() - 1); + if (command.empty()) + continue; + + if (command[0] == '?') { + std::string data(command.substr(1)); + if (data == "bypass") { + bypass = !bypass; + *out_ << "bypass == " << (bypass ? "true" : "false") << std::endl; + } else if (data == "debug") { + debug = !debug; + *out_ << "debug == " << (debug ? "true" : "false") << std::endl; + } else if (data == "destroy") { + CYDestroyContext(); + } else if (data == "gc") { + *out_ << "collecting... " << std::flush; + CYGarbageCollect(CYGetJSContext()); + *out_ << "done." << std::endl; + } else if (data == "exit") { + return; + } else if (data == "expand") { + expand = !expand; + *out_ << "expand == " << (expand ? "true" : "false") << std::endl; + } else if (data == "lower") { + lower = !lower; + *out_ << "lower == " << (lower ? "true" : "false") << std::endl; + } - char *begin(line), *end(line + strlen(line)); - while (char *nl = reinterpret_cast(memchr(begin, '\n', end - begin))) { - *nl = '\0'; - lines.push_back(begin); - begin = nl + 1; + history += command; + continue; } - lines.push_back(begin); - - free(line); - std::string code; - if (bypass) - code = command_; + code = command; else { - std::istringstream stream(command_); + std::istringstream stream(command); CYPool pool; CYDriver driver(pool, stream); Setup(driver); - bool failed(driver.Parse()); - - if (failed || !driver.errors_.empty()) { + if (driver.Parse() || !driver.errors_.empty()) { for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) { CYPosition begin(error->location_.begin); - if (begin.line != lines.size() + 1 || error->warning_) { - CYPosition end(error->location_.end); - - if (begin.line != lines.size()) { - std::cerr << " | "; - std::cerr << lines[begin.line - 1] << std::endl; - } - - std::cerr << "...."; - for (size_t i(0); i != begin.column; ++i) - std::cerr << '.'; - if (begin.line != end.line || begin.column == end.column) - std::cerr << '^'; - else for (size_t i(0), e(end.column - begin.column); i != e; ++i) - std::cerr << '^'; - std::cerr << std::endl; + CYPosition end(error->location_.end); + /*if (begin.line != lines2.size()) { std::cerr << " | "; - std::cerr << error->message_ << std::endl; + std::cerr << lines2[begin.line - 1] << std::endl; + }*/ + + std::cerr << "...."; + for (size_t i(0); i != begin.column; ++i) + std::cerr << '.'; + if (begin.line != end.line || begin.column == end.column) + std::cerr << '^'; + else for (size_t i(0), e(end.column - begin.column); i != e; ++i) + std::cerr << '^'; + std::cerr << std::endl; - history += command_.substr(0, command_.size() - 1); - goto restart; - } - } + std::cerr << " | "; + std::cerr << error->message_ << std::endl; - driver.errors_.clear(); + history += command; + break; + } - prompt = "cy> "; - goto read; + continue; } if (driver.script_ == NULL) - goto restart; + continue; std::stringbuf str; CYOutput out(str, options); @@ -412,7 +556,7 @@ static void Console(CYOptions &options) { code = str.str(); } - history += command_.substr(0, command_.size() - 1); + history += command; if (debug) { std::cout << "cy= "; diff --git a/Display.cpp b/Display.cpp deleted file mode 100644 index 5227fa1..0000000 --- a/Display.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/* Cycript - Optimizing JavaScript Compiler/Runtime - * Copyright (C) 2009-2015 Jay Freeman (saurik) -*/ - -/* GNU Affero General Public License, Version 3 {{{ */ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, 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 Affero General Public License for more details. - - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . -**/ -/* }}} */ - -#include -#include - -#ifdef HAVE_READLINE_H -#include -#else -#include -#endif - -#if RL_READLINE_VERSION >= 0x0600 - -#include - -#include - -#include "Highlight.hpp" - -typedef std::complex CYCursor; - -extern "C" int rl_display_fixed; -extern "C" int _rl_vis_botlin; -extern "C" int _rl_last_c_pos; -extern "C" int _rl_last_v_pos; - -CYCursor current_; -int width_; -size_t point_; - -unsigned CYDisplayWidth() { - struct winsize info; - if (ioctl(1, TIOCGWINSZ, &info) != -1) - return info.ws_col; - return tgetnum(const_cast("co")); -} - -void CYDisplayOutput_(int (*put)(int), const char *&data) { - for (;; ++data) { - char next(*data); - if (next == '\0' || next == CYIgnoreEnd) - return; - if (put != NULL) - put(next); - } -} - -CYCursor CYDisplayOutput(int (*put)(int), int width, const char *data, ssize_t offset = 0) { - CYCursor point(current_); - - for (;;) { - if (offset-- == 0) - point = current_; - - char next(*data++); - switch (next) { - case '\0': - return point; - break; - - case CYIgnoreStart: - CYDisplayOutput_(put, data); - case CYIgnoreEnd: - ++offset; - break; - - default: - current_ += CYCursor(0, 1); - if (current_.imag() == width) - case '\n': - current_ = CYCursor(current_.real() + 1, 0); - if (put != NULL) - put(next); - break; - - } - } -} - -void CYDisplayMove_(char *negative, char *positive, int offset) { - if (offset < 0) - putp(tparm(negative, -offset)); - else if (offset > 0) - putp(tparm(positive, offset)); -} - -void CYDisplayMove(CYCursor target) { - CYCursor offset(target - current_); - - CYDisplayMove_(parm_up_cursor, parm_down_cursor, offset.real()); - - if (char *parm = tparm(column_address, target.imag())) - putp(parm); - else - CYDisplayMove_(parm_left_cursor, parm_right_cursor, offset.imag()); - - current_ = target; -} - -void CYDisplayUpdate() { - rl_display_fixed = 1; - rl_redisplay(); - current_ = CYCursor(_rl_last_v_pos, _rl_last_c_pos); - -#if RL_READLINE_VERSION >= 0x0600 - const char *prompt(rl_display_prompt); -#else - const char *prompt(rl_prompt); -#endif - - std::ostringstream stream; - CYLexerHighlight(rl_line_buffer, rl_end, stream, true); - std::string string(stream.str()); - const char *buffer(string.c_str()); - - int width(CYDisplayWidth()); - if (width_ != width) { - current_ = CYCursor(); - CYDisplayOutput(NULL, width, prompt); - current_ = CYDisplayOutput(NULL, width, buffer, point_); - } - - CYDisplayMove(CYCursor()); - CYDisplayOutput(putchar, width, prompt); - CYCursor target(CYDisplayOutput(putchar, width, stream.str().c_str(), rl_point)); - - _rl_vis_botlin = current_.real(); - - if (current_.imag() == 0) - CYDisplayOutput(putchar, width, " "); - putp(clr_eos); - - CYDisplayMove(target); - fflush(stdout); - - _rl_last_v_pos = current_.real(); - _rl_last_c_pos = current_.imag(); - - width_ = width; - point_ = rl_point; -} - -#endif diff --git a/Display.hpp b/Display.hpp deleted file mode 100644 index ac3b8ac..0000000 --- a/Display.hpp +++ /dev/null @@ -1,27 +0,0 @@ -/* Cycript - Optimizing JavaScript Compiler/Runtime - * Copyright (C) 2009-2015 Jay Freeman (saurik) -*/ - -/* GNU Affero General Public License, Version 3 {{{ */ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, 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 Affero General Public License for more details. - - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . -**/ -/* }}} */ - -#ifndef CYCRIPT_DISPLAY_HPP -#define CYCRIPT_DISPLAY_HPP - -void CYDisplayUpdate(); - -#endif/*CYCRIPT_DISPLAY_HPP*/ diff --git a/Highlight.cpp b/Highlight.cpp index addd757..d668f78 100644 --- a/Highlight.cpp +++ b/Highlight.cpp @@ -34,9 +34,9 @@ static void Skip(const char *data, size_t size, std::ostream &output, size_t &of _assert(current.line < target.line || current.line == target.line && current.column < target.column); if (next == '\n') - current.lines(); + current.Lines(); else - current.columns(); + current.Columns(); } } diff --git a/Location.hpp b/Location.hpp index a53e932..b2d71ae 100644 --- a/Location.hpp +++ b/Location.hpp @@ -37,12 +37,12 @@ class CYPosition { { } - void lines(unsigned count = 1) { + void Lines(unsigned count = 1) { column = 0; line += count; } - void columns(unsigned count = 1) { + void Columns(unsigned count = 1) { column += count; } }; diff --git a/Makefile.am b/Makefile.am index a4d79e3..3a71b35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -50,7 +50,7 @@ filters = if CY_CONSOLE bin_PROGRAMS = cycript -cycript_SOURCES = Console.cpp Display.cpp +cycript_SOURCES = Console.cpp cycript_LDADD = libcycript.la $(LTLIBREADLINE) $(LTLIBTERMCAP) $(LTLIBGCC) $(PTHREAD_CFLAGS) -ldl libcycript_la_SOURCES += Complete.cpp endif diff --git a/Makefile.in b/Makefile.in index cafdcf7..e77a978 100644 --- a/Makefile.in +++ b/Makefile.in @@ -211,10 +211,10 @@ libcycript_la_LINK = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(OBJCXXLD) $(AM_OBJCXXFLAGS) \ $(OBJCXXFLAGS) $(libcycript_la_LDFLAGS) $(LDFLAGS) -o $@ PROGRAMS = $(bin_PROGRAMS) -am__cycript_SOURCES_DIST = Console.cpp Display.cpp Inject.cpp +am__cycript_SOURCES_DIST = Console.cpp Inject.cpp @CY_ATTACH_TRUE@@CY_CONSOLE_TRUE@am__objects_6 = Inject.$(OBJEXT) @CY_CONSOLE_TRUE@am_cycript_OBJECTS = Console.$(OBJEXT) \ -@CY_CONSOLE_TRUE@ Display.$(OBJEXT) $(am__objects_6) +@CY_CONSOLE_TRUE@ $(am__objects_6) cycript_OBJECTS = $(am_cycript_OBJECTS) @CY_CONSOLE_TRUE@cycript_DEPENDENCIES = libcycript.la \ @CY_CONSOLE_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ @@ -561,8 +561,7 @@ libcycript_la_SOURCES = ConvertUTF.c Decode.cpp Driver.cpp \ $(am__append_2) $(am__append_8) $(am__append_11) \ $(am__append_13) filters = $(am__append_5) $(am__append_7) $(am__append_10) -@CY_CONSOLE_TRUE@cycript_SOURCES = Console.cpp Display.cpp \ -@CY_CONSOLE_TRUE@ $(am__append_14) +@CY_CONSOLE_TRUE@cycript_SOURCES = Console.cpp $(am__append_14) @CY_CONSOLE_TRUE@cycript_LDADD = libcycript.la $(LTLIBREADLINE) $(LTLIBTERMCAP) $(LTLIBGCC) $(PTHREAD_CFLAGS) -ldl all: config.h $(MAKE) $(AM_MAKEFLAGS) all-recursive @@ -754,7 +753,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Console.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ConvertUTF.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Decode.Plo@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Display.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Driver.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Execute.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Handler.Plo@am__quote@ diff --git a/Scanner.lpp.in b/Scanner.lpp.in index 677d7e9..1399011 100644 --- a/Scanner.lpp.in +++ b/Scanner.lpp.in @@ -83,13 +83,13 @@ typedef cy::parser::token tk; nl = reinterpret_cast(memchr(nl + 1, '\n', left)); \ } while (nl != NULL); \ yylloc->step(); \ - yylloc->end.lines(lines); \ - yylloc->end.columns(left); \ + yylloc->end.Lines(lines); \ + yylloc->end.Columns(left); \ more \ } else L \ } -#define R yylloc->end.columns(yyleng); +#define R yylloc->end.Columns(yyleng); #define L yylloc->step(); R #define H(value, highlight) do { \ @@ -302,7 +302,7 @@ XMLName {XMLNameStart}{XMLNamePart}* { \**\*\/ R yy_pop_state(yyscanner); M N - \**{LineTerminatorSequence} yylloc->end.lines(); yyextra->last_ = true; + \**{LineTerminatorSequence} yylloc->end.Lines(); yyextra->last_ = true; \**{CommentCharacter}|\/ R \**({UnicodeFail}|\*) R E("invalid comment"); <> R E("invalid comment") @@ -624,7 +624,7 @@ XMLName {XMLNameStart}{XMLNamePart}* CYLexBufferPoint(point); } - \\{LineTerminatorSequence} yylloc->end.lines(); + \\{LineTerminatorSequence} yylloc->end.Lines(); \\(.|{NotLineTerminator}) R CYLexBufferUnits(yytext + 1, yyleng - 1); \\(x{HexDigit}{0,1}|u({HexDigit}{0,3}|\{{HexDigit}*)|{UnicodeFail})? R E("invalid escape"); @@ -632,7 +632,7 @@ XMLName {XMLNameStart}{XMLNamePart}* } /* }}} */ -{LineTerminatorSequence} yylloc->step(); yylloc->end.lines(); yyextra->last_ = true; N +{LineTerminatorSequence} yylloc->step(); yylloc->end.Lines(); yyextra->last_ = true; N {WhiteSpace} L <> if (yyextra->auto_) { yyextra->auto_ = false; F(tk::AutoComplete, hi::Nothing); } L yyterminate(); diff --git a/Syntax.hpp b/Syntax.hpp index 5e792b7..a83659d 100644 --- a/Syntax.hpp +++ b/Syntax.hpp @@ -78,15 +78,15 @@ struct CYOutput { _assert(out_.sputc(value) != EOF); recent_ = indent_; if (value == '\n') - position_.lines(1); + position_.Lines(1); else - position_.columns(1); + position_.Columns(1); } _finline void operator ()(const char *data, std::streamsize size) { _assert(out_.sputn(data, size) == size); recent_ = indent_; - position_.columns(size); + position_.Columns(size); } _finline void operator ()(const char *data) { -- 2.45.2