Add basic scripting support.
- Fix certain bugs in ruby parser - And a lot more cleanup/minor fixes.
This commit is contained in:
@@ -36,7 +36,7 @@ CFLAGS_RELEASE :=\
|
||||
-fomit-frame-pointer -DNDEBUG -s \
|
||||
-I./include -I./libs/unicode_width
|
||||
|
||||
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS) $(C_SANITIZER)
|
||||
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
|
||||
CFLAGS_RELEASE += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
|
||||
|
||||
UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "internal/functions/suffixes.h"
|
||||
#include "internal/io/io.h"
|
||||
#include "internal/marks/marks.h"
|
||||
#include "internal/scripting/ruby.h"
|
||||
#include "internal/theme/theme.h"
|
||||
#include "internal/ui/command.h"
|
||||
#include "internal/ui/text_mode.h"
|
||||
@@ -21,6 +22,8 @@ struct BEd {
|
||||
std::unordered_map<std::string, internal::syntax::Language *> languages;
|
||||
internal::io::IO io;
|
||||
internal::vase::AppendStorage append{"/tmp"};
|
||||
mrb_state *mrb = nullptr;
|
||||
int arena;
|
||||
|
||||
bool help_mode = false;
|
||||
bool prompt_mode = true;
|
||||
|
||||
@@ -14,8 +14,8 @@ struct HistoryItem {
|
||||
|
||||
struct GenericBuffer : ShardBuffer {
|
||||
uint64_t base_version{0};
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
std::string action;
|
||||
std::chrono::system_clock::time_point timestamp{std::chrono::system_clock::now()};
|
||||
std::string action{"Created buffer."};
|
||||
std::filesystem::path save_path{};
|
||||
std::vector<HistoryItem> undo_stack;
|
||||
std::vector<HistoryItem> redo_stack;
|
||||
@@ -24,6 +24,7 @@ struct GenericBuffer : ShardBuffer {
|
||||
: ShardBuffer(name, nullptr, nullptr, Kind::Generic) {}
|
||||
~GenericBuffer();
|
||||
|
||||
void language(BEd &ctx, std::string name);
|
||||
void list_history(BEd &ctx);
|
||||
ReadonlyBuffer *get_history(uint64_t version);
|
||||
void snapshot(std::string action);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "definitions.h"
|
||||
#include "pch.h"
|
||||
|
||||
namespace bed::internal::scripting {
|
||||
struct Block {
|
||||
mrb_value proc;
|
||||
mrb_state *mrb;
|
||||
|
||||
Block(mrb_state *mrb, mrb_value proc)
|
||||
: proc(proc), mrb(mrb) {
|
||||
mrb_gc_register(mrb, proc);
|
||||
}
|
||||
|
||||
Block() : proc(mrb_nil_value()) {}
|
||||
|
||||
~Block() {
|
||||
if (!mrb_nil_p(proc))
|
||||
mrb_gc_unregister(mrb, proc);
|
||||
}
|
||||
|
||||
void set_proc(mrb_value new_proc) {
|
||||
if (!mrb_nil_p(proc))
|
||||
mrb_gc_unregister(mrb, proc);
|
||||
proc = new_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 {
|
||||
if (mrb_nil_p(proc))
|
||||
return mrb_nil_value();
|
||||
mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
void register_basic(BEd &ctx);
|
||||
void run(BEd &ctx, const std::string &str);
|
||||
}; // namespace bed::internal::scripting
|
||||
@@ -48,6 +48,7 @@ struct ParseState {
|
||||
uint64_t line, uint64_t original, uint64_t final
|
||||
);
|
||||
static ParseState *concat(Language &lang, ParseState *a, ParseState *b);
|
||||
static void *state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line);
|
||||
};
|
||||
|
||||
struct ParseStateBranch : ParseState {
|
||||
@@ -83,18 +84,25 @@ struct ParsePieceBuilder {
|
||||
std::vector<ParseState *> pieces;
|
||||
std::vector<uint16_t> blocks;
|
||||
void *piece_state{nullptr};
|
||||
void *prev_state{nullptr};
|
||||
uint64_t chunk_start{0};
|
||||
uint64_t chunk_lines{0};
|
||||
ParsePieceBuilder(Language &lang, uint64_t first_line)
|
||||
: lang(lang), chunk_start(first_line) {}
|
||||
void add(
|
||||
void *state,
|
||||
uint64_t line,
|
||||
const std::vector<ParseEvent> &events
|
||||
) {
|
||||
ParsePieceBuilder(Language &lang, uint64_t first_line, void *entry_state)
|
||||
: lang(lang), chunk_start(first_line) {
|
||||
prev_state = lang.copy(entry_state);
|
||||
}
|
||||
~ParsePieceBuilder() {
|
||||
if (prev_state)
|
||||
lang.destroy(prev_state);
|
||||
if (piece_state)
|
||||
lang.destroy(piece_state);
|
||||
}
|
||||
ParsePieceBuilder(const ParsePieceBuilder &) = delete;
|
||||
ParsePieceBuilder &operator=(const ParsePieceBuilder &) = delete;
|
||||
void add(void *state, uint64_t line, const std::vector<ParseEvent> &events) {
|
||||
if (chunk_lines == 0) {
|
||||
chunk_start = line;
|
||||
piece_state = lang.copy(state);
|
||||
piece_state = lang.copy(prev_state);
|
||||
}
|
||||
for (const auto &ev : events) {
|
||||
blocks.push_back(
|
||||
@@ -103,6 +111,9 @@ struct ParsePieceBuilder {
|
||||
);
|
||||
}
|
||||
++chunk_lines;
|
||||
if (prev_state)
|
||||
lang.destroy(prev_state);
|
||||
prev_state = lang.copy(state);
|
||||
if (chunk_lines == ParseStateLeaf::MAX_CHUNK)
|
||||
flush();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang);
|
||||
ParserSnapshot retain(const ParserSnapshot &snap);
|
||||
void release(ParserSnapshot &snap);
|
||||
|
||||
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line);
|
||||
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line);
|
||||
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line);
|
||||
|
||||
|
||||
@@ -4,10 +4,29 @@
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/boxing_word.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/common.h>
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/data.h>
|
||||
#include <mruby/dump.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/gc.h>
|
||||
#include <mruby/hash.h>
|
||||
#include <mruby/internal.h>
|
||||
#include <mruby/irep.h>
|
||||
#include <mruby/numeric.h>
|
||||
#include <mruby/object.h>
|
||||
#include <mruby/opcode.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <mruby/proc.h>
|
||||
#include <mruby/range.h>
|
||||
#include <mruby/re.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/throw.h>
|
||||
#include <mruby/value.h>
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/version.h>
|
||||
#include <pcre2.h>
|
||||
extern "C" {
|
||||
#include <grapheme.h>
|
||||
|
||||
+12
-1
@@ -1,5 +1,4 @@
|
||||
#include "bed.h"
|
||||
#include "internal/parser/parser.h"
|
||||
|
||||
namespace bed {
|
||||
BEd::BEd(std::vector<std::string> args)
|
||||
@@ -43,9 +42,15 @@ BEd::BEd(std::vector<std::string> args)
|
||||
&& strcmp(colorterm, "24bit") != 0)
|
||||
color = false;
|
||||
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_extented(*this);
|
||||
internal::functions::Suffix::register_suffixes(*this);
|
||||
internal::scripting::register_basic(*this);
|
||||
languages["ruby"] = new internal::syntax::Language(internal::syntax::ruby::lang_ruby());
|
||||
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
|
||||
current() = {"default", 0};
|
||||
@@ -65,6 +70,12 @@ BEd::~BEd() {
|
||||
delete buffer;
|
||||
for (auto &[_, lang] : languages)
|
||||
delete lang;
|
||||
mrb->ud = nullptr;
|
||||
if (mrb) {
|
||||
mrb_gc_arena_restore(mrb, arena);
|
||||
mrb_full_gc(mrb);
|
||||
mrb_close(mrb);
|
||||
}
|
||||
}
|
||||
|
||||
void BEd::print_help() {
|
||||
|
||||
@@ -2,23 +2,6 @@
|
||||
#include "internal/parser/parser.h"
|
||||
|
||||
namespace bed {
|
||||
void BEd::run() {
|
||||
while (true) {
|
||||
internal::ui::CommandIO command(*this);
|
||||
auto [cmd, eof] = command.run();
|
||||
try {
|
||||
handle(cmd, eof);
|
||||
} catch (ed_error &e) {
|
||||
io.apply(internal::io::Token::Warning);
|
||||
io.write_line("?");
|
||||
if (help_mode)
|
||||
io.write_line(e.what());
|
||||
io.reset();
|
||||
last_help = e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BEd::handle(std::string_view cmd, bool eof) {
|
||||
if (eof && cmd.empty()) {
|
||||
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ void BEd::run() {
|
||||
auto [cmd, eof] = command.run();
|
||||
try {
|
||||
handle(cmd, eof);
|
||||
} catch (ed_error &e) {
|
||||
} catch (const ed_error &e) {
|
||||
io.apply(internal::io::Token::Warning);
|
||||
io.write_line("?");
|
||||
if (help_mode)
|
||||
|
||||
@@ -203,7 +203,23 @@ uint64_t GenericBuffer::prune(int keep) {
|
||||
bool GenericBuffer::waste() {
|
||||
return save_path.empty()
|
||||
&& root == nullptr
|
||||
&& undo_stack.empty();
|
||||
&& undo_stack.empty()
|
||||
&& parse.lang == nullptr;
|
||||
}
|
||||
|
||||
void GenericBuffer::language(BEd &ctx, std::string name) {
|
||||
syntax::Language *lang = nullptr;
|
||||
if (name.size()) {
|
||||
auto it = ctx.languages.find(name);
|
||||
if (it == ctx.languages.end())
|
||||
throw ed_error("Language not found.");
|
||||
lang = it->second;
|
||||
}
|
||||
if (lang == parse.lang)
|
||||
return;
|
||||
snapshot("Change buffer language.");
|
||||
syntax::release(parse);
|
||||
parse = syntax::make_parser(root, lines(), lang);
|
||||
}
|
||||
|
||||
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
||||
@@ -225,7 +241,7 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
||||
}
|
||||
ctx.current() = {name, lines()};
|
||||
syntax::release(parse);
|
||||
parse = syntax::make_parser(root, lines(), ctx.languages["ruby"]);
|
||||
parse = syntax::make_parser(root, lines(), nullptr);
|
||||
}
|
||||
|
||||
void GenericBuffer::set_filename(std::filesystem::path path) {
|
||||
|
||||
@@ -173,24 +173,95 @@ void Function::register_extented(BEd &ctx) {
|
||||
ctx.functions.insert(
|
||||
"`",
|
||||
Function{
|
||||
.address_kind = Function::AddressKind::None,
|
||||
.address_kind = Function::AddressKind::Range,
|
||||
.argument_kind = Function::ArgumentKind::Ruby,
|
||||
.input_mode = Function::InputMode::None,
|
||||
.desc = "Execute some ruby code",
|
||||
.default_address = "",
|
||||
.accept_zero = true,
|
||||
.pre_text_mode = nullptr,
|
||||
.handle = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
vase::Shard *,
|
||||
const Argument &arg_,
|
||||
std::vector<buffer::Line> *
|
||||
) {
|
||||
auto &addr = std::get<buffer::Range>(addr_);
|
||||
auto &arg = std::get<RubyArg>(arg_);
|
||||
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, arg.cmd);
|
||||
},
|
||||
}
|
||||
);
|
||||
ctx.functions.insert(
|
||||
"``",
|
||||
Function{
|
||||
.address_kind = Function::AddressKind::Range,
|
||||
.argument_kind = Function::ArgumentKind::None,
|
||||
.input_mode = Function::InputMode::None,
|
||||
.desc = "Execute addressed lines as ruby code.",
|
||||
.default_address = ".,.",
|
||||
.accept_zero = false,
|
||||
.pre_text_mode = nullptr,
|
||||
.handle = [](
|
||||
BEd &,
|
||||
const buffer::Address &,
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
vase::Shard *,
|
||||
const Argument &,
|
||||
std::vector<buffer::Line> *
|
||||
) {
|
||||
// TODO: connect up to mruby.
|
||||
auto &addr = std::get<buffer::Range>(addr_);
|
||||
auto &buf = ctx.buffer(addr.buffername);
|
||||
auto code = buf.copy(addr.start, addr.end);
|
||||
ctx.prev().buffername = addr.buffername;
|
||||
ctx.prev().start = addr.start;
|
||||
ctx.prev().end = addr.end;
|
||||
ctx.current() = {addr.buffername, addr.end};
|
||||
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(
|
||||
"```",
|
||||
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(
|
||||
"echo",
|
||||
Function{
|
||||
@@ -432,5 +503,38 @@ void Function::register_extented(BEd &ctx) {
|
||||
}
|
||||
}
|
||||
);
|
||||
ctx.functions.insert(
|
||||
"lang",
|
||||
Function{
|
||||
.address_kind = Function::AddressKind::None,
|
||||
.argument_kind = Function::ArgumentKind::Any,
|
||||
.input_mode = Function::InputMode::None,
|
||||
.desc = "Set buffer language",
|
||||
.default_address = "",
|
||||
.accept_zero = false,
|
||||
.pre_text_mode = nullptr,
|
||||
.handle = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
vase::Shard *,
|
||||
const Argument &arg_,
|
||||
std::vector<buffer::Line> *
|
||||
) {
|
||||
auto &addr = std::get<std::string>(addr_);
|
||||
auto name = std::get<std::string>(arg_);
|
||||
const auto first = name.find_first_not_of(" \t");
|
||||
const auto last = name.find_last_not_of(" \t");
|
||||
if (first == std::string::npos)
|
||||
name.clear();
|
||||
else
|
||||
name = name.substr(first, last - first + 1);
|
||||
auto &buf_ = ctx.buffer(addr);
|
||||
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||
throw ed_error("Can't set language to buffer.");
|
||||
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||
buf.language(ctx, name);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} // namespace bed::internal::functions
|
||||
|
||||
@@ -37,7 +37,19 @@ void Function::register_posix(BEd &ctx) {
|
||||
.desc = "Append text to a line",
|
||||
.default_address = ".",
|
||||
.accept_zero = true,
|
||||
.pre_text_mode = nullptr,
|
||||
.pre_text_mode = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
const Argument &
|
||||
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
|
||||
const auto &addr = std::get<buffer::Line>(addr_);
|
||||
auto &buf_ = ctx.buffer(addr.buffername);
|
||||
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||
return {nullptr, nullptr, nullptr};
|
||||
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
|
||||
return {nullptr, buf.parse.lang, state};
|
||||
},
|
||||
.handle = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
@@ -47,8 +59,7 @@ void Function::register_posix(BEd &ctx) {
|
||||
) {
|
||||
auto addr = std::get<buffer::Line>(addr_);
|
||||
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
|
||||
vase::Shard::release(text);
|
||||
}
|
||||
vase::Shard::release(text); }
|
||||
}
|
||||
);
|
||||
ctx.functions.insert(
|
||||
@@ -70,9 +81,7 @@ void Function::register_posix(BEd &ctx) {
|
||||
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||
return {buf_.copy(addr.start, addr.end), nullptr, nullptr};
|
||||
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||
if (!buf.parse.lang)
|
||||
return {buf.copy(addr.start, addr.end), nullptr, nullptr};
|
||||
void *state = nullptr; // TODO: buf.parse.root.get_at(addr.start);
|
||||
void *state = syntax::state_before(buf.parse, buf.root, addr.start - 1);
|
||||
return {buf.copy(addr.start, addr.end), buf.parse.lang, state};
|
||||
},
|
||||
.handle = [](
|
||||
@@ -291,7 +300,21 @@ void Function::register_posix(BEd &ctx) {
|
||||
.desc = "Insert text before a line",
|
||||
.default_address = ".",
|
||||
.accept_zero = true,
|
||||
.pre_text_mode = nullptr,
|
||||
.pre_text_mode = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
const Argument &
|
||||
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
|
||||
auto addr = std::get<buffer::Line>(addr_);
|
||||
if (addr.number)
|
||||
addr.number--;
|
||||
auto &buf_ = ctx.buffer(addr.buffername);
|
||||
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||
return {nullptr, nullptr, nullptr};
|
||||
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
|
||||
return {nullptr, buf.parse.lang, state};
|
||||
},
|
||||
.handle = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
@@ -303,8 +326,7 @@ void Function::register_posix(BEd &ctx) {
|
||||
if (addr.number)
|
||||
addr.number--;
|
||||
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
|
||||
vase::Shard::release(text);
|
||||
}
|
||||
vase::Shard::release(text); }
|
||||
}
|
||||
);
|
||||
ctx.functions.insert(
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "internal/scripting/ruby.h"
|
||||
#include "bed.h"
|
||||
|
||||
namespace bed::internal::scripting {
|
||||
static mrb_value mrb_bed_exit(mrb_state *mrb, mrb_value) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "Use `handle(\"q\")` to quit.");
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
static void
|
||||
raise_fatal(mrb_state *mrb, const fatal_error &e) {
|
||||
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_iv_set(mrb, exc, mrb_intern_lit(mrb, "@code"), mrb_fixnum_value(e.code));
|
||||
mrb_exc_raise(mrb, exc);
|
||||
}
|
||||
|
||||
static mrb_value mrb_bed_handle(mrb_state *mrb, mrb_value) {
|
||||
auto &ctx = *(BEd *)mrb->ud;
|
||||
const char *command;
|
||||
mrb_int len;
|
||||
mrb_get_args(mrb, "s", &command, &len);
|
||||
std::string_view cmd(command, len);
|
||||
try {
|
||||
ctx.handle(cmd, false);
|
||||
} catch (const ed_error &e) {
|
||||
mrb_raise(mrb, mrb_class_get(mrb, "EdError"), e.what());
|
||||
} catch (const fatal_error &f) {
|
||||
raise_fatal(mrb, f);
|
||||
} catch (...) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "Unexpected error.");
|
||||
}
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
void register_basic(BEd &ctx) {
|
||||
auto *bed_error =
|
||||
mrb_define_class(ctx.mrb, "EdError", mrb_exc_get_id(ctx.mrb, MRB_ERROR_SYM(RuntimeError)));
|
||||
mrb_define_class(ctx.mrb, "FatalError", bed_error);
|
||||
|
||||
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) {
|
||||
mrb_state *mrb = ctx.mrb;
|
||||
mrb_load_nstring(mrb, str.data(), str.size());
|
||||
if (!mrb->exc)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
} // namespace bed::internal::scripting
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
namespace bed::internal::syntax {
|
||||
Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase)
|
||||
: snap(p) {
|
||||
uint64_t offset;
|
||||
TreeCursor c = TreeCursor(*p.lang, p.root, target, &offset);
|
||||
at = target - offset;
|
||||
state = p.lang->copy(c.leaf->state);
|
||||
: snap(p), at(target) {
|
||||
at = target;
|
||||
if (snap.lang)
|
||||
state = ParseState::state_before(*snap.lang, snap.root, vase, at);
|
||||
it = vase::Iterator(vase, at, Direction::Forward);
|
||||
while (at < target) {
|
||||
it->next();
|
||||
tokens.clear();
|
||||
events.clear();
|
||||
p.lang->parse(&state, it->line, at == 0, &tokens, &events);
|
||||
at++;
|
||||
}
|
||||
}
|
||||
|
||||
Iterator::~Iterator() {
|
||||
@@ -46,6 +38,8 @@ Iterator &Iterator::operator=(Iterator &&other) {
|
||||
}
|
||||
|
||||
void Iterator::next() {
|
||||
if (!state)
|
||||
return;
|
||||
it->next();
|
||||
tokens.clear();
|
||||
events.clear();
|
||||
|
||||
@@ -8,7 +8,7 @@ MiniParser::MiniParser(Language &lang, vase::Shard *vase, void *initial_state)
|
||||
void *state = lang.copy(start_state);
|
||||
std::vector<io::Token> tokens;
|
||||
std::vector<ParseEvent> events;
|
||||
const uint64_t count = vase ? vase->lines + 1 : 0;
|
||||
const uint64_t count = vase ? vase->lines + 1 : 1;
|
||||
lines.reserve(count);
|
||||
for (uint64_t i = 0; i < count; ++i) {
|
||||
it.next();
|
||||
|
||||
@@ -22,6 +22,12 @@ void release(ParserSnapshot &snap) {
|
||||
snap.root = nullptr;
|
||||
}
|
||||
|
||||
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line) {
|
||||
if (!snap.lang)
|
||||
return nullptr;
|
||||
return ParseState::state_before(*snap.lang, snap.root, vase, line);
|
||||
}
|
||||
|
||||
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) {
|
||||
if (!snap.root || !snap.lang)
|
||||
return line + 10;
|
||||
|
||||
@@ -108,7 +108,7 @@ ParseState *ParseState::splice(
|
||||
void *state = lang.none_state();
|
||||
std::vector<io::Token> tokens;
|
||||
std::vector<ParseEvent> events;
|
||||
ParsePieceBuilder builder(lang, 0);
|
||||
ParsePieceBuilder builder(lang, 0, state);
|
||||
for (uint64_t at = 0; at < final; ++at) {
|
||||
it.next();
|
||||
tokens.clear();
|
||||
@@ -136,7 +136,7 @@ ParseState *ParseState::splice(
|
||||
uint64_t end_extra;
|
||||
TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra);
|
||||
uint64_t end_in_vase = line + final;
|
||||
ParsePieceBuilder builder(lang, at);
|
||||
ParsePieceBuilder builder(lang, at, state);
|
||||
while (at < end_in_vase + end_extra) {
|
||||
it.next();
|
||||
tokens.clear();
|
||||
@@ -192,4 +192,28 @@ ParseState *ParseState::concat(Language &lang, ParseState *a, ParseState *b) {
|
||||
}
|
||||
return balance(lang, new ParseStateBranch(a, b));
|
||||
}
|
||||
|
||||
void *ParseState::state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line) {
|
||||
uint64_t offset;
|
||||
uint64_t at;
|
||||
void *state;
|
||||
if (!root) {
|
||||
at = 0;
|
||||
state = lang.none_state();
|
||||
} else {
|
||||
TreeCursor c = TreeCursor(lang, root, line, &offset);
|
||||
at = line - offset;
|
||||
state = lang.copy(c.leaf->state);
|
||||
}
|
||||
vase::Iterator it(vase, at, Direction::Forward);
|
||||
std::vector<io::Token> tokens;
|
||||
std::vector<ParseEvent> events;
|
||||
for (; at < line; ++at) {
|
||||
it.next();
|
||||
tokens.clear();
|
||||
events.clear();
|
||||
lang.parse(&state, it.line, at == 0, &tokens, &events);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
} // namespace bed::internal::syntax
|
||||
|
||||
@@ -782,11 +782,6 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
|
||||
p.ending = false;
|
||||
p.advance();
|
||||
return false;
|
||||
case '\0':
|
||||
if (p.ending)
|
||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||
p.advance();
|
||||
return false;
|
||||
default:
|
||||
if ('0' <= p.peek() && p.peek() <= '9') {
|
||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||
@@ -981,6 +976,11 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
|
||||
| RubyState::RubyInternalState::DEF_NAME;
|
||||
return false;
|
||||
}
|
||||
if (j > 3 && p.peek_str(3) == "to_") {
|
||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||
tokens->push_back({p.i, p.i + j, io::Token::Function});
|
||||
p.advance(j);
|
||||
}
|
||||
uint32_t start = p.i;
|
||||
if (p.peek(j) == ':') {
|
||||
p.advance(j);
|
||||
@@ -1008,8 +1008,9 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
|
||||
return false;
|
||||
if (p.peek(j) == '&'
|
||||
|| p.peek(j) == '%'
|
||||
|| p.peek(j) == ':') {
|
||||
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '>')
|
||||
|| p.peek(j) == ':'
|
||||
|| p.peek(j) == '?') {
|
||||
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '\0')
|
||||
return false;
|
||||
} else if (p.peek(j) == '-') {
|
||||
if (p.peek(j + 1) != '>')
|
||||
@@ -1025,7 +1026,6 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
|
||||
|| p.peek(j) == '*'
|
||||
|| p.peek(j) == '/'
|
||||
|| p.peek(j) == '='
|
||||
|| p.peek(j) == '?'
|
||||
|| p.peek(j) == '|'
|
||||
|| p.peek(j) == '^'
|
||||
|| p.peek(j) == '<'
|
||||
@@ -1061,7 +1061,7 @@ void ruby_parse(
|
||||
std::vector<ParseEvent> *events
|
||||
) {
|
||||
RubyParser p(v_state, line);
|
||||
while (p.i <= p.len()) {
|
||||
while (p.i < p.len()) {
|
||||
p.op_last = p.set_op_last;
|
||||
p.set_op_last = false;
|
||||
if (p.current().state == RubyState::RubyInternalState::END)
|
||||
@@ -1099,6 +1099,13 @@ void ruby_parse(
|
||||
return;
|
||||
if (!handle_syntax(p, tokens, events))
|
||||
p.current().flags &= ~RubyState::RubyInternalState::NEWLINE;
|
||||
if (p.peek() == '\0') {
|
||||
if (p.ending)
|
||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||
p.advance();
|
||||
p.current().flags &= ~RubyState::RubyInternalState::NEWLINE;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (p.ending)
|
||||
|
||||
@@ -206,11 +206,13 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||
}
|
||||
vase::Range r = {last, cursor};
|
||||
vase = vase::erase(vase, r);
|
||||
if (cursor.row == last.row) {
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
} else {
|
||||
parser->erase(vase, cursor.row, 1);
|
||||
parser->dirty(vase, last.row, 1);
|
||||
if (parser) {
|
||||
if (cursor.row == last.row) {
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
} else {
|
||||
parser->erase(vase, cursor.row, 1);
|
||||
parser->dirty(vase, last.row, 1);
|
||||
}
|
||||
}
|
||||
cursor = last;
|
||||
}
|
||||
@@ -224,10 +226,12 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||
}
|
||||
}
|
||||
vase = vase::insert(&bed.append, vase, &cursor, '\n');
|
||||
parser->insert(vase, cursor.row - 1, 1);
|
||||
if (parser)
|
||||
parser->insert(vase, cursor.row - 1, 1);
|
||||
} else {
|
||||
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
if (parser)
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -235,11 +239,13 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||
case io::KeyEvent::KeyType::PASTE: {
|
||||
auto lines = std::count(res.text.begin(), res.text.end(), '\n');
|
||||
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||
if (lines) {
|
||||
parser->insert(vase, cursor.row - lines, lines);
|
||||
parser->dirty(vase, cursor.row - lines, 1);
|
||||
} else {
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
if (parser) {
|
||||
if (lines) {
|
||||
parser->insert(vase, cursor.row - lines, lines);
|
||||
parser->dirty(vase, cursor.row - lines, 1);
|
||||
} else {
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case io::KeyEvent::KeyType::SPECIAL:
|
||||
@@ -304,9 +310,11 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||
}
|
||||
vase::Range r = {cursor, next};
|
||||
vase = vase::erase(vase, r);
|
||||
if (cursor.row != next.row)
|
||||
parser->erase(vase, cursor.row + 1, 1);
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
if (parser) {
|
||||
if (cursor.row != next.row)
|
||||
parser->erase(vase, cursor.row + 1, 1);
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user