Compare commits

..
Author SHA1 Message Date
syedm 8f67be87ee Add escape in strings control to ruby parser. 2026-09-13 14:07:19 +01:00
syedm 88785436cf Add ARCHITECTURE.md. 2026-09-13 13:15:57 +01:00
syedm d246559292 Make posix line mode the default always. 2026-09-13 13:15:07 +01:00
syedm 3346bc9f83 Fix a few bugs.
- Moving lines after themselves didnt work.
- moving empty lines deleted them.
- "w" didnt set state to unmodified in generic buffers.
- in "hl" aligned the numbers (by digits)
2026-09-11 22:35:06 +01:00
syedm a8d51dc0b6 Make ` command behave like irb with coloring
- And `` command to run selected lines, but removing ``` as
  that can be done by making a new buffer and writing in that.
2026-09-10 07:21:22 +01:00
syedm 118934a393 Make mruby raii cleanup, and fix cleanup bug.
- Functions were cleaned up after mruby, which was wrong.
- Becuase functions could have stored a reference to a Block which refers to mruby.
- And so it would crash on release builds at exit if custom functions were used.
2026-09-08 23:12:52 +01:00
syedm f5ec5516fa Cleanup flake.nix 2026-09-08 22:44:58 +01:00
syedm c325409dd0 Add basic runtime function operations. 2026-09-08 22:34:45 +01:00
syedm 0e4422b0c8 Fix issue with mruby linking. 2026-09-08 19:10:52 +01:00
23 changed files with 534 additions and 213 deletions
+149
View File
@@ -0,0 +1,149 @@
# Base.
### `PCH`
Brings in all the external and standard headers. <br/>
Apart from c++std stuff we have:
- `mruby` and almost all it's subheaders.
- `pcre2` (For regex.)
- `grapheme.h` - The suckless libgrapheme (to find utf8 boundaries)
- `unicode_width.h` - To find terminal width of unicode char's.
*Maybe try removing some of them ive probably stopped using.*
### `definitions.h`
- Defines the `bed` namespace
- Forward declaration of `BEd` type needed by all.
- `fatal_error` definition, a msg, code (quits application on being thrown)
- `ed_error`. just a message (stops the current cycle of the ed repl.)
### `bed.h`
**As this is based heavily on the internals, and so ill look into it at the end.**
# `Internal`
## `Vase`
- This module doesn't depend on any other.
- It's namespace is `bed::internal::vase`
- Vase is the module to do with the actual storage and handling of a disk backed immutable and reference counted avl piece tree.
- It is a O(log n) per edit text datatype.
### `constants.h`
Defines `PETAL_SIZE_MAX` as `32 * 1024`.
- `PETAL_SIZE_MAX` limits teh size of a petal which makes multiple O(n) in petal searches O(1) as it is now bounded.
### `storage/`
A storage is something that stores the actual text, the text is actually stored in storages which are referenced by the trees.
#### `storage.h`
Defines the virtual `Storage` class.
It has the methods:
- `const char *read(uint64_t pos)`: reads text from position `pos` and returns a pointer to it.
- `uint64_t length()`: returns the length of the storage.
- `void retain() / release()`: Methods to help with refcounted storages.
#### `original.h`
Defines the `Original` type of storage.
This is a refcounted storage.
It has the fields:
- `atomic_uint64_t refs{0}`: refcount
- `const char *buf`: pointer to the actual text.
- `uint64_t len`
- `int fd = -1`: the file descriptor of teh undelying disk storage.
It adds the method `initialize` over the base class.
`src/vase/storage/original.cc`.
The constructor uses `mkstemp` to make a temp file, then unlinks it to hide it from the fs but keeps the fd alive.
`initialize` then `mmap`s the fd into a non modifyable pointer managed by the kernel then closes the fd itself.
- This is what gives us a pointer to the file contents that the kernel can load and evict from memory as needed, and therefore if we have say a 500MB file loaded, but the cursor is arount 200MB and the user is only editing a couple lines around that, the rest of the file will not be using your memory, it might be loaded but if needed the kernel can evict it.
The call site fills the fd before calling initialze, it cannot be modified after that.
#### `append.h`
Defines the `Append` type of storage.
This is not actually refcounted, it is to be owned by the application just once and reused all throughout.
It contains the actively typed stuff.
It has the fields:
- `const char *buf`: pointer to the actual text.
- `uint64_t allocated_capacity`: the amount of bytees that can be written to it without needing to increase the size.
- `uint64_t current_size`: Stores the size of actual text stored in it. (the cursor)
- `int fd = -1`: the file descriptor of the underlying disk storage.
It has the methods `append` (with 2 overloads) and private `grow` over the base class.
`retain` and `release` do nothing.
`src/vase/storage/append.cc`.
The constructor uses `mkstemp` an `unlink` similar to the original storage.
It then `ftruncate`s the file to `2^30` bytes or 1GiB this makes writing 1gb to the file possible but does'nt neccasarily use up 1gb on the disk.
But when `mmap`ing we set `PROT_READ | PROT_WRITE` and `MAP_SHARED` to be able to write edits to disk.
`grow` works by doubling the capacity `ftruncat`ing the fd to the new capacity and then on `linux` it uses `mremap` to remap to the new file size, but otherwise we `munmap` and `mmap` into the new size.
The append methods append text into the buffer (either a single char or char * + len), they may grow the storage size. (the char*+len version uses memcpy).
### `shard.h`
Defines the actual peice tree.
The tree is a polymorhic set of classes.
The base class `Shard` has:
- `enum Kind : uint8_t kind`: Branch or Petal.
- `uint16_t height`: the tree height (for avl balancing.)
- `atomic_uint32_t refs`: the refcount.
- `uint64_t length`: the length in bytes of this (sub)tree.
- `uint64_t lines`: the number of `\n` in this subtree.
- It starts refcount at 1.
The `Branch` class adds:
- `Shard *left/right` the subtrees.
- its constructor takes l, r and sets length/lines `(l->length + r->length)`
- and height `1 + std::max(l->height, r->height)`
The `Petal` (leaf) class adds:
- `Storage *` a pointer to the storage used.
- `uint64_t pos` the position in the storage its text starts at.
- it takes `(uint64_t length, uint64_t lines, Storage *source, uint64_t pos)`
- it retains `source`. (usefull for refcounted sotrages.)
A `nullptr` `Shard*` is an empty peice of text (i.e. valid).
`src/internal/vase/shard.cc`: defines a set of static function on the Shard class. (All the applications of it.)
the operations methods are:
- `retain/release` retain/releases them, if freeing a leaf it also releases the storage and for branches it releases both subtrees. nullptr's are ignored.
- `balance/rotate_(right/left) and height/balance factor` are for doing simple avl balancing operations.
- `concat` borrows 2 pointers to `Shard` and returns an owned Shard*.
- `split` borrows a `Shard` and returns a `pair<Shard*, Shard*>` of 2 owned shards.
- `merge_leaves` works similar to concat but it tries to merge the middle bit if they have their physical representation in order. for example if i have a shard `hell` and i type the letter `o` and because i'd been typing in order the Append storage will have text `hello` we can merge them into a single petal.
- `append` similar to concat, but uses merge_leaves, useful for small appends like when typing.
- `build` takes a pointer to an array of `Shard*` and start and end then makes a single avl balanced `Shard*` out of it it is generally faster than calling concat on each peice when we have many.
Then for building a Shard* in the first place we have:
- `from_command(const char *cmd)` runs the \0 terminated command string and reads its STDOUT and returns an owned Shard*.
- `from_file(const std::filesystem::path &path)` reads the file at path.
- `from_string(const char *data, uint64_t len)` loads the string given into a new storage, (normally inserting text can work by inserting into the append storage but this function loads it into a `OriginalStorage` object.).
These methods all clip the final newline if it exists.
And they also create a new `OriginalStorage` object.
### `vase.h`
### `iterators/`
#### `line.h`
#### `petal.h`
+3 -1
View File
@@ -56,5 +56,7 @@ It should support:
- Error handling. - Error handling.
- And more. - And more.
Not done yet. ### TODO:
- Make "g" command work.
- properly handle escapes for %q ' etc in ruby parser (rn everything is escapable.)
+2 -5
View File
@@ -70,9 +70,8 @@
buildPhase = '' buildPhase = ''
make \ make \
MRBC=${mruby}/bin/mrbc \ MRUBY_CFLAGS="-I${mruby}/include" \
MRUBY_CFLAGS=-I${mruby}/include \ MRUBY_LIBS="-L${mruby}/lib -lmruby"
MRUBY_LIBS=-L${mruby}/lib -lmruby
''; '';
installPhase = '' installPhase = ''
@@ -110,10 +109,8 @@
export CC="ccache $CC" export CC="ccache $CC"
export CXX="ccache $CXX" export CXX="ccache $CXX"
export MRBC=${mruby}/bin/mrbc
export MRUBY_CFLAGS=-I${mruby}/include export MRUBY_CFLAGS=-I${mruby}/include
export MRUBY_LIBS="-L${mruby}/lib -lmruby" export MRUBY_LIBS="-L${mruby}/lib -lmruby"
export PATH="$PWD/result/bin:$PATH"
''; '';
}; };
}; };
+2 -4
View File
@@ -14,6 +14,7 @@
namespace bed { namespace bed {
struct BEd { struct BEd {
internal::scripting::RubyState mrb;
internal::trie::Trie<internal::functions::Function> functions; internal::trie::Trie<internal::functions::Function> functions;
internal::functions::Function no_op; internal::functions::Function no_op;
internal::functions::Function eof_op; internal::functions::Function eof_op;
@@ -22,8 +23,7 @@ struct BEd {
std::unordered_map<std::string, internal::syntax::Language *> languages; std::unordered_map<std::string, internal::syntax::Language *> languages;
internal::io::IO io; internal::io::IO io;
internal::vase::AppendStorage append{"/tmp"}; internal::vase::AppendStorage append{"/tmp"};
mrb_state *mrb = nullptr; std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
int arena;
bool help_mode = false; bool help_mode = false;
bool prompt_mode = true; bool prompt_mode = true;
@@ -37,8 +37,6 @@ struct BEd {
std::string last_replacement = ""; std::string last_replacement = "";
std::string last_shell = ""; std::string last_shell = "";
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
internal::buffer::Range prev_1; internal::buffer::Range prev_1;
internal::buffer::Range prev_2; internal::buffer::Range prev_2;
internal::marks::MarksEngine marks; internal::marks::MarksEngine marks;
+1
View File
@@ -34,6 +34,7 @@ struct Buffer {
virtual uint64_t bytes() = 0; virtual uint64_t bytes() = 0;
virtual void set_filename(std::filesystem::path path) = 0; virtual void set_filename(std::filesystem::path path) = 0;
virtual std::filesystem::path filename() = 0; virtual std::filesystem::path filename() = 0;
virtual void saved_hook() = 0;
virtual void load(BEd &ctx, vase::Shard *text) = 0; virtual void load(BEd &ctx, vase::Shard *text) = 0;
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0; virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
virtual void substitute( virtual void substitute(
+1
View File
@@ -10,6 +10,7 @@ struct ClipBuffer : Buffer {
void clip_write(vase::Shard *text); void clip_write(vase::Shard *text);
bool waste() override; bool waste() override;
void saved_hook() override;
uint64_t lines() override; uint64_t lines() override;
uint64_t bytes() override; uint64_t bytes() override;
void load(BEd &ctx, vase::Shard *text) override; void load(BEd &ctx, vase::Shard *text) override;
+1
View File
@@ -31,6 +31,7 @@ struct GenericBuffer : ShardBuffer {
bool undo(BEd &ctx); bool undo(BEd &ctx);
bool redo(BEd &ctx); bool redo(BEd &ctx);
uint64_t prune(int); uint64_t prune(int);
void saved_hook() override;
bool waste() override; bool waste() override;
void load(BEd &ctx, vase::Shard *text) override; void load(BEd &ctx, vase::Shard *text) override;
void set_filename(std::filesystem::path path) override; void set_filename(std::filesystem::path path) override;
+1
View File
@@ -12,6 +12,7 @@ struct ReadonlyBuffer : ShardBuffer {
const syntax::ParserSnapshot &snapshot const syntax::ParserSnapshot &snapshot
) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {} ) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {}
void saved_hook() override {}
bool waste() override { bool waste() override {
return useless; return useless;
} }
+96 -15
View File
@@ -4,40 +4,121 @@
#include "pch.h" #include "pch.h"
namespace bed::internal::scripting { namespace bed::internal::scripting {
struct Block { struct RubyState {
mrb_value proc; mrb_state *state = nullptr;
mrb_state *mrb; int arena = 0;
Block(mrb_state *mrb, mrb_value proc) explicit RubyState(BEd *ctx) : state(mrb_open()) {
: proc(proc), mrb(mrb) { if (!state)
throw fatal_error("Failed to initialize mruby.", 1);
state->ud = ctx;
arena = mrb_gc_arena_save(state);
}
~RubyState() {
state->ud = nullptr;
if (state) {
mrb_gc_arena_restore(state, arena);
mrb_full_gc(state);
mrb_close(state);
}
}
RubyState(const RubyState &) = delete;
RubyState &operator=(const RubyState &) = delete;
};
struct Block {
mrb_state *mrb = nullptr;
mrb_value proc = mrb_nil_value();
Block(mrb_state *mrb, mrb_value proc) noexcept
: mrb(mrb), proc(proc) {
mrb_gc_register(mrb, proc); mrb_gc_register(mrb, proc);
} }
Block() : proc(mrb_nil_value()) {} Block() noexcept = default;
~Block() { ~Block() noexcept {
if (!mrb_nil_p(proc)) if (!mrb_nil_p(proc) && mrb)
mrb_gc_unregister(mrb, proc); mrb_gc_unregister(mrb, proc);
} }
void set_proc(mrb_value new_proc) { Block(const Block &other) noexcept
if (!mrb_nil_p(proc)) : mrb(other.mrb), proc(other.proc) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_register(mrb, proc);
}
Block &operator=(const Block &other) noexcept {
if (this != &other) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
mrb = other.mrb;
proc = other.proc;
if (mrb && !mrb_nil_p(proc))
mrb_gc_register(mrb, proc);
}
return *this;
}
Block(Block &&other) noexcept
: mrb(other.mrb), proc(other.proc) {
other.mrb = nullptr;
other.proc = mrb_nil_value();
}
Block &operator=(Block &&other) noexcept {
if (this != &other) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
mrb = other.mrb;
proc = other.proc;
other.mrb = nullptr;
other.proc = mrb_nil_value();
}
return *this;
}
void set_proc(mrb_state *mrb_, mrb_value new_proc) {
if (!mrb_nil_p(proc) && mrb)
mrb_gc_unregister(mrb, proc); mrb_gc_unregister(mrb, proc);
mrb = mrb_;
proc = new_proc; proc = new_proc;
mrb_gc_register(mrb, proc); mrb_gc_register(mrb, proc);
} }
Block(const Block &) = delete;
Block &operator=(const Block &) = delete;
mrb_value call(int argc = 0, mrb_value *argv = nullptr) const { mrb_value call(int argc = 0, mrb_value *argv = nullptr) const {
if (mrb_nil_p(proc)) if (mrb_nil_p(proc))
return mrb_nil_value(); return mrb_nil_value();
mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv); mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv);
return result; if (!mrb->exc)
return result;
mrb_value exc = mrb_obj_value(mrb->exc);
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
std::string error;
if (mrb_string_p(msg))
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
auto *fatal_class = mrb_class_get(mrb, "FatalError");
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
mrb_value code =
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
mrb->exc = nullptr;
int c = 1;
if (mrb_fixnum_p(code))
c = mrb_fixnum(code);
throw fatal_error(error, c);
}
auto *ed_class = mrb_class_get(mrb, "EdError");
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
mrb->exc = nullptr;
throw ed_error(error);
}
mrb->exc = nullptr;
throw ed_error("Ruby Exception: " + error);
} }
}; };
void register_basic(BEd &ctx); void register_basic(BEd &ctx);
void run(BEd &ctx, const std::string &str); std::string run(BEd &ctx, const std::string &str);
}; // namespace bed::internal::scripting }; // namespace bed::internal::scripting
+2 -1
View File
@@ -24,7 +24,8 @@ struct alignas(2) RubyState {
DEF_NAME = 0b10, DEF_NAME = 0b10,
MODULE_NAME = 0b11 MODULE_NAME = 0b11
}; };
static constexpr uint8_t NEWLINE = 1 << 5; static constexpr uint8_t NEWLINE = 1 << 4;
static constexpr uint8_t ALLOW_ESCAPE = 1 << 5;
static constexpr uint8_t ALLOW_INTERPOLATION = 1 << 6; static constexpr uint8_t ALLOW_INTERPOLATION = 1 << 6;
static constexpr uint8_t EXPECTING_EXPRESSION = 1 << 7; static constexpr uint8_t EXPECTING_EXPRESSION = 1 << 7;
uint8_t flags = 0; uint8_t flags = 0;
+5
View File
@@ -68,6 +68,11 @@ const static std::vector<std::string> builtins = {
}; };
const static std::vector<std::string> methods = { const static std::vector<std::string> methods = {
// BEd methods.
"handle",
"register",
"unregister",
// Normal:
"abort", "abort",
"at_exit", "at_exit",
"binding", "binding",
+3 -3
View File
@@ -24,9 +24,9 @@ struct Shard {
static void retain(Shard *n); static void retain(Shard *n);
static void release(Shard *n); static void release(Shard *n);
static Shard *from_file(const std::filesystem::path &path, bool posix_ending); static Shard *from_file(const std::filesystem::path &path);
static Shard *from_string(const char *data, uint64_t len, bool posix_ending); static Shard *from_string(const char *data, uint64_t len);
static Shard *from_command(const char *cmd, bool posix_ending); static Shard *from_command(const char *cmd);
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset); static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
static Shard *concat(Shard *a, Shard *b); static Shard *concat(Shard *a, Shard *b);
+3
View File
@@ -0,0 +1,3 @@
register :happy, address: :none do
puts "be nice"
end
+1 -12
View File
@@ -2,7 +2,7 @@
namespace bed { namespace bed {
BEd::BEd(std::vector<std::string> args) BEd::BEd(std::vector<std::string> args)
: theme(internal::theme::Theme::default_theme()), io(*this) { : mrb(this), theme(internal::theme::Theme::default_theme()), io(*this) {
std::string prompt_ = ""; std::string prompt_ = "";
std::string file = ""; std::string file = "";
bool suppress = false; bool suppress = false;
@@ -42,11 +42,6 @@ BEd::BEd(std::vector<std::string> args)
&& strcmp(colorterm, "24bit") != 0) && strcmp(colorterm, "24bit") != 0)
color = false; color = false;
color_mode = color; color_mode = color;
mrb = mrb_open();
if (!mrb)
throw fatal_error("Failed to initialize mruby.", 1);
mrb->ud = this;
arena = mrb_gc_arena_save(mrb);
internal::functions::Function::register_posix(*this); internal::functions::Function::register_posix(*this);
internal::functions::Function::register_extented(*this); internal::functions::Function::register_extented(*this);
internal::functions::Suffix::register_suffixes(*this); internal::functions::Suffix::register_suffixes(*this);
@@ -70,12 +65,6 @@ BEd::~BEd() {
delete buffer; delete buffer;
for (auto &[_, lang] : languages) for (auto &[_, lang] : languages)
delete lang; delete lang;
mrb->ud = nullptr;
if (mrb) {
mrb_gc_arena_restore(mrb, arena);
mrb_full_gc(mrb);
mrb_close(mrb);
}
} }
void BEd::print_help() { void BEd::print_help() {
+17 -10
View File
@@ -15,6 +15,8 @@ GenericBuffer::~GenericBuffer() {
void GenericBuffer::list_history(BEd &ctx) { void GenericBuffer::list_history(BEd &ctx) {
uint64_t current = base_version + undo_stack.size(); uint64_t current = base_version + undo_stack.size();
uint64_t max_version = current + redo_stack.size();
size_t width = std::to_string(max_version).size();
for (size_t i = 0; i < undo_stack.size(); ++i) { for (size_t i = 0; i < undo_stack.size(); ++i) {
auto &item = undo_stack[i]; auto &item = undo_stack[i];
uint64_t version = base_version + i; uint64_t version = base_version + i;
@@ -22,8 +24,9 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time); std::tm tm = *std::localtime(&time);
ctx.io.write_line( ctx.io.write_line(
std::format( std::format(
" {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", " {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version, version,
width,
tm.tm_year + 1900, tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mon + 1,
tm.tm_mday, tm.tm_mday,
@@ -39,8 +42,9 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time); std::tm tm = *std::localtime(&time);
ctx.io.write_line( ctx.io.write_line(
std::format( std::format(
"* {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", "* {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
current, current,
width,
tm.tm_year + 1900, tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mon + 1,
tm.tm_mday, tm.tm_mday,
@@ -58,8 +62,9 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time); std::tm tm = *std::localtime(&time);
ctx.io.write_line( ctx.io.write_line(
std::format( std::format(
" {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", " {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version, version,
width,
tm.tm_year + 1900, tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mon + 1,
tm.tm_mday, tm.tm_mday,
@@ -207,6 +212,10 @@ bool GenericBuffer::waste() {
&& parse.lang == nullptr; && parse.lang == nullptr;
} }
void GenericBuffer::saved_hook() {
state = buffer::GenericBuffer::Unmodified;
}
void GenericBuffer::language(BEd &ctx, std::string name) { void GenericBuffer::language(BEd &ctx, std::string name) {
syntax::Language *lang = nullptr; syntax::Language *lang = nullptr;
if (name.size()) { if (name.size()) {
@@ -253,17 +262,15 @@ std::filesystem::path GenericBuffer::filename() {
}; };
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) { void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
if (!text) snapshot(std::format("Insert {} lines after line {}", (text ? text->lines + 1 : 1), line));
return;
snapshot(std::format("Insert {} lines after line {}", text->lines + 1, line));
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = line + 1; ctx.prev().start = line + 1;
ctx.prev().end = line + text->lines + 1; ctx.prev().end = line + (text ? text->lines + 1 : 1);
ctx.current() = {name, line + text->lines + 1}; ctx.current() = {name, line + (text ? text->lines + 1 : 1)};
root = vase::insert(&ctx.append, root, text, line); root = vase::insert(&ctx.append, root, text, line);
ctx.marks.insert(name, line, text->lines + 1); ctx.marks.insert(name, line, (text ? text->lines + 1 : 1));
if (parse.lang) if (parse.lang)
syntax::insert(parse, root, line, text->lines + 1); syntax::insert(parse, root, line, (text ? text->lines + 1 : 1));
state = Modified; state = Modified;
} }
+15 -13
View File
@@ -8,15 +8,17 @@ bool ClipBuffer::waste() {
return false; return false;
} }
void ClipBuffer::saved_hook() {}
uint64_t ClipBuffer::lines() { uint64_t ClipBuffer::lines() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t lines = s ? s->lines + 1 : 0; uint64_t lines = s ? s->lines + 1 : 0;
vase::Shard::release(s); vase::Shard::release(s);
return lines; return lines;
} }
uint64_t ClipBuffer::bytes() { uint64_t ClipBuffer::bytes() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t length = s ? s->length + 1 : 0; uint64_t length = s ? s->length + 1 : 0;
vase::Shard::release(s); vase::Shard::release(s);
return length; return length;
@@ -55,7 +57,7 @@ void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = line + 1; ctx.prev().start = line + 1;
ctx.prev().end = line + (text ? text->lines + 1 : 0); ctx.prev().end = line + (text ? text->lines + 1 : 0);
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::insert(&ctx.append, s, text, line); s = vase::insert(&ctx.append, s, text, line);
clip_write(s); clip_write(s);
vase::Shard::release(s); vase::Shard::release(s);
@@ -63,7 +65,7 @@ void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
} }
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) { void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::erase(s, start_line, end_line); s = vase::erase(s, start_line, end_line);
clip_write(s); clip_write(s);
ctx.prev().buffername = name; ctx.prev().buffername = name;
@@ -83,7 +85,7 @@ void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint6
ctx.prev().end = start_line + text->lines; ctx.prev().end = start_line + text->lines;
uint64_t new_count = text->lines + 1; uint64_t new_count = text->lines + 1;
uint64_t old_count = end_line - start_line + 1; uint64_t old_count = end_line - start_line + 1;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::replace(s, text, start_line, end_line); s = vase::replace(s, text, start_line, end_line);
clip_write(s); clip_write(s);
vase::Shard::release(s); vase::Shard::release(s);
@@ -97,7 +99,7 @@ void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint6
} }
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) { void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::join(s, start_line, end_line); s = vase::join(s, start_line, end_line);
clip_write(s); clip_write(s);
vase::Shard::release(s); vase::Shard::release(s);
@@ -114,7 +116,7 @@ void ClipBuffer::substitute(
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = start_line; ctx.prev().start = start_line;
ctx.prev().end = end_line; ctx.prev().end = end_line;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::substitute( s = vase::substitute(
&ctx.append, &ctx.append,
s, s,
@@ -136,21 +138,21 @@ void ClipBuffer::substitute(
} }
vase::Shard *ClipBuffer::copy(uint64_t start_line, uint64_t end_line) { vase::Shard *ClipBuffer::copy(uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Shard *o = vase::copy(s, start_line, end_line); vase::Shard *o = vase::copy(s, start_line, end_line);
vase::Shard::release(s); vase::Shard::release(s);
return o; return o;
} }
uint64_t ClipBuffer::find_next(std::string_view pattern, uint64_t start) { uint64_t ClipBuffer::find_next(std::string_view pattern, uint64_t start) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t line = vase::find_next(s, pattern, start); uint64_t line = vase::find_next(s, pattern, start);
vase::Shard::release(s); vase::Shard::release(s);
return line; return line;
} }
uint64_t ClipBuffer::find_prev(std::string_view pattern, uint64_t start) { uint64_t ClipBuffer::find_prev(std::string_view pattern, uint64_t start) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t line = vase::find_prev(s, pattern, start); uint64_t line = vase::find_prev(s, pattern, start);
vase::Shard::release(s); vase::Shard::release(s);
return line; return line;
@@ -174,7 +176,7 @@ void ClipBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = start_line; ctx.prev().start = start_line;
ctx.prev().end = end_line; ctx.prev().end = end_line;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Iterator it(s, start_line - 1, Direction::Forward); vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line) while (it.next() && start_line++ <= end_line)
ctx.io.write_line(it.line); ctx.io.write_line(it.line);
@@ -188,7 +190,7 @@ void ClipBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line)
uint8_t width = 1; uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10) for (uint64_t n = end_line; n >= 10; n /= 10)
++width; ++width;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Iterator it(s, start_line - 1, Direction::Forward); vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line) while (it.next() && start_line <= end_line)
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line)); ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
@@ -199,7 +201,7 @@ void ClipBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = start_line; ctx.prev().start = start_line;
ctx.prev().end = end_line; ctx.prev().end = end_line;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true); auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Iterator it(s, start_line - 1, Direction::Forward); vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line) while (it.next() && start_line++ <= end_line)
ctx.io.write_line(list_string(it.line)); ctx.io.write_line(list_string(it.line));
+28 -37
View File
@@ -176,8 +176,8 @@ void Function::register_extented(BEd &ctx) {
.address_kind = Function::AddressKind::Range, .address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Ruby, .argument_kind = Function::ArgumentKind::Ruby,
.input_mode = Function::InputMode::None, .input_mode = Function::InputMode::None,
.desc = "Execute some ruby code", .desc = "Execute given ruby code.",
.default_address = "", .default_address = "0,0",
.accept_zero = true, .accept_zero = true,
.pre_text_mode = nullptr, .pre_text_mode = nullptr,
.handle = []( .handle = [](
@@ -193,7 +193,30 @@ void Function::register_extented(BEd &ctx) {
+ ";$END=" + std::to_string(addr.end) + ";$END=" + std::to_string(addr.end)
+ ";$BUFNAME=\"" + addr.buffername + "\""; + ";$BUFNAME=\"" + addr.buffername + "\"";
scripting::run(ctx, pre_code); scripting::run(ctx, pre_code);
scripting::run(ctx, arg.cmd); auto line = scripting::run(ctx, arg.cmd);
ctx.io.write("=> ", 3);
auto parser = syntax::MiniParser(
*ctx.languages["ruby"],
vase::Shard::from_string(line.data(), line.size()),
nullptr
);
const auto &tokens = parser.lines[0].second;
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() || end > line.size())
break;
if (cursor < start)
ctx.io.write(line.data() + cursor, start - cursor);
ctx.io.apply(token.type);
ctx.io.write(line.data() + start, end - start);
ctx.io.reset();
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
}, },
} }
); );
@@ -204,7 +227,7 @@ void Function::register_extented(BEd &ctx) {
.argument_kind = Function::ArgumentKind::None, .argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None, .input_mode = Function::InputMode::None,
.desc = "Execute addressed lines as ruby code.", .desc = "Execute addressed lines as ruby code.",
.default_address = ".,.", .default_address = "1,$",
.accept_zero = false, .accept_zero = false,
.pre_text_mode = nullptr, .pre_text_mode = nullptr,
.handle = []( .handle = [](
@@ -230,38 +253,6 @@ void Function::register_extented(BEd &ctx) {
}, },
} }
); );
ctx.functions.insert(
"```",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Execute some ruby code (taken from text mode)",
.default_address = "",
.accept_zero = true,
.pre_text_mode = [](
BEd &ctx,
const buffer::Address &,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
return {nullptr, ctx.languages["ruby"], nullptr};
},
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *code,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
auto pre_code = "$START=" + std::to_string(addr.start)
+ ";$END=" + std::to_string(addr.end)
+ ";$BUFNAME=\"" + addr.buffername + "\"";
scripting::run(ctx, pre_code);
scripting::run(ctx, vase::to_string(code));
vase::Shard::release(code); },
}
);
ctx.functions.insert( ctx.functions.insert(
"echo", "echo",
Function{ Function{
@@ -296,7 +287,7 @@ void Function::register_extented(BEd &ctx) {
str[i - 1] = '\n'; str[i - 1] = '\n';
continue; continue;
} }
if (str[i] == '$' && i < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') { if (str[i] == '$' && i + 1 < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') {
char c = str[i + 1]; char c = str[i + 1];
str.erase(i, 2); str.erase(i, 2);
switch (c) { switch (c) {
+11 -11
View File
@@ -144,17 +144,17 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr; vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) { if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg); auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
buf.set_filename(path); buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) { } else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd; auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string()); ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true); s = vase::Shard::from_command(cmd.c_str());
} else { } else {
auto path = buf.filename(); auto path = buf.filename();
if (path.empty()) if (path.empty())
throw ed_error("Need filename."); throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
}; };
try { try {
buf.load(ctx, s); buf.load(ctx, s);
@@ -190,17 +190,17 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr; vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) { if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg); auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
buf.set_filename(path); buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) { } else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd; auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string()); ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true); s = vase::Shard::from_command(cmd.c_str());
} else { } else {
auto path = buf.filename(); auto path = buf.filename();
if (path.empty()) if (path.empty())
throw ed_error("Need filename."); throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
}; };
try { try {
buf.load(ctx, s); buf.load(ctx, s);
@@ -416,8 +416,7 @@ void Function::register_posix(BEd &ctx) {
auto addr = std::get<buffer::Range>(addr_); auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_); auto arg = std::get<buffer::Line>(arg_);
if (arg.buffername == addr.buffername if (arg.buffername == addr.buffername
&& addr.start <= arg.number && arg.number >= addr.start && arg.number < addr.end)
&& addr.end < arg.number)
throw ed_error("Can't move lines within themselves."); throw ed_error("Can't move lines within themselves.");
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end); auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
ctx.mark(252, arg); ctx.mark(252, arg);
@@ -589,18 +588,18 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr; vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) { if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg); auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
if (buf.filename().empty()) if (buf.filename().empty())
buf.set_filename(path); buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) { } else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd; auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string()); ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true); s = vase::Shard::from_command(cmd.c_str());
} else { } else {
auto path = buf.filename(); auto path = buf.filename();
if (path.empty()) if (path.empty())
throw ed_error("Need filename."); throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
}; };
try { try {
buf.append(ctx, s, addr.number); buf.append(ctx, s, addr.number);
@@ -757,6 +756,7 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(text); vase::Shard::release(text);
throw; throw;
} }
buf.saved_hook();
if (!ctx.suppress_mode) if (!ctx.suppress_mode)
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0)); ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
} }
+92 -16
View File
@@ -7,8 +7,7 @@ static mrb_value mrb_bed_exit(mrb_state *mrb, mrb_value) {
return mrb_nil_value(); return mrb_nil_value();
} }
static void static void raise_fatal(mrb_state *mrb, const fatal_error &e) {
raise_fatal(mrb_state *mrb, const fatal_error &e) {
struct RClass *klass = mrb_class_get(mrb, "FatalError"); struct RClass *klass = mrb_class_get(mrb, "FatalError");
mrb_value exc = mrb_exc_new_str(mrb, klass, mrb_str_new_cstr(mrb, e.what())); mrb_value exc = mrb_exc_new_str(mrb, klass, mrb_str_new_cstr(mrb, e.what()));
mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "@code"), mrb_fixnum_value(e.code)); mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "@code"), mrb_fixnum_value(e.code));
@@ -27,26 +26,103 @@ static mrb_value mrb_bed_handle(mrb_state *mrb, mrb_value) {
mrb_raise(mrb, mrb_class_get(mrb, "EdError"), e.what()); mrb_raise(mrb, mrb_class_get(mrb, "EdError"), e.what());
} catch (const fatal_error &f) { } catch (const fatal_error &f) {
raise_fatal(mrb, f); raise_fatal(mrb, f);
} catch (...) {
mrb_raise(mrb, E_RUNTIME_ERROR, "Unexpected error.");
} }
return mrb_nil_value(); return mrb_nil_value();
} }
void register_basic(BEd &ctx) { static mrb_value hash_get(mrb_state *mrb, mrb_value hash, const char *name) {
auto *bed_error = if (mrb_nil_p(hash))
mrb_define_class(ctx.mrb, "EdError", mrb_exc_get_id(ctx.mrb, MRB_ERROR_SYM(RuntimeError))); return mrb_nil_value();
mrb_define_class(ctx.mrb, "FatalError", bed_error); return mrb_hash_get(mrb, hash, mrb_symbol_value(mrb_intern_cstr(mrb, name)));
mrb_define_method(ctx.mrb, ctx.mrb->kernel_module, "exit", mrb_bed_exit, MRB_ARGS_NONE());
mrb_define_method(ctx.mrb, ctx.mrb->kernel_module, "handle", mrb_bed_handle, MRB_ARGS_REQ(1));
} }
void run(BEd &ctx, const std::string &str) { static functions::Function::AddressKind parse_address_kind(mrb_state *mrb, mrb_value value) {
mrb_state *mrb = ctx.mrb; if (mrb_nil_p(value))
mrb_load_nstring(mrb, str.data(), str.size()); return functions::Function::AddressKind::None;
if (!mrb->exc) if (!mrb_symbol_p(value))
return; mrb_raise(mrb, E_TYPE_ERROR, "address must be a Symbol");
auto name = mrb_sym_name(mrb, mrb_symbol(value));
if (strcmp(name, "none") == 0)
return functions::Function::AddressKind::None;
if (strcmp(name, "line") == 0)
return functions::Function::AddressKind::Line;
if (strcmp(name, "range") == 0)
return functions::Function::AddressKind::Range;
mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid address type: :%s", name);
return functions::Function::AddressKind::None;
}
static mrb_value mrb_bed_register(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
mrb_sym r_cmd_name;
mrb_value proc;
mrb_value opts = mrb_nil_value();
mrb_get_args(mrb, "n&|H", &r_cmd_name, &proc, &opts);
mrb_int cmd_name_len;
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
std::string_view cmd_name(name, cmd_name_len);
std::string desc;
mrb_value r_desc = hash_get(mrb, opts, "desc");
if (mrb_string_p(r_desc))
desc.assign(RSTRING_PTR(r_desc), RSTRING_LEN(r_desc));
std::string default_address;
mrb_value r_default_address = hash_get(mrb, opts, "default");
if (mrb_string_p(r_default_address))
default_address.assign(RSTRING_PTR(r_default_address), RSTRING_LEN(r_default_address));
ctx.functions.insert(
cmd_name,
functions::Function{
.address_kind = parse_address_kind(mrb, hash_get(mrb, opts, "address")),
.argument_kind = functions::Function::ArgumentKind::None,
.input_mode = functions::Function::InputMode::None,
.desc = desc,
.default_address = default_address,
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [b = Block(mrb, proc)](
BEd &,
const buffer::Address &,
vase::Shard *,
const functions::Function::Argument &,
std::vector<buffer::Line> *
) {
b.call();
}
}
);
return mrb_nil_value();
}
static mrb_value mrb_bed_unregister(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
mrb_sym r_cmd_name;
mrb_get_args(mrb, "n", &r_cmd_name);
mrb_int cmd_name_len;
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
std::string_view cmd_name(name, cmd_name_len);
ctx.functions.remove(cmd_name);
return mrb_nil_value();
}
void register_basic(BEd &ctx) {
auto mrb = ctx.mrb.state;
auto *bed_error =
mrb_define_class(mrb, "EdError", mrb_exc_get_id(mrb, MRB_ERROR_SYM(RuntimeError)));
mrb_define_class(mrb, "FatalError", bed_error);
mrb_define_method(mrb, mrb->kernel_module, "exit", mrb_bed_exit, MRB_ARGS_NONE());
mrb_define_method(mrb, mrb->kernel_module, "handle", mrb_bed_handle, MRB_ARGS_REQ(1));
mrb_define_method(mrb, mrb->kernel_module, "register", mrb_bed_register, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK());
mrb_define_method(mrb, mrb->kernel_module, "unregister", mrb_bed_unregister, MRB_ARGS_REQ(1));
}
std::string run(BEd &ctx, const std::string &str) {
mrb_state *mrb = ctx.mrb.state;
mrb_value result = mrb_load_nstring(mrb, str.data(), str.size());
if (!mrb->exc) {
mrb_value inspected = mrb_funcall(mrb, result, "inspect", 0);
std::string output(RSTRING_PTR(inspected), RSTRING_LEN(inspected));
return output;
}
mrb_value exc = mrb_obj_value(mrb->exc); mrb_value exc = mrb_obj_value(mrb->exc);
mrb_value msg = mrb_funcall(mrb, exc, "message", 0); mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
std::string error; std::string error;
+81 -61
View File
@@ -35,71 +35,83 @@ inline uint8_t utf8_codepoint_width(unsigned char c) {
} }
bool handle_escapes(RubyParser &p, std::vector<io::Token> *tokens, uint32_t &start, bool string = true) { bool handle_escapes(RubyParser &p, std::vector<io::Token> *tokens, uint32_t &start, bool string = true) {
if (p.peek() == '\\') { if (p.peek() != '\\')
if (string) return false;
tokens->push_back({start, p.i, io::Token::String}); if (!(p.current().flags & RubyState::RubyInternalState::ALLOW_ESCAPE)) {
else
tokens->push_back({start, p.i, io::Token::Regexp});
start = p.i;
p.advance(); p.advance();
if (p.peek() == 'x') { if (p.peek() != '\'' && p.peek() != '\\')
p.advance(); return false;
if (is_hex(p.peek())) if (string)
p.advance(); tokens->push_back({start, p.i - 1, io::Token::String});
if (is_hex(p.peek())) else
p.advance(); tokens->push_back({start, p.i - 1, io::Token::Regexp});
} else if (p.peek() == 'u') { p.advance();
p.advance(); tokens->push_back({p.i - 2, p.i, io::Token::Escape});
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
} else {
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
}
} else if ('0' <= p.peek() && p.peek() <= '7') {
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
} else if (p.peek() == 'c') {
p.advance();
if (p.peek() != '\\')
p.advance();
} else if (p.peek() == 'M' || p.peek() == 'C') {
p.advance();
if (p.peek() == '-') {
p.advance();
if (p.peek() != '\\')
p.advance();
}
} else if (p.peek() == 'N') {
p.advance();
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
}
} else {
p.advance();
}
tokens->push_back({start, p.i, io::Token::Escape});
start = p.i; start = p.i;
return true; return true;
} }
return false; if (string)
tokens->push_back({start, p.i, io::Token::String});
else
tokens->push_back({start, p.i, io::Token::Regexp});
start = p.i;
p.advance();
if (p.peek() == 'x') {
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
} else if (p.peek() == 'u') {
p.advance();
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
} else {
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
}
} else if ('0' <= p.peek() && p.peek() <= '7') {
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
} else if (p.peek() == 'c') {
p.advance();
if (p.peek() != '\\')
p.advance();
} else if (p.peek() == 'M' || p.peek() == 'C') {
p.advance();
if (p.peek() == '-') {
p.advance();
if (p.peek() != '\\')
p.advance();
}
} else if (p.peek() == 'N') {
p.advance();
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
}
} else {
p.advance();
}
tokens->push_back({start, p.i, io::Token::Escape});
start = p.i;
return true;
}; };
bool handle_heredoc(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) { bool handle_heredoc(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) {
@@ -668,6 +680,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.current().delim_start = '\''; p.current().delim_start = '\'';
p.current().delim_end = '\''; p.current().delim_end = '\'';
p.current().flags &= ~RubyState::RubyInternalState::ALLOW_INTERPOLATION; p.current().flags &= ~RubyState::RubyInternalState::ALLOW_INTERPOLATION;
p.current().flags &= ~RubyState::RubyInternalState::ALLOW_ESCAPE;
p.advance(); p.advance();
return false; return false;
} }
@@ -678,6 +691,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.current().delim_start = '"'; p.current().delim_start = '"';
p.current().delim_end = '"'; p.current().delim_end = '"';
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION; p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
p.current().flags |= RubyState::RubyInternalState::ALLOW_ESCAPE;
p.advance(); p.advance();
return false; return false;
} }
@@ -703,6 +717,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
char delim_start = '\0'; char delim_start = '\0';
char delim_end = '\0'; char delim_end = '\0';
bool allow_interp = true; bool allow_interp = true;
bool allow_escape = true;
int prefix_len = 1; int prefix_len = 1;
bool is_regexp = false; bool is_regexp = false;
switch (type) { switch (type) {
@@ -716,6 +731,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
case 'I': case 'I':
case 'W': case 'W':
allow_interp = true; allow_interp = true;
allow_escape = true;
prefix_len = 2; prefix_len = 2;
break; break;
case 'w': case 'w':
@@ -723,10 +739,12 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
case 'i': case 'i':
case 's': case 's':
allow_interp = false; allow_interp = false;
allow_escape = false;
prefix_len = 2; prefix_len = 2;
break; break;
default: default:
allow_interp = true; allow_interp = true;
allow_escape = true;
prefix_len = 1; prefix_len = 1;
break; break;
} }
@@ -768,6 +786,8 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.current().delim_end = delim_end; p.current().delim_end = delim_end;
if (allow_interp) if (allow_interp)
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION; p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
if (allow_escape)
p.current().flags |= RubyState::RubyInternalState::ALLOW_ESCAPE;
p.current().lit_brace_level = 1; p.current().lit_brace_level = 1;
p.advance(prefix_len + 1); p.advance(prefix_len + 1);
return false; return false;
+20 -22
View File
@@ -202,7 +202,7 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
return node; return node;
} }
Shard *Shard::from_command(const char *cmd, bool posix_ending) { Shard *Shard::from_command(const char *cmd) {
auto o = new OriginalStorage("/tmp"); auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd; int dest_fd = o->fd;
if (dest_fd == -1) { if (dest_fd == -1) {
@@ -278,23 +278,21 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
io::IO::enable_raw(); io::IO::enable_raw();
return nullptr; return nullptr;
} }
if (posix_ending) { if (ending[1] == '\n') {
if (ending[1] == '\n') { Petal *last = (Petal *)pieces.back();
Petal *last = (Petal *)pieces.back(); last->lines--;
last->lines--; last->length--;
last->length--; if (last->length == 0) {
if (last->length == 0) { Shard::release(last);
Shard::release(last); pieces.pop_back();
pieces.pop_back(); last = nullptr;
last = nullptr; if (!pieces.empty())
if (!pieces.empty()) last = (Petal *)pieces.back();
last = (Petal *)pieces.back(); else
else return nullptr;
return nullptr;
}
if (last && ending[0] == '\r')
last->length--;
} }
if (last && ending[0] == '\r')
last->length--;
} }
o->initialize(); o->initialize();
io::IO::enable_raw(); io::IO::enable_raw();
@@ -303,7 +301,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
return build(pieces.data(), 0, pieces.size()); return build(pieces.data(), 0, pieces.size());
} }
Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) { Shard *Shard::from_file(const std::filesystem::path &path) {
auto o = new OriginalStorage("/tmp"); auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd; int dest_fd = o->fd;
if (dest_fd == -1) { if (dest_fd == -1) {
@@ -313,10 +311,10 @@ Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
int src_fd = open(path.c_str(), O_RDONLY); int src_fd = open(path.c_str(), O_RDONLY);
if (src_fd == -1) { if (src_fd == -1) {
delete o; delete o;
return nullptr; throw ed_error("Couldn't open file.");
} }
uint64_t total = std::filesystem::file_size(path); uint64_t total = std::filesystem::file_size(path);
if (posix_ending && total > 0) { if (total > 0) {
char last; char last;
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) { if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) {
delete o; delete o;
@@ -381,7 +379,7 @@ Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
return build(pieces.data(), 0, pieces.size()); return build(pieces.data(), 0, pieces.size());
} }
Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) { Shard *Shard::from_string(const char *data, uint64_t len) {
auto o = new OriginalStorage("/tmp"); auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd; int dest_fd = o->fd;
if (dest_fd == -1 || data == nullptr) { if (dest_fd == -1 || data == nullptr) {
@@ -389,7 +387,7 @@ Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
return nullptr; return nullptr;
} }
uint64_t total = len; uint64_t total = len;
if (posix_ending && total > 0) { if (total > 0) {
if (data[total - 1] == '\n') { if (data[total - 1] == '\n') {
total--; total--;
if (total > 0 && data[total - 1] == '\r') if (total > 0 && data[total - 1] == '\r')
-2
View File
@@ -182,8 +182,6 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
Shard::retain(text); Shard::retain(text);
return text; return text;
} }
if (!text)
return root;
if (line > root->lines + 1) if (line > root->lines + 1)
throw ed_error("line out of range"); throw ed_error("line out of range");
Shard::retain(text); Shard::retain(text);