From f6d4987b8cc2264169929916fdfb90063e846e4c Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Mon, 10 Aug 2026 16:29:33 +0100 Subject: [PATCH] Add some more ed commands. --- include/commands/ed/ed.h | 5 ++ src/commands/ed/edit.cc | 21 -------- src/commands/ed/handle.cc | 82 +++++++++++++++++++++++------ src/commands/ed/helpers.cc | 83 ++++++++++++++++++++++++++++++ src/commands/ed/parsers/command.cc | 18 +++++++ src/internal/vase/regex/api.cc | 6 +++ src/internal/vase/regex/core.cc | 56 ++------------------ 7 files changed, 181 insertions(+), 90 deletions(-) delete mode 100644 src/commands/ed/edit.cc create mode 100644 src/commands/ed/helpers.cc diff --git a/include/commands/ed/ed.h b/include/commands/ed/ed.h index 190b4b6..afca18c 100644 --- a/include/commands/ed/ed.h +++ b/include/commands/ed/ed.h @@ -17,12 +17,15 @@ struct Command { HelpToggle, Help, Print, + List, Number, Append, Insert, Change, Delete, Write, + Join, + Mark, Edit, ForceEdit, Filename, @@ -85,6 +88,8 @@ struct Ed { Command parse(std::string cmd, bool eof); void append(std::string text, uint64_t line); void remove(uint64_t start_line, uint64_t end_line); + std::string list_string(std::string_view s); + void join(uint64_t start_line, uint64_t end_line); bool handle(std::string cmd, bool eof); }; diff --git a/src/commands/ed/edit.cc b/src/commands/ed/edit.cc deleted file mode 100644 index 17ebde6..0000000 --- a/src/commands/ed/edit.cc +++ /dev/null @@ -1,21 +0,0 @@ -#include "commands/ed/ed.h" - -namespace crib::commands::ed { -void Ed::append(std::string text, uint64_t line) { - using namespace crib::internal::vase; - Point p = {line, 0}; - if (!vase.lines()) { - text.pop_back(); - } else if (line == vase.lines()) { - p.row--; - p.col = UINT64_MAX; - text = "\n" + text; - text.pop_back(); - } - vase.insert(&p, text); -} - -void Ed::remove(uint64_t start_line, uint64_t end_line) { - vase.erase({{start_line - 1, 0}, {end_line, 0}}); -} -} // namespace crib::commands::ed diff --git a/src/commands/ed/handle.cc b/src/commands/ed/handle.cc index 06de350..ff021df 100644 --- a/src/commands/ed/handle.cc +++ b/src/commands/ed/handle.cc @@ -70,6 +70,24 @@ bool Ed::handle(std::string cmd, bool eof) { std::cout << it.line << std::endl; line = line_end; } break; + case Command::Type::List: { + uint64_t line_start = line; + uint64_t line_end = line; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (line_start == 0) + throw ed_error("Line 0 is invalid."); + if (line_end < line_start) + throw ed_error("Invalid address range."); + Iterator it = vase.iterate(line_start - 1, Direction::Forward); + while (it.next() && line_start++ <= line_end) + std::cout << list_string(it.line) << std::endl; + line = line_end; + } break; case Command::Type::Number: { uint64_t line_start = line; uint64_t line_end = line; @@ -89,6 +107,12 @@ bool Ed::handle(std::string cmd, bool eof) { line = line_end; } break; case Command::Type::Append: { + if (command.start.type != Command::Address::Type::None) { + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line); + else + resolve_address(command.start, &line); + } std::string text; std::string cline; uint64_t line_count = 0; @@ -99,12 +123,6 @@ bool Ed::handle(std::string cmd, bool eof) { text.append(cline); text.push_back('\n'); } - if (command.start.type != Command::Address::Type::None) { - if (command.address_flags & Command::RANGE) - resolve_address(command.end, &line); - else - resolve_address(command.start, &line); - } if (text.empty()) { if (line == 0) line = 1; @@ -116,6 +134,14 @@ bool Ed::handle(std::string cmd, bool eof) { line += line_count; } break; case Command::Type::Insert: { + if (command.start.type != Command::Address::Type::None) { + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line); + else + resolve_address(command.start, &line); + } + if (line == 0) + line = 1; std::string text; std::string cline; uint64_t line_count = 0; @@ -126,14 +152,6 @@ bool Ed::handle(std::string cmd, bool eof) { text.append(cline); text.push_back('\n'); } - if (command.start.type != Command::Address::Type::None) { - if (command.address_flags & Command::RANGE) - resolve_address(command.end, &line); - else - resolve_address(command.start, &line); - } - if (line == 0) - line = 1; if (text.empty()) break; vase.snapshot(); @@ -247,6 +265,39 @@ bool Ed::handle(std::string cmd, bool eof) { if (!suppress_mode) std::cout << bytes << std::endl; } break; + case Command::Type::Join: { + uint64_t line_start = line; + uint64_t line_end = line + 1; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (line_end < line_start) + throw ed_error("Invalid address range."); + if (line_start == 0) + throw ed_error("Invalid line number given."); + if (line_end == line_start) + break; + vase.snapshot(); + join(line_start, line_end); + line = line_start; + modified = true; + } break; + case Command::Type::Mark: { + uint64_t mark_line = line; + if (command.start.type != Command::Address::Type::None) { + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &mark_line); + else + resolve_address(command.start, &mark_line); + } + if (mark_line == 0) + throw ed_error("Invalid line number given."); + char mark = command.argument[0]; + marks[mark - 'a'] = mark_line; + } break; case Command::Type::Edit: { if (modified && !was_editing) { editing = true; @@ -318,8 +369,7 @@ bool Ed::handle(std::string cmd, bool eof) { std::cout << line << "\t" << it.line << std::endl; break; case 'l': - // TODO: escaping. - std::cout << it.line << std::endl; + std::cout << list_string(it.line) << std::endl; break; } } diff --git a/src/commands/ed/helpers.cc b/src/commands/ed/helpers.cc new file mode 100644 index 0000000..f24b073 --- /dev/null +++ b/src/commands/ed/helpers.cc @@ -0,0 +1,83 @@ +#include "commands/ed/ed.h" + +namespace crib::commands::ed { +void Ed::append(std::string text, uint64_t line) { + using namespace crib::internal::vase; + Point p = {line, 0}; + if (!vase.lines()) { + text.pop_back(); + } else if (line == vase.lines()) { + p.row--; + p.col = UINT64_MAX; + text = "\n" + text; + text.pop_back(); + } + vase.insert(&p, text); +} + +void Ed::remove(uint64_t start_line, uint64_t end_line) { + vase.erase({{start_line - 1, 0}, {end_line, 0}}); +} + +void Ed::join(uint64_t start_line, uint64_t end_line) { + vase.regex_search_replace("\\n", {{start_line - 1, 0}, {end_line, 0}}, "", ""); +} + +std::string Ed::list_string(std::string_view s) { + uint32_t width = 80; + winsize ws{}; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) + width = ws.ws_col; + std::string out; + out.reserve(s.size()); + const uint32_t max_width = width > 1 ? width - 1 : 1; + uint32_t column = 0; + auto append = [&](std::string_view text) { + if (column + text.size() > max_width) { + out += "\\\n"; + column = 0; + } + out += text; + column += text.size(); + }; + for (unsigned char c : s) { + switch (c) { + case '\\': + append("\\\\"); + break; + case '$': + append("\\$"); + break; + case '\a': + append("\\a"); + break; + case '\b': + append("\\b"); + break; + case '\f': + append("\\f"); + break; + case '\r': + append("\\r"); + break; + case '\t': + append("\\t"); + break; + case '\v': + append("\\v"); + break; + default: + if (!std::isprint(c)) { + char buf[5]; + std::snprintf(buf, sizeof(buf), "\\%03o", c); + append(buf); + } else { + append(std::string_view((const char *)&c, 1)); + } + break; + } + } + out += '$'; + return out; +} +} // namespace crib::commands::ed diff --git a/src/commands/ed/parsers/command.cc b/src/commands/ed/parsers/command.cc index dd8d503..e86cd4d 100644 --- a/src/commands/ed/parsers/command.cc +++ b/src/commands/ed/parsers/command.cc @@ -59,6 +59,10 @@ Command Ed::parse(std::string cmd, bool eof) { command.type = Command::Type::Print; i++; break; + case 'l': + command.type = Command::Type::List; + i++; + break; case 'n': command.type = Command::Type::Number; i++; @@ -91,6 +95,10 @@ Command Ed::parse(std::string cmd, bool eof) { command.type = Command::Type::Delete; i++; break; + case 'j': + command.type = Command::Type::Join; + i++; + break; case 'w': command.type = Command::Type::Write; i++; @@ -135,6 +143,16 @@ Command Ed::parse(std::string cmd, bool eof) { throw ed_error("Invalid command."); command.argument = cmd.substr(i); return command; + case 'k': + command.type = Command::Type::Mark; + i++; + if (i >= cmd.size()) + throw ed_error("Invalid command."); + if ('a' <= cmd[i] && cmd[i] <= 'z') + command.argument = cmd[i]; + else + throw ed_error("Invalid mark used."); + break; case '#': command.type = Command::Type::Dump; i++; diff --git a/src/internal/vase/regex/api.cc b/src/internal/vase/regex/api.cc index 308b7d6..d564601 100644 --- a/src/internal/vase/regex/api.cc +++ b/src/internal/vase/regex/api.cc @@ -7,6 +7,12 @@ std::vector Vase::parse_replace(std::string_view s) { auto flush_constant = [&]() { if (!constant.empty()) { uint64_t lines = 0; + const char *p = constant.data(); + const char *end = p + constant.size(); + while ((p = (const char *)memchr(p, '\n', end - p))) { + ++lines; + ++p; + } uint64_t pos = append->append(constant.data(), (uint64_t)constant.size()); parts.push_back( ReplacePart{ diff --git a/src/internal/vase/regex/core.cc b/src/internal/vase/regex/core.cc index 7f8c51a..f4cdde8 100644 --- a/src/internal/vase/regex/core.cc +++ b/src/internal/vase/regex/core.cc @@ -7,8 +7,6 @@ std::vector Vase::_regex_search( bool global = false; uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF; - const char *dot = "(?:(?!\\n)\\X)"; - for (char c : options) { switch (c) { case 'g': @@ -18,7 +16,7 @@ std::vector Vase::_regex_search( flags |= PCRE2_CASELESS; break; case 's': - dot = "(?:\\X)"; + flags |= PCRE2_DOTALL; break; case 'x': flags |= PCRE2_EXTENDED; @@ -40,57 +38,9 @@ std::vector Vase::_regex_search( int errornumber; PCRE2_SIZE erroroffset; - std::string out; - out.reserve(pattern.size() * 2); - bool escaped = false; - bool in_class = false; - bool in_quote = false; - for (size_t i = 0; i < pattern.size(); i++) { - char c = pattern[i]; - if (escaped) { - out += c; - escaped = false; - continue; - } - if (c == '\\') { - out += c; - if (i + 1 < pattern.size()) { - char next = pattern[i + 1]; - if (next == 'Q') - in_quote = true; - else if (next == 'E') - in_quote = false; - out += next; - i++; - continue; - } - escaped = true; - continue; - } - if (in_quote) { - out += c; - continue; - } - if (c == '[') { - in_class = true; - out += c; - continue; - } - if (c == ']' && in_class) { - in_class = false; - out += c; - continue; - } - if (c == '.' && !in_class) { - out += dot; - continue; - } - out += c; - } - pcre2_code *re = pcre2_compile( - (PCRE2_SPTR)out.data(), - out.size(), + (PCRE2_SPTR)pattern.data(), + pattern.size(), flags, &errornumber, &erroroffset,