diff --git a/include/bed.h b/include/bed.h index 2313c2d..e0a379e 100644 --- a/include/bed.h +++ b/include/bed.h @@ -31,5 +31,6 @@ struct BEd { ~BEd(); void handle(std::string_view cmd, bool eof); void run(); + void suffix_handle(char s); }; } // namespace bed diff --git a/include/internal/address/address.h b/include/internal/address/address.h index 50a98c7..2e98713 100644 --- a/include/internal/address/address.h +++ b/include/internal/address/address.h @@ -41,17 +41,11 @@ struct Address { char m; }; - struct RegexLine { // /re/ or ?re? + struct Regex { // /re/ or ?re? internal::Direction dir; std::string re; }; - // BEd Extended types of addressing. - struct Regex { // &/re/ or &?re? - internal::Direction dir; // returns next/previous line containing the regex - std::string re; // (not only if it matches full line) - }; - struct Diagnostic { // #n# for diagnostic number n. (From lsp.) uint16_t n; }; @@ -91,7 +85,6 @@ struct Address { Last, Number, Mark, - RegexLine, Regex, Diagnostic, DiagnosticNext, diff --git a/include/internal/buffer/buffer.h b/include/internal/buffer/buffer.h index 8a9fe9a..c9a61de 100644 --- a/include/internal/buffer/buffer.h +++ b/include/internal/buffer/buffer.h @@ -34,6 +34,7 @@ struct Buffer { void remove(uint64_t start_line, uint64_t end_line); void append(std::string text, uint64_t line); void print(BEd &ctx, uint64_t start_line, uint64_t end_line); + void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line); std::string list_string(std::string_view s); }; } // namespace bed::internal::buffer diff --git a/include/internal/marks/marks.h b/include/internal/marks/marks.h index 3487c99..720c186 100644 --- a/include/internal/marks/marks.h +++ b/include/internal/marks/marks.h @@ -3,5 +3,55 @@ #include "pch.h" namespace bed::internal::marks { -struct MarksEngine {}; +struct MarksEngine { + uint64_t marks[1 << UINT8_WIDTH]{UINT64_MAX}; + + MarksEngine() { + for (auto &m : marks) + m = UINT64_MAX; + } + + uint64_t get(uint8_t m) { + return marks[m]; + } + + void set(uint8_t m, uint64_t line) { + marks[m] = line; + } + + void insert(uint64_t start, uint64_t count) { + for (uint16_t i = 0; i <= UINT8_MAX; ++i) { + if (marks[i] == UINT64_MAX) + continue; + if (marks[i] >= start) + marks[i] += count; + } + } + + void erase(uint64_t start, uint64_t count) { + for (uint16_t i = 0; i <= UINT8_MAX; ++i) { + if (marks[i] == UINT64_MAX) + continue; + if (marks[i] >= start) { + if (marks[i] < start + count) + marks[i] = UINT64_MAX; + else + marks[i] -= count; + } + } + } + + void collapse(uint64_t start, uint64_t count) { + for (uint16_t i = 0; i <= UINT8_MAX; ++i) { + if (marks[i] == UINT64_MAX) + continue; + if (marks[i] >= start) { + if (marks[i] < start + count) + marks[i] = start; + else + marks[i] -= count; + } + } + } +}; } // namespace bed::internal::marks diff --git a/include/internal/syntax/decl.h b/include/internal/syntax/decl.h index a6e542c..5e9b874 100644 --- a/include/internal/syntax/decl.h +++ b/include/internal/syntax/decl.h @@ -69,17 +69,8 @@ struct Token { }; struct ParseEvent { - std::string_view name; - enum T : uint8_t { - Opening, - Closing, - SymbolDef, - Symbol - } ev_type; - uint8_t type; // type. - - ParseEvent(T t) : ev_type(t) {} - ParseEvent(T e_t, std::string_view s, uint8_t type) : name(s), ev_type(e_t), type(type) {} + uint8_t closing; + ParseEvent(uint8_t t) : closing(t) {} }; struct Language { @@ -90,16 +81,6 @@ struct Language { std::function destroy; }; -struct Symbol { - uint32_t definition; - uint16_t reference_count; - uint8_t type; - uint8_t len; - // first chars of count len, padded to 4 bytes. - // then a set of references. 32bit - // references later as modifying at end is faster than shifting the name. -}; - struct ParseState { static constexpr uint64_t BRANCH_BIT = 1ull << 63; static constexpr uint64_t LINES_MASK = ~BRANCH_BIT; diff --git a/include/internal/syntax/ruby/decl.h b/include/internal/syntax/ruby/decl.h index ef06e87..6167d9c 100644 --- a/include/internal/syntax/ruby/decl.h +++ b/include/internal/syntax/ruby/decl.h @@ -5,15 +5,6 @@ #include "tries.h" namespace bed::internal::syntax::ruby { -enum struct ScopeTypes : uint8_t { - None, - Comment, - Method, - Class, - Module, - Block -}; - struct alignas(2) RubyState { struct RubyInternalState { uint16_t brace_level; diff --git a/include/internal/vase/vase.h b/include/internal/vase/vase.h index 1d01d38..16aa2fd 100644 --- a/include/internal/vase/vase.h +++ b/include/internal/vase/vase.h @@ -3,6 +3,7 @@ #include "buffer/append.h" #include "buffer/original.h" #include "constants.h" +#include "definitions.h" #include "iterators/line.h" #include "pch.h" #include "shard.h" @@ -92,6 +93,9 @@ struct Vase { std::string_view pattern, Range range, std::string_view options ); + uint64_t find_next(std::string_view pattern, uint64_t start); + uint64_t find_prev(std::string_view pattern, uint64_t start); + bool undo(); bool redo(); void snapshot(); diff --git a/src/bed/handle.cc b/src/bed/handle.cc index 36f5f67..b2735bb 100644 --- a/src/bed/handle.cc +++ b/src/bed/handle.cc @@ -94,4 +94,9 @@ void BEd::handle(std::string_view cmd_, bool eof) { suffix->handle(*this); return; } + +void BEd::suffix_handle(char s) { + if (suffixes[s - 'a'].has_value()) + suffixes[s - 'a'].value().handle(*this); +} } // namespace bed diff --git a/src/internal/address/address.cc b/src/internal/address/address.cc index 9c5183d..aeef700 100644 --- a/src/internal/address/address.cc +++ b/src/internal/address/address.cc @@ -29,11 +29,12 @@ Address::Address(std::string &cmd, uint64_t &i) { break; case '\'': { i++; - if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z') + if (i < cmd.size() + && (('a' <= cmd[i] && cmd[i] <= 'z') || ('A' <= cmd[i] && cmd[i] <= 'Z'))) i++; else throw address_error("Invalid mark."); - base = Mark(cmd[i]); + base = Mark(cmd[i - 1]); } break; case '/': { i++; @@ -54,7 +55,9 @@ Address::Address(std::string &cmd, uint64_t &i) { else i++; } - base = RegexLine(Direction::Forward, cmd.substr(start, i - start)); + base = Regex(Direction::Forward, cmd.substr(start, i - start)); + if (i < cmd.size()) + i++; } break; case '?': { i++; @@ -75,7 +78,9 @@ Address::Address(std::string &cmd, uint64_t &i) { else i++; } - base = RegexLine(Direction::Backward, cmd.substr(start, i - start)); + base = Regex(Direction::Backward, cmd.substr(start, i - start)); + if (i < cmd.size()) + i++; } break; case '+': { base = Current(); diff --git a/src/internal/address/resolve.cc b/src/internal/address/resolve.cc index 19949df..ec73900 100644 --- a/src/internal/address/resolve.cc +++ b/src/internal/address/resolve.cc @@ -17,6 +17,21 @@ uint64_t Address::resolve(BEd &ctx) { line = ctx.active->vase.lines(); } else if constexpr (std::is_same_v) { line = addr.i; + } else if constexpr (std::is_same_v) { + line = ctx.active->marks.get(addr.m); + if (line == UINT64_MAX) + throw address_error("Mark not set."); + } else if constexpr (std::is_same_v) { + std::string_view re = addr.re; + if (re.size() == 0) + re = ctx.last_regex; + if (re.size() == 0) + throw address_error("No regex given."); + if (addr.dir == Direction::Forward) + line = ctx.active->vase.find_next(re, ctx.active->line - 1) + 1; + else + line = ctx.active->vase.find_prev(re, ctx.active->line - 1) + 1; + ctx.last_regex = re; } else if constexpr (std::is_same_v) { uint64_t current_line = ctx.active->line; if (ctx.active->vase.lines() > 0 && current_line == 0) diff --git a/src/internal/buffer/buffer.cc b/src/internal/buffer/buffer.cc index 6d2db04..8e7274e 100644 --- a/src/internal/buffer/buffer.cc +++ b/src/internal/buffer/buffer.cc @@ -64,8 +64,6 @@ void Buffer::jump(uint64_t n_line) { if (n_line > vase.lines()) throw ed_error("Line number too high."); line = n_line; - prev_range.start = line; - prev_range.end = line; } void Buffer::append(std::string text, uint64_t line) { @@ -82,6 +80,9 @@ void Buffer::append(std::string text, uint64_t line) { prev_range.start = p.row + 1; vase.insert(&p, text); prev_range.end = p.row + 1; + marks.insert(prev_range.start, prev_range.end); + if (parser) + parser->insert(vase, prev_range.start, prev_range.end); modified = true; } @@ -89,6 +90,9 @@ void Buffer::remove(uint64_t start_line, uint64_t end_line) { vase.erase({{start_line - 1, 0}, {end_line, 0}}); prev_range.start = start_line; prev_range.end = start_line; + marks.erase(start_line, end_line - start_line + 1); + if (parser) + parser->erase(vase, start_line, end_line - start_line + 1); modified = true; } @@ -96,6 +100,9 @@ void Buffer::join(uint64_t start_line, uint64_t end_line) { vase.regex_search_replace(R"(\n)", {{start_line - 1, 0}, {end_line, 0}}, "", "g"); prev_range.start = start_line; prev_range.end = start_line; + marks.collapse(start_line, end_line - start_line); + if (parser) + parser->erase(vase, start_line, end_line - start_line); modified = true; } @@ -132,6 +139,8 @@ inline void reset(std::ostream &out) { } void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { + prev_range.start = start_line; + prev_range.end = end_line; if (parser) { std::optional it_o = parser->get_hl(vase, start_line - 1); auto &it = *it_o; @@ -169,8 +178,52 @@ void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { while (it.next() && start_line++ <= end_line) std::cout << it.line << std::endl; } +} + +void Buffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) { prev_range.start = start_line; prev_range.end = end_line; + uint8_t width = 1; + for (uint64_t n = end_line; n >= 10; n /= 10) + ++width; + if (parser) { + std::optional it_o = parser->get_hl(vase, start_line - 1); + auto &it = *it_o; + while (start_line <= end_line) { + it.next(); + std::cout << std::setw(width) << it.at << "\t"; + const std::string &line = it.it->line; + const auto &tokens = it.tokens; + uint32_t cursor = 0; + for (const auto &token : tokens) { + const uint32_t start = token.start; + const uint32_t end = token.end; + if (start > line.size()) + break; + if (end > line.size()) + break; + if (cursor < start) { + std::cout.write( + line.data() + cursor, + start - cursor + ); + } + const auto highlight = ctx.theme.get(token); + apply(std::cout, highlight); + std::cout.write(line.data() + start, end - start); + reset(std::cout); + cursor = end; + } + if (cursor < line.size()) + std::cout.write(line.data() + cursor, line.size() - cursor); + std::cout << std::endl; + ++start_line; + } + } else { + vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward); + while (it.next() && start_line <= end_line) + std::cout << std::setw(width) << start_line++ << "\t" << it.line << std::endl; + } } std::string Buffer::list_string(std::string_view s) { diff --git a/src/internal/commands/commands.cc b/src/internal/commands/commands.cc index 75644a6..b837d35 100644 --- a/src/internal/commands/commands.cc +++ b/src/internal/commands/commands.cc @@ -9,6 +9,15 @@ void Suffix::register_suffixes(BEd &ctx) { .handle = [](BEd &ctx) { auto line = ctx.active->line; ctx.active->print(ctx, line, line); + ctx.active->jump(line); + } + }; + ctx.suffixes['n' - 'a'] = Suffix{ + .desc = "Prints current line with line number.", + .handle = [](BEd &ctx) { + auto line = ctx.active->line; + ctx.active->number_print(ctx, line, line); + ctx.active->jump(line); } }; } @@ -55,6 +64,26 @@ void Command::register_posix(BEd &ctx) { } } ); + ctx.commands.insert( + "j", + Command{ + .address_mode = Command::AddressMode::Range, + .suffix = Command::SuffixKind::Suffix, + .desc = "Join a set of lines (default: .,.+1)", + .accept_zero = true, + .handle = [](BEd &ctx, std::span addresses, std::string_view) { + std::cout << addresses.size() << "\n"; + uint64_t start_line = ctx.active->line; + uint64_t end_line = ctx.active->line + 1; + if (addresses.size() == 1) + return; + else if (addresses.size() == 2) + start_line = addresses[0], end_line = addresses[1]; + ctx.active->join(start_line, end_line); + ctx.active->jump(start_line); + } + } + ); ctx.commands.insert( "q", Command{ @@ -92,27 +121,67 @@ void Command::register_posix(BEd &ctx) { .handle = [](BEd &ctx, std::span addresses, std::string_view) { uint64_t start_line = ctx.active->line; uint64_t end_line = ctx.active->line; - if (!addresses.size()) - ctx.active->print(ctx, start_line, end_line); - else if (addresses.size() == 1) - ctx.active->print(ctx, addresses[0], addresses[0]); - else - ctx.active->print(ctx, addresses[0], addresses[1]); + if (addresses.size() == 1) + start_line = addresses[0], end_line = addresses[0]; + else if (addresses.size() == 2) + start_line = addresses[0], end_line = addresses[1]; + ctx.active->print(ctx, start_line, end_line); + ctx.active->jump(end_line); + } + } + ); + ctx.commands.insert( + "n", + Command{ + .address_mode = Command::AddressMode::Range, + .suffix = Command::SuffixKind::Suffix, + .desc = "Print range with line numbers (default .,.)", + .accept_zero = false, + .handle = [](BEd &ctx, std::span addresses, std::string_view) { + uint64_t start_line = ctx.active->line; + uint64_t end_line = ctx.active->line; + if (addresses.size() == 1) + start_line = addresses[0], end_line = addresses[0]; + else if (addresses.size() == 2) + start_line = addresses[0], end_line = addresses[1]; + ctx.active->number_print(ctx, start_line, end_line); + ctx.active->jump(end_line); } } ); ctx.commands.insert( "=", Command{ - .address_mode = Command::AddressMode::Single, + .address_mode = Command::AddressMode::Range, .suffix = Command::SuffixKind::Suffix, - .desc = "Print line number", + .desc = "Print line number(s)", .accept_zero = true, .handle = [](BEd &ctx, std::span addresses, std::string_view) { if (!addresses.size()) std::cout << ctx.active->vase.lines() << std::endl; - else + else if (addresses.size() == 1) std::cout << addresses[0] << std::endl; + else + std::cout << addresses[0] << "," << addresses[1] << std::endl; + } + } + ); + ctx.commands.insert( + "k", + Command{ + .address_mode = Command::AddressMode::Single, + .suffix = Command::SuffixKind::Continuation, + .desc = "Mark a line.", + .accept_zero = true, + .handle = [](BEd &ctx, std::span addresses, std::string_view args) { + if (args.size() < 1 || args.size() > 2) + throw ed_error("Malformed mark command"); + if (addresses.size()) + ctx.active->marks.set(args[0], addresses[0]); + else + ctx.active->marks.set(args[0], ctx.active->line); + if (args.size() > 1) + ctx.suffix_handle(args[1]); } } ); diff --git a/src/internal/syntax/parser.cc b/src/internal/syntax/parser.cc index 76382d9..ee989a2 100644 --- a/src/internal/syntax/parser.cc +++ b/src/internal/syntax/parser.cc @@ -243,15 +243,12 @@ void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) { events.clear(); lang.parse(&state, it.line, at == 0, &tokens, &events); for (const auto &ev : events) { - const auto t = uint8_t(ev.ev_type); - if (t > ParseEvent::Closing) - continue; if (c.leaf->n == c.leaf->cap) { uint32_t cap = c.leaf->cap ? c.leaf->cap * 2 : 8; c.leaf->blocks = (uint16_t *)realloc(c.leaf->blocks, cap * sizeof(uint16_t)); c.leaf->cap = cap; } - c.leaf->blocks[c.leaf->n++] = t << 15 | (at - chunk_start); + c.leaf->blocks[c.leaf->n++] = ev.closing << 15 | (at - chunk_start); } at++; } diff --git a/src/internal/syntax/ruby/parse.cc b/src/internal/syntax/ruby/parse.cc index 2bbb1ad..91db386 100644 --- a/src/internal/syntax/ruby/parse.cc +++ b/src/internal/syntax/ruby/parse.cc @@ -225,7 +225,7 @@ bool handle_line_markers(RubyParser &p, std::vector *tokens, std::vector< if (p.len() == 6 && p.peek_str(6) == "=begin") { p.current().state = RubyState::RubyInternalState::COMMENT; p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION; - events->push_back(ParseEvent::Opening); + events->push_back(false); tokens->push_back({0, p.len(), Token::Comment}); return true; } @@ -266,7 +266,7 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back(ParseEvent::Opening); + events->push_back(false); p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK); p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION; return false; @@ -285,7 +285,7 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back(ParseEvent::Opening); + events->push_back(false); p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK); return false; } @@ -311,7 +311,7 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back(ParseEvent::Opening); + events->push_back(false); p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK); return false; } @@ -330,7 +330,7 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back(ParseEvent::Opening); + events->push_back(false); p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK); return false; } @@ -899,21 +899,21 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back(ParseEvent::Opening); + events->push_back(false); p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION; tokens->push_back({p.i, p.i + j, Token::Keyword}); p.advance(j); return false; } else if (j == tries.conditional_keywords_trie.longest_match(p.peek_str(j))) { if (p.current().flags & RubyState::RubyInternalState::NEWLINE || p.op_last) - events->push_back(ParseEvent::Opening); + events->push_back(false); p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION; tokens->push_back({p.i, p.i + j, Token::Keyword}); p.advance(j); return false; } else if (j == tries.end_keywords_trie.longest_match(p.peek_str(j))) { p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION; - events->push_back(ParseEvent::Opening); + events->push_back(false); tokens->push_back({p.i, p.i + j, Token::Keyword}); p.advance(j); return false; @@ -950,7 +950,7 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back(ParseEvent::Closing); + events->push_back(true); tokens->push_back({p.i, p.i + j, Token::Keyword}); p.advance(3); return false; @@ -980,17 +980,18 @@ bool handle_syntax(RubyParser &p, std::vector *tokens, std::vectorpush_back({start, p.i, Token::Label}); return false; - } else if (p.peek(-1) == '!' || p.peek(-1) == '?') { - p.advance(); + } else if (p.peek(j - 1) == '!' || p.peek(j - 1) == '?') { + p.advance(j); tokens->push_back({start, p.i, Token::Function}); return false; } else { - uint32_t j = 0; + p.advance(j); + j = 0; if (p.peek(j) == '(' || p.peek(j) == '{') { tokens->push_back({start, p.i, Token::Function}); return false; @@ -1066,7 +1067,7 @@ void ruby_parse( tokens->push_back({p.i, p.len(), Token::Comment}); if (p.i == 0 && p.peek_str(4) == "=end") { p.current().state = RubyState::RubyInternalState::NONE; - events->push_back(ParseEvent::Closing); + events->push_back(true); } return; } diff --git a/src/internal/vase/regex/api.cc b/src/internal/vase/regex/api.cc index 2cdbcf2..ec82278 100644 --- a/src/internal/vase/regex/api.cc +++ b/src/internal/vase/regex/api.cc @@ -156,4 +156,122 @@ std::vector Vase::regex_search( result.push_back({point_of(match.start), point_of(match.end)}); return result; } + +uint64_t Vase::find_next(std::string_view pattern, uint64_t start) { + std::vector results; + int errornumber; + PCRE2_SIZE erroroffset; + pcre2_code *re = pcre2_compile( + (PCRE2_SPTR)pattern.data(), + pattern.size(), + PCRE2_UTF | PCRE2_EXTENDED, + &errornumber, + &erroroffset, + NULL + ); + if (re == NULL) + throw ed_error("Can't compile regex."); + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); + if (!match_data) { + pcre2_code_free(re); + throw ed_error("Can't create regex match data."); + } + uint64_t at = (start + 1) % lines(); + LineIterator it(root, at, Direction::Forward); + std::string line; + while (it.next(&line)) { + int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); + if (rc >= 0) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + return at; + } + if (rc != PCRE2_ERROR_NOMATCH) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + throw ed_error("Regex matching failed."); + } + at++; + } + at = 0; + LineIterator it2(root, at, Direction::Forward); + while (it2.next(&line)) { + if (at >= start) + break; + int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); + if (rc >= 0) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + return at; + } + if (rc != PCRE2_ERROR_NOMATCH) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + throw ed_error("Regex matching failed."); + } + at++; + } + pcre2_match_data_free(match_data); + pcre2_code_free(re); + throw ed_error("No line matched."); +} + +uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) { + std::vector results; + int errornumber; + PCRE2_SIZE erroroffset; + pcre2_code *re = pcre2_compile( + (PCRE2_SPTR)pattern.data(), + pattern.size(), + PCRE2_UTF | PCRE2_EXTENDED, + &errornumber, + &erroroffset, + NULL + ); + if (re == NULL) + throw ed_error("Can't compile regex."); + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr); + if (!match_data) { + pcre2_code_free(re); + throw ed_error("Can't create regex match data."); + } + uint64_t at = (start == 0 ? lines() : start) - 1; + LineIterator it(root, at, Direction::Backward); + std::string line; + while (it.next(&line)) { + int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); + if (rc >= 0) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + return at; + } + if (rc != PCRE2_ERROR_NOMATCH) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + throw ed_error("Regex matching failed."); + } + at--; + } + at = lines(); + LineIterator it2(root, at, Direction::Backward); + while (it2.next(&line)) { + if (at <= start) + break; + int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); + if (rc >= 0) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + return at; + } + if (rc != PCRE2_ERROR_NOMATCH) { + pcre2_match_data_free(match_data); + pcre2_code_free(re); + throw ed_error("Regex matching failed."); + } + at--; + } + pcre2_match_data_free(match_data); + pcre2_code_free(re); + throw ed_error("No line matched."); +} } // namespace bed::internal::vase