From 7cc61e30e413516084f647aadb1025345443d620 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Wed, 25 May 2022 15:53:09 +0200 Subject: [PATCH] REPL --- Makefile | 2 +- src/main.cc | 79 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index efc6f1e..101ba8c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DEBUG_FLAGS=-O0 -ggdb CXX=g++ LDFLAGS=-L./lib/midi/ -LDLIBS=-lmidi-alsa -lasound +LDLIBS=-lmidi-alsa -lasound -lreadline Obj= \ context.o \ diff --git a/src/main.cc b/src/main.cc index e49539d..eee4bb9 100644 --- a/src/main.cc +++ b/src/main.cc @@ -7,10 +7,15 @@ #include #include +#include +#include namespace fs = std::filesystem; static bool ast_only_mode = false; +static bool enable_repl = false; + +#define Ignore(Call) do { auto const ignore_ ## __LINE__ = (Call); (void) ignore_ ## __LINE__; } while(0) static std::string_view pop(std::span &span) { @@ -28,6 +33,8 @@ void usage() " where options are:\n" " -c,--run CODE\n" " executes given code\n" + " -i,--interactive,--repl\n" + " enables interactive mode even when another code was passed\n" " --ast\n" " prints ast for given code\n" " -l,--list\n" @@ -35,6 +42,25 @@ void usage() std::exit(1); } +static void trim(std::string_view &s) +{ + // left trim + if (auto const i = std::find_if_not(s.begin(), s.end(), unicode::is_space); i != s.begin()) { + // std::string_view::remove_prefix has UB when we wan't to remove more characters then str has + // src: https://en.cppreference.com/w/cpp/string/basic_string_view/remove_prefix + if (i != s.end()) { + s.remove_prefix(std::distance(s.begin(), i)); + } else { + s = {}; + } + } + + // right trim + if (auto const ws_end = std::find_if_not(s.rbegin(), s.rend(), unicode::is_space); ws_end != s.rbegin()) { + s.remove_suffix(std::distance(ws_end.base(), s.end())); + } +} + struct Runner { midi::ALSA alsa; @@ -84,10 +110,6 @@ struct Run static Result Main(std::span args) { - if (args.empty()) { - usage(); - } - std::vector runnables; while (not args.empty()) { @@ -119,14 +141,15 @@ static Result Main(std::span args) continue; } + if (arg == "--repl" || arg == "-i" || arg == "--interactive") { + enable_repl = true; + continue; + } + std::cerr << "musique: error: unrecognized command line option: " << arg << std::endl; std::exit(1); } - if (runnables.empty()) { - usage(); - } - Runner runner("14"); for (auto const& [is_file, argument] : runnables) { @@ -149,11 +172,51 @@ static Result Main(std::span args) Try(runner.run(eternal_sources.back(), path)); } + if (runnables.empty() || enable_repl) { + for (;;) { + char *input_buffer = readline("> "); + if (input_buffer == nullptr) { + break; + } + + // Raw input line used for execution in language + std::string_view raw = input_buffer; + + // Used to recognize REPL commands + std::string_view command = raw; + trim(command); + + if (command.empty()) { + // Line is empty so there is no need to execute it or parse it + free(input_buffer); + continue; + } + + add_history(input_buffer); + + if (command.starts_with(':')) { + command.remove_prefix(1); + if (command == "exit") { break; } + if (command == "clear") { std::cout << "\x1b[1;1H\x1b[2J" << std::flush; continue; } + if (command.starts_with('!')) { Ignore(system(command.data() + 1)); continue; } + std::cerr << "musique: error: unrecognized REPL command '" << command << '\'' << std::endl; + continue; + } + + Try(runner.run(raw, "")); + // We don't free input line since there could be values that still relay on it + } + } + return {}; } int main(int argc, char const** argv) { + rl_readline_name = *argv; + // Set editing mode to Vim-like + rl_editing_mode = 0; + auto const args = std::span(argv, argc).subspan(1); auto const result = Main(args); if (not result.has_value()) {