From cd13557e150a6fdaa52dcdd3510218d112dda838 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Thu, 13 Aug 2026 16:17:32 +0100 Subject: [PATCH] Reqrite project as BEd specific. --- LICENSE.md | 2 +- Makefile | 4 +- README.md | 68 ++-- flake.nix | 18 +- include/bed.h | 32 ++ include/cli.h | 22 - include/commands/bed/bed.h | 99 ----- include/definitions.h | 18 + include/internal/address/address.h | 115 ++++++ include/internal/buffer/buffer.h | 34 ++ include/internal/commands/commands.h | 30 ++ include/internal/commands/suffixes.h | 13 + .../{vase/iterators/iter.h => generic.h} | 3 +- include/internal/marks/marks.h | 7 + include/internal/trie/trie.h | 246 +++++++++++ include/internal/vase/buffer/append.h | 4 +- include/internal/vase/buffer/buffer.h | 4 +- include/internal/vase/buffer/original.h | 4 +- include/internal/vase/constants.h | 2 +- include/internal/vase/iterators/line.h | 4 +- include/internal/vase/iterators/petal.h | 6 +- include/internal/vase/shard.h | 4 +- include/internal/vase/vase.h | 4 +- src/bed.cc | 5 + src/bed/handle.cc | 95 +++++ src/bed/run.cc | 53 +++ src/cli/cli.cc | 72 ---- src/commands/bed/bed.cc | 40 -- src/commands/bed/handle.cc | 384 ------------------ src/commands/bed/helpers.cc | 83 ---- src/commands/bed/parsers/command.cc | 176 -------- src/commands/bed/parsers/regex.cc | 0 .../parsers => internal/address}/address.cc | 87 +--- src/internal/address/handle.cc | 50 +++ src/internal/address/resolve.cc | 40 ++ src/internal/buffer/buffer.cc | 161 ++++++++ src/internal/commands/commands.cc | 104 +++++ src/internal/vase/buffer/append.cc | 4 +- src/internal/vase/buffer/original.cc | 4 +- src/internal/vase/iterators/line.cc | 4 +- src/internal/vase/iterators/petal.cc | 4 +- src/internal/vase/regex/api.cc | 4 +- src/internal/vase/regex/core.cc | 4 +- src/internal/vase/shard.cc | 4 +- src/internal/vase/vase.cc | 4 +- src/main.cc | 16 +- 46 files changed, 1115 insertions(+), 1026 deletions(-) create mode 100644 include/bed.h delete mode 100644 include/cli.h delete mode 100644 include/commands/bed/bed.h create mode 100644 include/definitions.h create mode 100644 include/internal/address/address.h create mode 100644 include/internal/buffer/buffer.h create mode 100644 include/internal/commands/commands.h create mode 100644 include/internal/commands/suffixes.h rename include/internal/{vase/iterators/iter.h => generic.h} (62%) create mode 100644 include/internal/marks/marks.h create mode 100644 include/internal/trie/trie.h create mode 100644 src/bed.cc create mode 100644 src/bed/handle.cc create mode 100644 src/bed/run.cc delete mode 100644 src/cli/cli.cc delete mode 100644 src/commands/bed/bed.cc delete mode 100644 src/commands/bed/handle.cc delete mode 100644 src/commands/bed/helpers.cc delete mode 100644 src/commands/bed/parsers/command.cc delete mode 100644 src/commands/bed/parsers/regex.cc rename src/{commands/bed/parsers => internal/address}/address.cc (54%) create mode 100644 src/internal/address/handle.cc create mode 100644 src/internal/address/resolve.cc create mode 100644 src/internal/buffer/buffer.cc create mode 100644 src/internal/commands/commands.cc diff --git a/LICENSE.md b/LICENSE.md index 3ffbdc7..6141996 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -627,7 +627,7 @@ attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Crib - Text editing tools. + BEd - Better Ed. Copyright (C) 2026 Syed (me@syedm.dev) This program is free software: you can redistribute it and/or modify diff --git a/Makefile b/Makefile index 9474637..9f765f3 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,8 @@ INCLUDE_DIR := include CXX ?= g++ CC ?= gcc -TARGET_DEBUG := $(BIN_DIR)/crib-dbg -TARGET_RELEASE := $(BIN_DIR)/crib +TARGET_DEBUG := $(BIN_DIR)/bed-dbg +TARGET_RELEASE := $(BIN_DIR)/bed PCH_DIR_DEBUG := $(OBJ_DIR)/debug/pch PCH_DIR_RELEASE := $(OBJ_DIR)/release/pch diff --git a/README.md b/README.md index 861cb44..b607986 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,4 @@ -# Crib - -A set of text editing tools (still in early development). - -### Modules - -#### `Vase` - -Vase is a avl piece tree like structure which handles loading, insertion, erasure, regex searching, regex replacing etc. -
-done. - -#### `hl` - -`hl` is a stateful super fast syntax highlighter. -
-Not done yet. - -#### `lsp` - -`lsp` is a generic plug for lsp's. -
-
-It should support: -- Lsp over ssh or stdin/out or unix sockets etc. -- Handling multiple lsps at once (including over a single file.) -- Converting offsets encoding between utf16 - utf8 according to lsp standards. -- Loading lsp features etc. automatically. -- Error handling. -- And more. - -Not done yet. - -### BEd +## BEd Better ed.
An ed implementation (mostly posix compliant) but with: @@ -65,11 +32,42 @@ An ed implementation (mostly posix compliant) but with: - File operations, directory & files listing. - Nerdfonts & themable colorized outputs. - Better address marking with ranges. +- text copy/paste & registers. - & A lot more cool stuff. -#### Implementation status: +### Implementation status: - Internal buffer: - Super fast and memory efficient buffer implementation done. - Loading from files/subshell commands done. - Most of the posix ed commands except regex ones done. + +### Modules + +#### `Vase` + +Vase is a avl piece tree like structure which handles loading, insertion, erasure, regex searching, regex replacing etc. +
+done. + +#### `hl` + +`hl` is a stateful super fast syntax highlighter. +
+mostly done. + +#### `lsp` + +`lsp` is a generic plug for lsp's. +
+
+It should support: +- Lsp over ssh or stdin/out or unix sockets etc. +- Handling multiple lsps at once (including over a single file.) +- Converting offsets encoding between utf16 - utf8 according to lsp standards. +- Loading lsp features etc. automatically. +- Error handling. +- And more. + +Not done yet. + diff --git a/flake.nix b/flake.nix index 502e229..d7744cb 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "Crib - An IDE."; + description = "BEd - Better Ed."; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -47,17 +47,16 @@ ''; }; - crib = pkgs.stdenv.mkDerivation { - pname = "crib"; + bed = pkgs.stdenv.mkDerivation { + pname = "bed"; version = "0.1.0"; src = ./.; nativeBuildInputs = with hostPkgs; [ + bash pkg-config gnumake - xxd - bash findutils binutils ]; @@ -78,19 +77,19 @@ installPhase = '' mkdir -p $out/bin - cp bin/crib $out/bin/crib + cp bin/bed $out/bin/bed ''; }; in { packages.${system} = { - default = crib; - crib = crib; + default = bed; + bed = bed; mruby = mruby; }; devShells.${system}.default = hostPkgs.mkShell { - inputsFrom = [ crib ]; + inputsFrom = [ bed ]; packages = [ hostPkgs.clang-tools @@ -99,7 +98,6 @@ hostPkgs.gnumake hostPkgs.bear hostPkgs.ruby - hostPkgs.xxd hostPkgs.gdb hostPkgs.binutils hostPkgs.findutils diff --git a/include/bed.h b/include/bed.h new file mode 100644 index 0000000..dfd545a --- /dev/null +++ b/include/bed.h @@ -0,0 +1,32 @@ +#pragma once + +#include "definitions.h" +#include "internal/buffer/buffer.h" +#include "internal/commands/commands.h" +#include "internal/commands/suffixes.h" +#include "internal/marks/marks.h" +#include "pch.h" + +namespace bed { +struct BEd { + internal::trie::Trie commands; + internal::commands::Command no_op; + internal::commands::Command eof_op; + std::array, 26> suffixes; + + bool help_mode = false; + std::string last_help = ""; + bool prompt_mode = true; + std::function prompt = nullptr; + bool suppress_mode = false; + std::string last_regex = ""; + + std::unordered_map buffers; + internal::buffer::Buffer *active; + + BEd(std::vector args); + ~BEd(); + void handle(std::string_view cmd, bool eof); + void run(); +}; +} // namespace bed diff --git a/include/cli.h b/include/cli.h deleted file mode 100644 index e54ae0b..0000000 --- a/include/cli.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "pch.h" - -namespace crib::cli { -struct Command { - void (*run)(std::vector); - std::string (*summary)(); - void (*help)(); -}; - -struct cli_error : std::runtime_error { - int exit_code; - cli_error(std::string msg, int code) - : std::runtime_error(std::move(msg)), exit_code(code) {} -}; - -extern bool use_colors; -void help(); -int execute(const std::string &name, const Command &cmd, int argc, char *argv[]); -int run(int argc, char *argv[]); -} // namespace crib::cli diff --git a/include/commands/bed/bed.h b/include/commands/bed/bed.h deleted file mode 100644 index edfb1ed..0000000 --- a/include/commands/bed/bed.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include "internal/vase/vase.h" -#include "pch.h" - -namespace crib::commands::bed { -struct bed_error : std::runtime_error { - bed_error(const char *msg) : std::runtime_error(msg) {} -}; - -struct Command { - enum struct Type : uint8_t { - Invalid, - Quit, - ForceQuit, - PromptToggle, - HelpToggle, - Help, - Print, - List, - Number, - Append, - Insert, - Change, - Delete, - Write, - Join, - Mark, - Edit, - ForceEdit, - Filename, - Dump, - None - } type; - - struct Address { - enum struct Type : uint8_t { - None, - Current, - Last, - Number, - Mark, - SearchForward, - SearchBackward - } type; - int64_t number = 0; - std::string regex = ""; - int64_t offset = 0; - char mark = '\0'; - }; - - Address start{}; - Address end{}; - static constexpr uint8_t SEMICOLON = 0b01; - static constexpr uint8_t RANGE = 0b10; - uint8_t address_flags = 0; - char suffix = '\0'; - std::string argument; -}; - -struct BEd { - crib::internal::vase::Vase vase; - uint64_t line = 0; - bool modified = false; - bool quitting = false; - bool editing = false; - uint64_t marks[26]{0}; - std::string last_regex = ""; - bool help_mode = false; - bool suppress_mode = false; - bool prompt_mode = false; - std::string prompt = "*"; - std::filesystem::path save_path = ""; - std::string last_message = ""; - - BEd(std::string file_path, bool suppress_mode, std::string prompt_) - : vase("/tmp"), suppress_mode(suppress_mode), prompt(prompt_) { - if (prompt == "") - prompt = "*"; - else - prompt_mode = true; - if (file_path != "") - handle("E " + file_path, false); - } - - void parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr); - void resolve_address(Command::Address addr, uint64_t *out_line); - 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); -}; - -std::string summary(); -void help(); -void run(std::vector); -} // namespace crib::commands::bed diff --git a/include/definitions.h b/include/definitions.h new file mode 100644 index 0000000..a633e7f --- /dev/null +++ b/include/definitions.h @@ -0,0 +1,18 @@ +#pragma once + +#include "internal/vase/vase.h" +#include "pch.h" + +namespace bed { +struct fatal_error : std::runtime_error { + uint8_t code; + fatal_error(std::string msg, uint8_t code) + : std::runtime_error(msg), code(code) {} +}; + +struct ed_error : std::runtime_error { + ed_error(std::string msg) : std::runtime_error(msg) {} +}; + +struct BEd; +} // namespace bed diff --git a/include/internal/address/address.h b/include/internal/address/address.h new file mode 100644 index 0000000..50a98c7 --- /dev/null +++ b/include/internal/address/address.h @@ -0,0 +1,115 @@ +#pragma once + +#include "definitions.h" +#include "internal/generic.h" +#include "pch.h" + +namespace bed::internal::address { +struct address_error : ed_error { + address_error(const char *msg) : ed_error(msg) {} +}; + +struct Address { + // % refers to range of whatever was resulted from the previous modification. + // it expands in theory to a Num,Num and so can be followed by chaining more adresses the ed way. + // this is handled and resolved by the handle function, + // the constuctor and resolve etc. are also only called in the handle function, + // i.e. handle is the only public facing API from this namespace & class for now. + // + // in case of any issue an instance of address_error(const char *) is thrown. + + struct Result { + std::array data{}; + uint8_t size{0}; + std::span span(uint8_t max = UINT8_MAX) const { + return {data.data(), std::min(size, max)}; + } + }; + + struct None {}; + + // Posix ed types of addressing. + struct Current {}; // . + + struct Last {}; // $ + + struct Number { // n + uint64_t i; + }; + + struct Mark { // 'm + char m; + }; + + struct RegexLine { // /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; + }; + + struct DiagnosticNext { // ^ or % for previous/next diagnostic + internal::Direction dir; + }; + + struct SymbolDefinition { // goto symbol definition. (From lsp or using internal language parsers) + std::string sym; + }; + + struct SymbolReference { // goto nth symbol reference + uint16_t n; + std::string sym; + }; + + struct SymbolReferenceNext { // >sym> or + base{}; + + // they can then be followed by any number of +n or -n etc accumulating in. + int64_t offset = 0; + + Address(std::string &cmd, uint64_t &i); + Address() = default; + + uint64_t resolve(BEd &ctx); + + static Result handle(BEd &ctx, std::string &cmd, uint64_t &i); +}; +} // namespace bed::internal::address diff --git a/include/internal/buffer/buffer.h b/include/internal/buffer/buffer.h new file mode 100644 index 0000000..5ad24c3 --- /dev/null +++ b/include/internal/buffer/buffer.h @@ -0,0 +1,34 @@ +#pragma once + +#include "definitions.h" +#include "internal/marks/marks.h" +#include "pch.h" + +namespace bed::internal::buffer { +struct Buffer { + internal::vase::Vase vase; + internal::marks::MarksEngine marks; + uint64_t line = 0; + bool modified; + std::filesystem::path save_path = ""; + struct { + uint64_t start{0}; + uint64_t end{0}; + } prev_range; + + Buffer(); + Buffer(std::string command); + Buffer(std::filesystem::path path); + + ~Buffer() = default; + + void load(std::string command); + void load(std::filesystem::path path); + void jump(uint64_t n_line); + void join(uint64_t start_line, uint64_t end_line); + void remove(uint64_t start_line, uint64_t end_line); + void append(std::string text, uint64_t line); + void print(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/commands/commands.h b/include/internal/commands/commands.h new file mode 100644 index 0000000..e6aec59 --- /dev/null +++ b/include/internal/commands/commands.h @@ -0,0 +1,30 @@ +#pragma once + +#include "definitions.h" +#include "internal/trie/trie.h" +#include "pch.h" + +namespace bed::internal::commands { +struct Command { + enum struct AddressMode : uint8_t { + None, + Single, + Range + } address_mode; + + enum struct SuffixKind : uint8_t { + None, + Suffix, + Argument, + Continuation + } suffix; + + std::string desc; + + bool accept_zero; + + void (*handle)(BEd &, std::span, std::string_view); + + static void register_posix(BEd &ctx); +}; +} // namespace bed::internal::commands diff --git a/include/internal/commands/suffixes.h b/include/internal/commands/suffixes.h new file mode 100644 index 0000000..454ad7f --- /dev/null +++ b/include/internal/commands/suffixes.h @@ -0,0 +1,13 @@ +#pragma once + +#include "definitions.h" +#include "pch.h" + +namespace bed::internal::commands { +struct Suffix { + std::string desc; + void (*handle)(BEd &); + + static void register_suffixes(BEd &ctx); +}; +} // namespace bed::internal::commands diff --git a/include/internal/vase/iterators/iter.h b/include/internal/generic.h similarity index 62% rename from include/internal/vase/iterators/iter.h rename to include/internal/generic.h index cf873ec..0d61e08 100644 --- a/include/internal/vase/iterators/iter.h +++ b/include/internal/generic.h @@ -1,9 +1,8 @@ #pragma once -#include "../shard.h" #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal { enum struct Direction : uint8_t { Forward, Backward diff --git a/include/internal/marks/marks.h b/include/internal/marks/marks.h new file mode 100644 index 0000000..3487c99 --- /dev/null +++ b/include/internal/marks/marks.h @@ -0,0 +1,7 @@ +#pragma once + +#include "pch.h" + +namespace bed::internal::marks { +struct MarksEngine {}; +} // namespace bed::internal::marks diff --git a/include/internal/trie/trie.h b/include/internal/trie/trie.h new file mode 100644 index 0000000..fde446a --- /dev/null +++ b/include/internal/trie/trie.h @@ -0,0 +1,246 @@ +#pragma once + +#include "pch.h" + +namespace bed::internal::trie { +template +struct Trie { + using V = std::conditional_t, std::monostate, T>; + + struct Node { + std::string edge; + std::optional value{}; + std::vector children; + Node(std::string e = {}) : edge(std::move(e)) {} + ~Node() { + for (auto *c : children) + delete c; + }; + } root; + + bool case_sensitive; + + Trie(bool cs = true) : case_sensitive(cs) {} + + void insert(std::string_view key) + requires std::is_void_v + { + _insert(key, std::monostate{}); + } + + void insert(std::string_view key, V el) + requires(!std::is_void_v) + { + _insert(key, std::move(el)); + } + + void _insert(std::string_view key, V &&el) { + Node *current = &root; + uint64_t pos = 0; + while (pos < key.size()) { + Node *child = find_child(*current, key[pos]); + if (!child) { + auto *node = new Node(std::string(key.substr(pos))); + node->value = std::move(el); + current->children.push_back(node); + return; + } + const auto common = common_prefix(child->edge, key.substr(pos)); + if (common == child->edge.size()) { + pos += common; + current = child; + continue; + } + auto *split = new Node(child->edge.substr(0, common)); + child->edge.erase(0, common); + split->children.push_back(child); + current->children.erase( + std::find(current->children.begin(), current->children.end(), child) + ); + current->children.push_back(split); + pos += common; + if (pos == key.size()) { + split->value = std::move(el); + return; + } + auto *node = new Node(std::string(key.substr(pos))); + node->value = std::move(el); + split->children.push_back(node); + return; + } + current->value = std::move(el); + } + + void remove(std::string_view key) { + _remove(root, key, 0); + } + + bool _remove(Node &node, std::string_view key, uint64_t pos) { + if (pos == key.size()) { + if (!node.value) + return false; + node.value.reset(); + return true; + } + Node *child = find_child(node, key[pos]); + if (!child) + return false; + const auto remaining = key.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common != child->edge.size()) + return false; + const auto child_pos = pos + common; + if (!_remove(*child, key, child_pos)) + return false; + if (!child->value && child->children.empty()) { + auto it = std::find( + node.children.begin(), + node.children.end(), + child + ); + node.children.erase(it); + delete child; + return true; + } + if (!child->value && child->children.size() == 1) { + Node *grandchild = child->children.front(); + child->edge += grandchild->edge; + child->value = std::move(grandchild->value); + child->children = std::move(grandchild->children); + grandchild->children.clear(); + delete grandchild; + } + return true; + } + + std::vector search(std::string_view prefix) { + std::vector result; + Node *current = &root; + std::string key; + uint64_t pos = 0; + while (pos < prefix.size()) { + Node *child = find_child(*current, prefix[pos]); + if (!child) + return result; + const auto remaining = prefix.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common == 0) + return result; + if (common < child->edge.size()) { + if (common == remaining.size()) { + key += child->edge; + collect(*child, key, result); + return result; + } + return result; + } + key += child->edge; + pos += common; + current = child; + } + collect(*current, key, result); + return result; + } + + static void collect( + const Node &node, + std::string &key, + std::vector &result + ) { + if (node.value) + result.push_back(key); + for (auto *child : node.children) { + const auto old_size = key.size(); + key += child->edge; + collect(*child, key, result); + key.resize(old_size); + } + } + + uint64_t longest_match(std::string_view input) { + Node *current = &root; + uint64_t pos = 0; + uint64_t longest = 0; + if (current->value) + longest = 0; + while (pos < input.size()) { + Node *child = find_child(*current, input[pos]); + if (!child) + break; + const auto remaining = input.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common != child->edge.size()) + break; + pos += common; + current = child; + if (current->value) + longest = pos; + } + return longest; + } + + bool matches(std::string_view key) + requires(std::is_void_v) + { + Node *current = &root; + uint64_t pos = 0; + while (pos < key.size()) { + Node *child = find_child(*current, key[pos]); + if (!child) + return false; + const auto remaining = key.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common != child->edge.size()) + return false; + pos += common; + current = child; + } + return current->value.has_value(); + } + + std::optional get(std::string_view key) + requires(!std::is_void_v) + { + Node *current = &root; + uint64_t pos = 0; + while (pos < key.size()) { + Node *child = find_child(*current, key[pos]); + if (!child) + return std::nullopt; + const auto remaining = key.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common != child->edge.size()) + return std::nullopt; + pos += common; + current = child; + } + if (!current->value) + return std::nullopt; + return *current->value; + } + + bool equal_char(char a, char b) const { + if (case_sensitive) + return a == b; + return std::tolower((unsigned char)a) == std::tolower((unsigned char)b); + } + + uint64_t common_prefix( + std::string_view a, + std::string_view b + ) { + const auto n = std::min(a.size(), b.size()); + uint64_t i = 0; + while (i < n && equal_char(a[i], b[i])) + ++i; + return i; + } + + Node *find_child(Node &node, char first) { + for (auto *child : node.children) + if (equal_char(child->edge.front(), first)) + return child; + return nullptr; + } +}; +} // namespace bed::internal::trie diff --git a/include/internal/vase/buffer/append.h b/include/internal/vase/buffer/append.h index eac53ad..e258e6b 100644 --- a/include/internal/vase/buffer/append.h +++ b/include/internal/vase/buffer/append.h @@ -4,7 +4,7 @@ #include "buffer.h" #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct AppendBuffer : Buffer { char *buf = nullptr; uint64_t allocated_capacity = 0; @@ -22,4 +22,4 @@ struct AppendBuffer : Buffer { private: void grow(uint64_t len); }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/include/internal/vase/buffer/buffer.h b/include/internal/vase/buffer/buffer.h index fe54580..2f1812a 100644 --- a/include/internal/vase/buffer/buffer.h +++ b/include/internal/vase/buffer/buffer.h @@ -2,10 +2,10 @@ #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct Buffer { virtual const char *read(uint64_t pos) = 0; virtual uint64_t length() = 0; virtual ~Buffer() = default; }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/include/internal/vase/buffer/original.h b/include/internal/vase/buffer/original.h index 689f36f..f2e9d41 100644 --- a/include/internal/vase/buffer/original.h +++ b/include/internal/vase/buffer/original.h @@ -3,7 +3,7 @@ #include "buffer.h" #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct OriginalBuffer : Buffer { const char *buf; uint64_t len; @@ -16,4 +16,4 @@ struct OriginalBuffer : Buffer { const char *read(uint64_t pos) override; uint64_t length() override; }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/include/internal/vase/constants.h b/include/internal/vase/constants.h index 648e5ba..a6b4579 100644 --- a/include/internal/vase/constants.h +++ b/include/internal/vase/constants.h @@ -2,6 +2,6 @@ #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal::vase { constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024; } diff --git a/include/internal/vase/iterators/line.h b/include/internal/vase/iterators/line.h index 72514ee..9c12a04 100644 --- a/include/internal/vase/iterators/line.h +++ b/include/internal/vase/iterators/line.h @@ -4,7 +4,7 @@ #include "pch.h" #include "petal.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct LineIterator { PetalIterator it; const char *chunk; @@ -70,4 +70,4 @@ struct Iterator { private: LineIterator it; }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/include/internal/vase/iterators/petal.h b/include/internal/vase/iterators/petal.h index 059d102..129f537 100644 --- a/include/internal/vase/iterators/petal.h +++ b/include/internal/vase/iterators/petal.h @@ -1,10 +1,10 @@ #pragma once #include "../shard.h" -#include "iter.h" +#include "internal/generic.h" #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct PetalIterator { Direction dir; Shard *root = nullptr; @@ -26,4 +26,4 @@ struct PetalIterator { private: Petal *_next(uint64_t *offset); }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/include/internal/vase/shard.h b/include/internal/vase/shard.h index 1787f82..508317a 100644 --- a/include/internal/vase/shard.h +++ b/include/internal/vase/shard.h @@ -5,7 +5,7 @@ #include "constants.h" #include "pch.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct Shard { enum struct Kind : uint8_t { Branch, @@ -63,4 +63,4 @@ struct Petal : Shard { source(source), pos(pos) {}; }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/include/internal/vase/vase.h b/include/internal/vase/vase.h index 0d673f8..1d01d38 100644 --- a/include/internal/vase/vase.h +++ b/include/internal/vase/vase.h @@ -7,7 +7,7 @@ #include "pch.h" #include "shard.h" -namespace crib::internal::vase { +namespace bed::internal::vase { struct Point { uint64_t row; uint64_t col; @@ -114,4 +114,4 @@ private: std::string_view pattern, Range range, std::string_view options ); }; -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/bed.cc b/src/bed.cc new file mode 100644 index 0000000..786bcf8 --- /dev/null +++ b/src/bed.cc @@ -0,0 +1,5 @@ +#include "bed.h" + +namespace bed { +void run(std::vector) {} +} // namespace bed diff --git a/src/bed/handle.cc b/src/bed/handle.cc new file mode 100644 index 0000000..3764b3d --- /dev/null +++ b/src/bed/handle.cc @@ -0,0 +1,95 @@ +#include "bed.h" +#include "internal/address/address.h" +#include "internal/commands/commands.h" + +namespace bed { +void BEd::handle(std::string_view cmd_, bool eof) { + using namespace internal::commands; + std::string cmd(cmd_); + if (eof) { + eof_op.handle(*this, {}, ""); + return; + } + uint64_t i = 0; + auto skip_space = [&] { + while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) + ++i; + }; + auto addresses = internal::address::Address::handle(*this, cmd, i); + if (i >= cmd.size()) { + if (!no_op.accept_zero) + for (auto l : addresses.span()) + if (l == 0) + throw ed_error("Invalid address given."); + switch (no_op.address_mode) { + case Command::AddressMode::None: + no_op.handle(*this, addresses.span(0), ""); + break; + case Command::AddressMode::Single: + no_op.handle(*this, addresses.span(1), ""); + break; + case Command::AddressMode::Range: + no_op.handle(*this, addresses.span(2), ""); + break; + } + return; + } + uint64_t len = commands.longest_match(cmd.substr(i)); + if (len == 0) + throw ed_error("Command not found."); + std::optional command_opt = commands.get(cmd.substr(i, len)); + i += len; + if (!command_opt) + throw ed_error("Command error."); + Command command = command_opt.value(); + std::string argument; + Suffix *suffix = nullptr; + switch (command.suffix) { + case Command::SuffixKind::None: + skip_space(); + if (i < cmd.size()) + throw ed_error("Command error."); + break; + case Command::SuffixKind::Suffix: + if (i < cmd.size()) { + if (suffixes[cmd[i] - 'a'].has_value()) + suffix = &(suffixes[cmd[i++] - 'a'].value()); + skip_space(); + std::cout << i << " " << cmd << std::endl; + if (i < cmd.size()) + throw ed_error("Command error."); + } + break; + case Command::SuffixKind::Argument: + if (i < cmd.size()) { + if (cmd[i] == ' ' || cmd[i] == '\t') + skip_space(); + else + throw ed_error("Command Error."); + argument = cmd.substr(i); + } + break; + case Command::SuffixKind::Continuation: + argument = cmd.substr(i); + break; + } + if (!command.accept_zero) + for (auto l : addresses.span()) + if (l == 0) + throw ed_error("Invalid address given."); + switch (command.address_mode) { + case Command::AddressMode::None: + command.handle(*this, addresses.span(0), argument); + break; + case Command::AddressMode::Single: + command.handle(*this, addresses.span(1), argument); + break; + case Command::AddressMode::Range: + command.handle(*this, addresses.span(2), argument); + break; + } + if (suffix) + suffix->handle(*this); + return; +} +} // namespace bed diff --git a/src/bed/run.cc b/src/bed/run.cc new file mode 100644 index 0000000..8e311c6 --- /dev/null +++ b/src/bed/run.cc @@ -0,0 +1,53 @@ +#include "bed.h" + +namespace bed { +BEd::BEd(std::vector args) { + internal::commands::Command::register_posix(*this); + internal::commands::Suffix::register_suffixes(*this); + std::string prompt_ = ""; + std::string file = ""; + bool suppress = false; + for (size_t i = 1; i < args.size(); i++) { + if (args[i] == "-p") { + i++; + if (i >= args.size()) + throw fatal_error("Prompt not specified!", 1); + prompt_ = args[i]; + } else if (args[i] == "-s") { + suppress = true; + } else { + if (file.size()) + throw fatal_error("Invalid arguments given.", 1); + file = args[i]; + } + } + if (prompt_ != "") + prompt = [p = std::move(prompt_)](BEd &) { return p; }; + else + prompt_mode = false; + suppress_mode = suppress; + active = new internal::buffer::Buffer(); + buffers.emplace("0", active); + if (file != "") + handle("E " + file, false); +} + +BEd::~BEd() { + for (auto &[_, buffer] : buffers) + delete buffer; +} + +void BEd::run() { + while (true) { + std::string cmd; + if (prompt_mode) + std::cout << prompt(*this); + bool eof = !std::getline(std::cin, cmd); + try { + handle(cmd, eof); + } catch (ed_error &e) { + std::cout << e.what() << std::endl; + } + } +} +} // namespace bed diff --git a/src/cli/cli.cc b/src/cli/cli.cc deleted file mode 100644 index 596412b..0000000 --- a/src/cli/cli.cc +++ /dev/null @@ -1,72 +0,0 @@ -#include "cli.h" -#include "commands/bed/bed.h" - -namespace crib::cli { -static const std::unordered_map commands = { - {"bed", - {crib::commands::bed::run, - crib::commands::bed::summary, - crib::commands::bed::help}}, -}; - -bool use_color() { - if (std::getenv("NO_COLOR")) - return false; - if (std::getenv("CLICOLOR_FORCE")) - return true; - if (!isatty(STDERR_FILENO)) - return false; - const char *term = std::getenv("TERM"); - if (!term || std::string_view(term) == "dumb" || std::string_view(term) == "xterm") - return false; - if (const char *clicolor = std::getenv("CLICOLOR")) - return std::string_view(clicolor) != "0"; - return true; -} - -bool use_colors = use_color(); - -void help() { - std::cout << "Usage: crib [options]\n\n" - << "Where command is one of\n"; - for (auto &[name, cmd] : commands) - std::cout << " " << name << " - " << cmd.summary() << "\n"; -} - -int execute(const std::string &name, const Command &cmd, int argc, char *argv[]) { - try { - std::vector args(argv, argv + argc); - cmd.run(args); - return 0; - } catch (const cli_error &e) { - std::cerr << "Error running " << name << ": " << e.what() << '\n'; - return e.exit_code; - } catch (const std::exception &e) { - std::cerr << "Error running " << name << ": " << e.what() << '\n'; - return 1; - } catch (...) { - std::cerr << "Unknown error running " << name << '\n'; - return 1; - } -} - -int run(int argc, char *argv[]) { - std::string invoked_as = std::filesystem::path(argv[0]).filename(); - - if (auto it = commands.find(invoked_as); it != commands.end()) - return execute(it->first, it->second, argc, argv); - - if (argc > 1) - if (auto it = commands.find(argv[1]); it != commands.end()) - return execute(it->first, it->second, argc - 1, argv + 1); - - if (argc > 1 && std::string(argv[1]) == "--help") { - help(); - return 0; - } - - std::cerr << "Invalid command given.\n\n"; - help(); - return 2; -} -} // namespace crib::cli diff --git a/src/commands/bed/bed.cc b/src/commands/bed/bed.cc deleted file mode 100644 index 0ee9d90..0000000 --- a/src/commands/bed/bed.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include "commands/bed/bed.h" -#include "cli.h" - -namespace crib::commands::bed { -std::string summary() { - return "Better Ed, a modern take on a posix compliant line editor."; -} - -void help() {} - -void run(std::vector args) { - std::string prompt = ""; - std::string file; - bool suppress = false; - for (size_t i = 1; i < args.size(); i++) { - if (args[i] == "-p") { - i++; - if (i >= args.size()) - throw crib::cli::cli_error("Prompt not specified!", 1); - prompt = args[i]; - } else if (args[i] == "-s") { - suppress = true; - } else { - if (file.size()) - throw crib::cli::cli_error("Invalid arguments given.", 1); - file = args[i]; - } - } - BEd bed(file, suppress, prompt); - std::string command; - while (true) { - std::string cmd; - if (bed.prompt_mode) - std::cout << bed.prompt; - bool eof = !std::getline(std::cin, cmd); - if (!bed.handle(cmd, eof)) - return; - } -} -} // namespace crib::commands::bed diff --git a/src/commands/bed/handle.cc b/src/commands/bed/handle.cc deleted file mode 100644 index f9c82c7..0000000 --- a/src/commands/bed/handle.cc +++ /dev/null @@ -1,384 +0,0 @@ -#include "commands/bed/bed.h" - -namespace crib::commands::bed { -bool BEd::handle(std::string cmd, bool eof) { - using namespace crib::internal::vase; - try { - if (line > vase.lines()) - line = vase.lines(); - Command command = parse(cmd, eof); - bool was_quitting = quitting; - quitting = false; - bool was_editing = editing; - editing = false; - switch (command.type) { - case Command::Type::Quit: - if (modified && !was_quitting) { - quitting = true; - throw bed_error("Buffer modified."); - } else { - return false; - } - case Command::Type::ForceQuit: - return false; - case Command::Type::None: { - 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); - } else { - line++; - } - if (line == 0) - throw bed_error("Line 0 is invalid."); - if (line > vase.lines()) - throw bed_error("Line position too high."); - Iterator it = vase.iterate(line - 1, Direction::Forward); - it.next(); - std::cout << it.line << std::endl; - } break; - case Command::Type::Invalid: - throw bed_error("Invalid command."); - case Command::Type::HelpToggle: - help_mode = !help_mode; - if (help_mode && last_message != "") - std::cout << last_message << std::endl; - break; - case Command::Type::Help: - if (last_message != "") - std::cout << last_message << std::endl; - break; - case Command::Type::PromptToggle: - prompt_mode = !prompt_mode; - break; - case Command::Type::Print: { - 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 bed_error("Line 0 is invalid."); - if (line_end < line_start) - throw bed_error("Invalid address range."); - Iterator it = vase.iterate(line_start - 1, Direction::Forward); - while (it.next() && line_start++ <= line_end) - 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 bed_error("Line 0 is invalid."); - if (line_end < line_start) - throw bed_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; - 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 bed_error("Line 0 is invalid."); - if (line_end < line_start) - throw bed_error("Invalid address range."); - Iterator it = vase.iterate(line_start - 1, Direction::Forward); - while (it.next() && line_start <= line_end) - std::cout << line_start++ << "\t" << it.line << std::endl; - 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; - while (std::getline(std::cin, cline)) { - if (cline == ".") - break; - line_count++; - text.append(cline); - text.push_back('\n'); - } - if (text.empty()) { - if (line == 0) - line = 1; - break; - } - vase.snapshot(); - append(text, line); - modified = true; - 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; - while (std::getline(std::cin, cline)) { - if (cline == ".") - break; - line_count++; - text.append(cline); - text.push_back('\n'); - } - if (text.empty()) - break; - vase.snapshot(); - append(text, line - 1); - modified = true; - line += line_count - 1; - } break; - case Command::Type::Change: { - uint64_t line_start = line; - uint64_t line_end = line; - if (command.start.type != Command::Address::Type::None) { - resolve_address(command.start, &line_start); - if (line_start == 0) - line_start = 1; - line_end = line_start; - if (command.address_flags & Command::RANGE) - resolve_address(command.end, &line_end); - } - if (line_end < line_start) - throw bed_error("Invalid address range."); - std::string text; - std::string cline; - uint64_t line_count = 0; - while (std::getline(std::cin, cline)) { - if (cline == ".") - break; - line_count++; - text.append(cline); - text.push_back('\n'); - } - vase.snapshot(); - remove(line_start, line_end); - line = line_start; - if (text.empty()) - break; - append(text, line); - modified = true; - line += line_count; - } break; - case Command::Type::Delete: { - uint64_t line_start = line; - uint64_t line_end = line; - if (command.start.type != Command::Address::Type::None) { - resolve_address(command.start, &line_start); - if (line_start == 0) - line_start = 1; - line_end = line_start; - if (command.address_flags & Command::RANGE) - resolve_address(command.end, &line_end); - } - if (line_end < line_start) - throw bed_error("Invalid address range."); - vase.snapshot(); - vase.erase({{line_start - 1, 0}, {line_end, 0}}); - modified = true; - line = line_start; - if (line > vase.lines()) - line = vase.lines(); - } break; - case Command::Type::Write: { - std::string path = command.argument; - if (path.empty()) { - path = save_path; - if (path.empty()) - throw bed_error("Need file to write to."); - } - uint64_t line_start = 1; - uint64_t line_end = vase.lines(); - if (vase.lines() == 0) - line_start = line_end = 0; - 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 (vase.lines() && line_start == 0) - throw bed_error("Line 0 is invalid."); - if (line_end < line_start) - throw bed_error("Invalid address range."); - uint64_t bytes = 0; - if (path[0] == '!') { - FILE *pipe = popen(path.c_str() + 1, "w"); - if (!pipe) - throw bed_error("Error starting command."); - Iterator it = vase.iterate(line_start - 1, Direction::Forward); - while (it.next() && line_start++ <= line_end) { - it.line += '\n'; - bytes += it.line.size(); - if (fputs(it.line.c_str(), pipe) == EOF) { - pclose(pipe); - throw bed_error("Error writing to command."); - } - } - if (pclose(pipe) == -1) - throw bed_error("Error closing command."); - } else { - std::ofstream file(path, std::ios::out | std::ios::trunc); - if (!file) - throw bed_error("Error writing to file."); - Iterator it = vase.iterate(line_start - 1, Direction::Forward); - while (it.next() && line_start++ <= line_end) { - file << it.line << '\n'; - bytes += it.line.size() + 1; - } - if (!file) - throw bed_error("Error writing to file."); - save_path = path; - modified = false; - } - 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 bed_error("Invalid address range."); - if (line_start == 0) - throw bed_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 bed_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; - throw bed_error("Buffer modified."); - } - std::string path = command.argument; - if (path.empty()) { - path = save_path; - if (path.empty()) - throw bed_error("Need file to read from."); - } - if (path[0] == '!') { - path.erase(1); - Vase new_vase(std::string(path), "/tmp"); - vase = std::move(new_vase); - modified = true; - } else { - Vase new_vase(std::filesystem::path(path), "/tmp"); - vase = std::move(new_vase); - save_path = path; - modified = false; - } - line = vase.lines(); - if (!suppress_mode) - std::cout << vase.length() << std::endl; - } break; - case Command::Type::ForceEdit: { - std::string path = command.argument; - if (path.empty()) { - path = save_path; - if (path.empty()) - throw bed_error("Need file to read from."); - } - if (path[0] == '!') { - path.erase(0, 1); - Vase new_vase(std::string(path), "/tmp"); - vase = std::move(new_vase); - modified = true; - } else { - Vase new_vase(std::filesystem::path(path), "/tmp"); - vase = std::move(new_vase); - save_path = path; - modified = false; - } - line = vase.lines(); - std::fill(std::begin(marks), std::end(marks), '\0'); - if (!suppress_mode) - std::cout << vase.length() << std::endl; - } break; - case Command::Type::Filename: { - std::string path = command.argument; - if (path.empty()) - throw bed_error("No filename given."); - save_path = path; - std::cout << save_path.string() << std::endl; - } break; - case Command::Type::Dump: - Shard::dump(vase.root); - break; - } - if (command.suffix) { - Iterator it = vase.iterate(line - 1, Direction::Forward); - it.next(); - switch (command.suffix) { - case 'p': - std::cout << it.line << std::endl; - break; - case 'n': - std::cout << line << "\t" << it.line << std::endl; - break; - case 'l': - std::cout << list_string(it.line) << std::endl; - break; - } - } - } catch (bed_error &e) { - last_message = e.what(); - std::cout << "?" << std::endl; - if (help_mode) - std::cout << e.what() << std::endl; - } - return true; -} -} // namespace crib::commands::bed diff --git a/src/commands/bed/helpers.cc b/src/commands/bed/helpers.cc deleted file mode 100644 index c3c4e57..0000000 --- a/src/commands/bed/helpers.cc +++ /dev/null @@ -1,83 +0,0 @@ -#include "commands/bed/bed.h" - -namespace crib::commands::bed { -void BEd::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 BEd::remove(uint64_t start_line, uint64_t end_line) { - vase.erase({{start_line - 1, 0}, {end_line, 0}}); -} - -void BEd::join(uint64_t start_line, uint64_t end_line) { - vase.regex_search_replace("\\n", {{start_line - 1, 0}, {end_line, 0}}, "", ""); -} - -std::string BEd::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::bed diff --git a/src/commands/bed/parsers/command.cc b/src/commands/bed/parsers/command.cc deleted file mode 100644 index 035ad85..0000000 --- a/src/commands/bed/parsers/command.cc +++ /dev/null @@ -1,176 +0,0 @@ -#include "commands/bed/bed.h" - -namespace crib::commands::bed { -Command BEd::parse(std::string cmd, bool eof) { - Command command = {}; - if (eof) - return (command.type = Command::Type::Quit, command); - uint64_t i = 0; - auto skip_space = [&] { - while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) - ++i; - }; - Command::Address first; - Command::Address second; - parse_address(cmd, i, first); - bool have_range = false; - while (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) { - char sep = cmd[i++]; - if (sep == ';') - resolve_address(first, &line); - command.address_flags = sep == ';' ? Command::SEMICOLON : 0; - if (have_range) - first = second; - parse_address(cmd, i, second); - have_range = true; - } - if (have_range) { - if (first.type != Command::Address::Type::None - && second.type == Command::Address::Type::None) { - second = first; - } else if (first.type == Command::Address::Type::None) { - if (command.address_flags & Command::SEMICOLON) { - first.type = Command::Address::Type::Current; - } else { - first.type = Command::Address::Type::Number; - first.number = 1; - } - if (second.type == Command::Address::Type::None) - second.type = Command::Address::Type::Last; - } - command.address_flags |= Command::RANGE; - command.start = first; - command.end = second; - } else { - command.start = first; - } - if (i >= cmd.size()) { - command.type = Command::Type::None; - return command; - } - switch (cmd[i]) { - case 'q': - command.type = Command::Type::Quit; - skip_space(); - if (i >= cmd.size()) - return command; - throw bed_error("Invalid command."); - case 'Q': - command.type = Command::Type::ForceQuit; - skip_space(); - if (i >= cmd.size()) - return command; - throw bed_error("Invalid command."); - case 'p': - command.type = Command::Type::Print; - i++; - break; - case 'l': - command.type = Command::Type::List; - i++; - break; - case 'n': - command.type = Command::Type::Number; - i++; - break; - case 'P': - command.type = Command::Type::PromptToggle; - i++; - break; - case 'H': - command.type = Command::Type::HelpToggle; - i++; - break; - case 'h': - command.type = Command::Type::Help; - i++; - break; - case 'a': - command.type = Command::Type::Append; - i++; - break; - case 'i': - command.type = Command::Type::Insert; - i++; - break; - case 'c': - command.type = Command::Type::Change; - i++; - break; - case 'd': - command.type = Command::Type::Delete; - i++; - break; - case 'j': - command.type = Command::Type::Join; - i++; - break; - case 'w': - command.type = Command::Type::Write; - i++; - if (i >= cmd.size()) - return command; - if (cmd[i] == ' ' || cmd[i] == '\t') - skip_space(); - else - throw bed_error("Invalid command."); - command.argument = cmd.substr(i); - return command; - case 'e': - command.type = Command::Type::Edit; - i++; - if (i >= cmd.size()) - return command; - if (cmd[i] == ' ' || cmd[i] == '\t') - skip_space(); - else - throw bed_error("Invalid command."); - command.argument = cmd.substr(i); - return command; - case 'E': - command.type = Command::Type::ForceEdit; - i++; - if (i >= cmd.size()) - return command; - if (cmd[i] == ' ' || cmd[i] == '\t') - skip_space(); - else - throw bed_error("Invalid command."); - command.argument = cmd.substr(i); - return command; - case 'f': - command.type = Command::Type::Filename; - i++; - if (i >= cmd.size()) - return command; - if (cmd[i] == ' ' || cmd[i] == '\t') - skip_space(); - else - throw bed_error("Invalid command."); - command.argument = cmd.substr(i); - return command; - case 'k': - command.type = Command::Type::Mark; - i++; - if (i >= cmd.size()) - throw bed_error("Invalid command."); - if ('a' <= cmd[i] && cmd[i] <= 'z') - command.argument = cmd[i]; - else - throw bed_error("Invalid mark used."); - break; - case '#': - command.type = Command::Type::Dump; - i++; - break; - } - if (i < cmd.size()) { - if (cmd[i] == 'l' || cmd[i] == 'n' || cmd[i] == 'p') - command.suffix = cmd[i++]; - skip_space(); - if (i != cmd.size()) - throw bed_error("Invalid command."); - } - return command; -} -} // namespace crib::commands::bed diff --git a/src/commands/bed/parsers/regex.cc b/src/commands/bed/parsers/regex.cc deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/bed/parsers/address.cc b/src/internal/address/address.cc similarity index 54% rename from src/commands/bed/parsers/address.cc rename to src/internal/address/address.cc index 105f77d..9813551 100644 --- a/src/commands/bed/parsers/address.cc +++ b/src/internal/address/address.cc @@ -1,36 +1,33 @@ -#include "commands/bed/bed.h" +#include "internal/address/address.h" -namespace crib::commands::bed { -void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr) { +namespace bed::internal::address { +Address::Address(std::string &cmd, uint64_t &i) { + base = None{}; auto skip_space = [&] { while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) ++i; }; skip_space(); - if (i >= cmd.size()) { - addr.type = Command::Address::Type::None; + if (i >= cmd.size()) return; - } switch (cmd[i]) { case '.': - addr.type = Command::Address::Type::Current; + base = Current(); i++; break; case '$': - addr.type = Command::Address::Type::Last; + base = Last(); i++; break; - case '\'': - addr.type = Command::Address::Type::Mark; + case '\'': { i++; if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z') i++; else - throw bed_error("Invalid mark."); - addr.mark = cmd[i]; - break; + throw address_error("Invalid mark."); + base = Mark(cmd[i]); + } break; case '/': { - addr.type = Command::Address::Type::SearchForward; i++; uint64_t start = i; while (true) { @@ -49,10 +46,9 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add else i++; } - addr.regex = cmd.substr(start, i - start); + base = RegexLine(Direction::Forward, cmd.substr(start, i - start)); } break; case '?': { - addr.type = Command::Address::Type::SearchBackward; i++; uint64_t start = i; while (true) { @@ -71,9 +67,10 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add else i++; } - addr.regex = cmd.substr(start, i - start); + base = RegexLine(Direction::Backward, cmd.substr(start, i - start)); } break; case '+': { + base = Current(); i++; skip_space(); uint64_t start = i; @@ -84,9 +81,10 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add } if (start == i) num = 1; - addr.offset += num; + offset += num; } break; case '-': { + base = Current(); i++; skip_space(); uint64_t start = i; @@ -97,19 +95,17 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add } if (start == i) num = 1; - addr.offset -= num; + offset -= num; } break; default: { if ('0' <= cmd[i] && cmd[i] <= '9') { - int64_t num = 0; - addr.type = Command::Address::Type::Number; + uint64_t num = 0; while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') { num = num * 10 + (cmd[i] - '0'); i++; } - addr.number = num; + base = Number(num); } else { - addr.type = Command::Address::Type::None; return; } break; @@ -130,49 +126,8 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add } if (start == i) num = 1; - addr.offset += positive ? num : -num; + offset += positive ? num : -num; skip_space(); } } - -void BEd::resolve_address(Command::Address addr, uint64_t *out_line) { - switch (addr.type) { - case Command::Address::Type::None: - break; - case Command::Address::Type::Current: - *out_line = line; - if (*out_line > vase.lines()) - *out_line = vase.lines(); - break; - case Command::Address::Type::Number: - *out_line = addr.number; - break; - case Command::Address::Type::Last: - *out_line = vase.lines(); - break; - case Command::Address::Type::Mark: - *out_line = marks[addr.mark - 'a']; - if (!*out_line) - throw bed_error("Unset mark used."); - break; - case Command::Address::Type::SearchForward: - if (addr.regex == "") - addr.regex = last_regex; - last_regex = addr.regex; - // TODO: use vase.regex_search(regex, range, options); - break; - case Command::Address::Type::SearchBackward: - if (addr.regex == "") - addr.regex = last_regex; - last_regex = addr.regex; - // TODO - break; - } - if ((int64_t)*out_line + addr.offset < 0) - throw bed_error("Line position can't be negative."); - else - *out_line += addr.offset; - if (*out_line > vase.lines()) - throw bed_error("Line position too high."); -} -} // namespace crib::commands::bed +} // namespace bed::internal::address diff --git a/src/internal/address/handle.cc b/src/internal/address/handle.cc new file mode 100644 index 0000000..8492e0d --- /dev/null +++ b/src/internal/address/handle.cc @@ -0,0 +1,50 @@ +#include "bed.h" +#include "internal/address/address.h" + +namespace bed::internal::address { +Address::Result Address::handle(BEd &ctx, std::string &cmd, uint64_t &i) { + bool prev_given = false; + Address prev; + Address curr; + while (i < cmd.size()) { + if (cmd[i] == '%') { + prev_given = true; + prev.base = Number(ctx.active->prev_range.start); + prev.offset = 0; + curr.base = Number(ctx.active->prev_range.end); + curr.offset = 0; + } else { + curr = Address(cmd, i); + } + if (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) { + if (std::holds_alternative(curr.base)) { + if (cmd[i] == ',') + curr.base = Number(1); + else + curr.base = Current(); + curr.offset = 0; + prev_given = false; + } else { + if (cmd[i] == ';') + ctx.active->jump(curr.resolve(ctx)); + prev_given = true; + } + prev = std::move(curr); + } else { + if (std::holds_alternative(curr.base)) { + if (std::holds_alternative(prev.base)) + return {}; + if (prev_given) { + curr = prev; + } else { + curr.base = Last(); + curr.offset = 0; + } + return {{prev.resolve(ctx), curr.resolve(ctx)}, 2}; + } + return {{curr.resolve(ctx)}, 1}; + } + } + return {}; +} +}; // namespace bed::internal::address diff --git a/src/internal/address/resolve.cc b/src/internal/address/resolve.cc new file mode 100644 index 0000000..28e14d7 --- /dev/null +++ b/src/internal/address/resolve.cc @@ -0,0 +1,40 @@ +#include "bed.h" +#include "internal/address/address.h" + +namespace bed::internal::address { +uint64_t Address::resolve(BEd &ctx) { + uint64_t result = std::visit( + [&](auto const &addr) -> uint64_t { + uint64_t line = 0; + + using T = std::decay_t; + + if constexpr (std::is_same_v) { + throw address_error("empty address"); + } else if constexpr (std::is_same_v) { + throw address_error("no address"); + } else if constexpr (std::is_same_v) { + line = ctx.active->line; + } else if constexpr (std::is_same_v) { + line = ctx.active->vase.lines(); + } else if constexpr (std::is_same_v) { + line = addr.i; + } else { + throw ed_error("Unhandled address given."); + } + + if (offset < 0 && line < (uint64_t)-offset) + throw address_error("Can't have negative addresses"); + line += offset; + + if (line > ctx.active->vase.lines()) + throw address_error("Line number too high."); + + return line; + }, + base + ); + + return result + offset; +} +}; // namespace bed::internal::address diff --git a/src/internal/buffer/buffer.cc b/src/internal/buffer/buffer.cc new file mode 100644 index 0000000..30e2ed7 --- /dev/null +++ b/src/internal/buffer/buffer.cc @@ -0,0 +1,161 @@ +#include "internal/buffer/buffer.h" + +namespace bed::internal::buffer { +Buffer::Buffer() : vase("/tmp") { + line = vase.lines(); + modified = false; +} + +Buffer::Buffer(std::string command) : vase(command, "/tmp") { + line = vase.lines(); + if (!line) + return; + prev_range.start = 1; + prev_range.end = line; + modified = false; +} + +Buffer::Buffer(std::filesystem::path path) : vase(path, "/tmp") { + line = vase.lines(); + if (!line) + return; + prev_range.start = 1; + prev_range.end = line; + save_path = path; + modified = false; +} + +void Buffer::load(std::string command) { + vase::Vase new_vase = vase::Vase(command, "/tmp"); + vase = std::move(new_vase); + line = vase.lines(); + if (!line) { + prev_range.start = 0; + prev_range.end = 0; + } else { + prev_range.start = 1; + prev_range.end = line; + } + modified = false; +} + +void Buffer::load(std::filesystem::path path) { + vase::Vase new_vase = vase::Vase(path, "/tmp"); + vase = std::move(new_vase); + line = vase.lines(); + if (!line) { + prev_range.start = 0; + prev_range.end = 0; + } else { + prev_range.start = 1; + prev_range.end = line; + } + save_path = path; + modified = false; +} + +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) { + using namespace bed::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(); + } + prev_range.start = p.row + 1; + vase.insert(&p, text); + prev_range.end = p.row + 1; + modified = true; +} + +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; + modified = true; +} + +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; + modified = true; +} + +void Buffer::print(uint64_t start_line, uint64_t end_line) { + vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward); + while (it.next() && start_line++ <= end_line) + std::cout << it.line << std::endl; + prev_range.start = start_line; + prev_range.end = end_line; +} + +std::string Buffer::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 bed::internal::buffer diff --git a/src/internal/commands/commands.cc b/src/internal/commands/commands.cc new file mode 100644 index 0000000..b929e85 --- /dev/null +++ b/src/internal/commands/commands.cc @@ -0,0 +1,104 @@ +#include "internal/commands/commands.h" +#include "bed.h" +#include "internal/commands/suffixes.h" + +namespace bed::internal::commands { +void Suffix::register_suffixes(BEd &ctx) { + ctx.suffixes['p' - 'a'] = Suffix{ + .desc = "Prints current line.", + .handle = [](BEd &ctx) { + auto line = ctx.active->line; + ctx.active->print(line, line); + } + }; +} + +void Command::register_posix(BEd &ctx) { + ctx.no_op = Command{ + .address_mode = Command::AddressMode::Single, + .suffix = Command::SuffixKind::None, + .desc = "Prints a line and jumps to it (default: .+1)", + .accept_zero = false, + .handle = [](BEd &ctx, std::span addresses, std::string_view) { + uint64_t line; + if (addresses.size()) + line = addresses[0]; + else + line = ctx.active->line + 1; + if (line == 0) + throw ed_error("Line 0 is invalid."); + ctx.active->jump(line); + ctx.active->print(line, line); + } + }; + ctx.eof_op = Command{ + .address_mode = Command::AddressMode::None, + .suffix = Command::SuffixKind::None, + .desc = "Try quitting.", + .accept_zero = false, + .handle = [](BEd &ctx, std::span, std::string_view) { + for (auto &[name, buffer] : ctx.buffers) + if (buffer->modified) + throw ed_error("Buffer " + name + " modified."); + throw fatal_error("", 0); + } + }; + ctx.commands.insert( + "a", + Command{ + .address_mode = Command::AddressMode::Single, + .suffix = Command::SuffixKind::Suffix, + .desc = "Append lines at address (default: .)", + .accept_zero = true, + .handle = [](BEd &, std::span, std::string_view) { + // TODO: start a text editing session, then append its stuff. + } + } + ); + ctx.commands.insert( + "q", + Command{ + .address_mode = Command::AddressMode::None, + .suffix = Command::SuffixKind::None, + .desc = "Try quitting.", + .accept_zero = false, + .handle = [](BEd &ctx, std::span, std::string_view) { + for (auto &[name, buffer] : ctx.buffers) + if (buffer->modified) + throw ed_error("Buffer " + name + " modified."); + throw fatal_error("", 0); + } + } + ); + ctx.commands.insert( + "E", + Command{ + .address_mode = Command::AddressMode::None, + .suffix = Command::SuffixKind::Argument, + .desc = "Load a file into the current buffer.", + .accept_zero = true, + .handle = [](BEd &ctx, std::span, std::string_view file) { + bool empty = false; + if (file.empty()) + empty = true; + uint64_t i = 0; + while (i < file.length() && (file[i] == ' ' || file[i] == '\t')) + i++; + if (i >= file.size()) + empty = true; + if (empty) { + if (ctx.active->save_path == "") + throw ed_error("Need filename!"); + ctx.active->load(ctx.active->save_path); + } else if (file[i] == '!') { + file = file.substr(1); + ctx.active->load(file); + } else { + ctx.active->load(std::filesystem::path(file)); + } + std::cout << ctx.active->vase.length() << std::endl; + } + } + ); +} +} // namespace bed::internal::commands diff --git a/src/internal/vase/buffer/append.cc b/src/internal/vase/buffer/append.cc index 6691aac..f0000cd 100644 --- a/src/internal/vase/buffer/append.cc +++ b/src/internal/vase/buffer/append.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { AppendBuffer::AppendBuffer(std::filesystem::path base_dir) { base_dir /= "tapp.XXXXXX"; char *s = strdup(base_dir.c_str()); @@ -69,4 +69,4 @@ const char *AppendBuffer::read(uint64_t pos) { uint64_t AppendBuffer::length() { return current_size; } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/buffer/original.cc b/src/internal/vase/buffer/original.cc index 6ab5df0..9935b08 100644 --- a/src/internal/vase/buffer/original.cc +++ b/src/internal/vase/buffer/original.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) { if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir)) throw std::runtime_error("Swap directory does not exist or is not a directory."); @@ -44,4 +44,4 @@ const char *OriginalBuffer::read(uint64_t pos) { uint64_t OriginalBuffer::length() { return len; } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/iterators/line.cc b/src/internal/vase/iterators/line.cc index 81186b5..e878fad 100644 --- a/src/internal/vase/iterators/line.cc +++ b/src/internal/vase/iterators/line.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { LineIterator::LineIterator(Shard *root, uint64_t line_num, Direction dir) : it(root, dir), dir(dir) { it.seek_line(line_num); @@ -72,4 +72,4 @@ bool LineIterator::next(std::string *line) { uint64_t LineIterator::byte_offset() { return last_line_offset; } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/iterators/petal.cc b/src/internal/vase/iterators/petal.cc index 174166a..8d8756e 100644 --- a/src/internal/vase/iterators/petal.cc +++ b/src/internal/vase/iterators/petal.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { PetalIterator::PetalIterator(Shard *r, Direction dir) : dir(dir), root(r) { if (!root) @@ -168,4 +168,4 @@ bool PetalIterator::next(const char **data, uint64_t *out_len) { uint64_t PetalIterator::byte_offset() { return last_offset; } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/regex/api.cc b/src/internal/vase/regex/api.cc index d564601..2cdbcf2 100644 --- a/src/internal/vase/regex/api.cc +++ b/src/internal/vase/regex/api.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { std::vector Vase::parse_replace(std::string_view s) { std::vector parts; std::string constant; @@ -156,4 +156,4 @@ std::vector Vase::regex_search( result.push_back({point_of(match.start), point_of(match.end)}); return result; } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/regex/core.cc b/src/internal/vase/regex/core.cc index f4cdde8..d0a2fd8 100644 --- a/src/internal/vase/regex/core.cc +++ b/src/internal/vase/regex/core.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { std::vector Vase::_regex_search( std::string_view pattern, Range range, std::string_view options ) { @@ -244,4 +244,4 @@ std::vector Vase::_regex_search( return results; } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/shard.cc b/src/internal/vase/shard.cc index ed87fb2..66045b2 100644 --- a/src/internal/vase/shard.cc +++ b/src/internal/vase/shard.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { void Shard::retain(Shard *n) { if (n) n->refs++; @@ -424,4 +424,4 @@ void Shard::dump(Shard *node, int depth) { << "\"\n"; } } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/internal/vase/vase.cc b/src/internal/vase/vase.cc index c41985e..be4ca58 100644 --- a/src/internal/vase/vase.cc +++ b/src/internal/vase/vase.cc @@ -1,6 +1,6 @@ #include "internal/vase/vase.h" -namespace crib::internal::vase { +namespace bed::internal::vase { Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir) : path(path), swapdir(swapdir) { if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir)) @@ -507,4 +507,4 @@ void Vase::move_lines(Point *point, uint64_t amount, Direction dir) { } clamp(point); } -} // namespace crib::internal::vase +} // namespace bed::internal::vase diff --git a/src/main.cc b/src/main.cc index 734069c..0bd5f68 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,6 +1,18 @@ -#include "cli.h" +#include "bed.h" #include "pch.h" int main(int argc, char *argv[]) { - return crib::cli::run(argc, argv); + std::vector args(argv, argv + argc); + try { + bed::BEd ed(args); + ed.run(); + } catch (bed::fatal_error &e) { + if (std::string_view(e.what()) != "") + std::cout << "Fatal error: " << e.what() << std::endl; + return e.code; + } catch (...) { + std::cout << "Unexpected error." << std::endl; + return 1; + } + return 0; }