-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* repl: Add dummy reply app * cmake: Cleanup naming of run targets * core: Fix typos * core: Support no-signal tty mode * repl: Wip prompt logic * core: Add tty_write_set_cursor_hor_sequence util * core: Add 'EndOfText' unicode name * core: Add basic tty input lexer * repl: Add edit buffer * repl: Minor line edit polish * core: Add tty_write_line_wrap_sequence util * repl: Disable line wrap * repl: Add script execution * repl: Add syntax highlighting * repl: Add prev text support * repl: Tweak syntax highlighting colors * repl: MSVC compat * core: Add carriage return as accept input
- Loading branch information
1 parent
6c1de1b
commit 43cadea
Showing
9 changed files
with
433 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
#include "app_cli.h" | ||
#include "core_alloc.h" | ||
#include "core_file.h" | ||
#include "core_format.h" | ||
#include "core_tty.h" | ||
#include "core_utf8.h" | ||
#include "script_eval.h" | ||
#include "script_lex.h" | ||
#include "script_mem.h" | ||
#include "script_read.h" | ||
|
||
/** | ||
* ReadEvalPrintLoop - Utility to play around with script execution. | ||
*/ | ||
|
||
static void repl_output(const String text) { file_write_sync(g_file_stdout, text); } | ||
|
||
static void repl_output_error(const String message) { | ||
const String text = fmt_write_scratch( | ||
"{}ERROR: {}{}\n", | ||
fmt_ttystyle(.bgColor = TtyBgColor_Red, .flags = TtyStyleFlags_Bold), | ||
fmt_text(message), | ||
fmt_ttystyle()); | ||
repl_output(text); | ||
} | ||
|
||
static TtyFgColor repl_token_color(const ScriptTokenType tokenType) { | ||
switch (tokenType) { | ||
case ScriptTokenType_Error: | ||
return TtyFgColor_BrightRed; | ||
case ScriptTokenType_Number: | ||
return TtyFgColor_Yellow; | ||
case ScriptTokenType_Identifier: | ||
return TtyFgColor_Magenta; | ||
case ScriptTokenType_Key: | ||
return TtyFgColor_Blue; | ||
case ScriptTokenType_Eq: | ||
case ScriptTokenType_EqEq: | ||
case ScriptTokenType_Bang: | ||
case ScriptTokenType_BangEq: | ||
case ScriptTokenType_Le: | ||
case ScriptTokenType_LeEq: | ||
case ScriptTokenType_Gt: | ||
case ScriptTokenType_GtEq: | ||
case ScriptTokenType_Plus: | ||
case ScriptTokenType_Minus: | ||
case ScriptTokenType_Star: | ||
case ScriptTokenType_Slash: | ||
case ScriptTokenType_Colon: | ||
case ScriptTokenType_SemiColon: | ||
case ScriptTokenType_AmpAmp: | ||
case ScriptTokenType_PipePipe: | ||
case ScriptTokenType_QMark: | ||
case ScriptTokenType_QMarkQMark: | ||
return TtyFgColor_Green; | ||
case ScriptTokenType_ParenOpen: | ||
case ScriptTokenType_ParenClose: | ||
case ScriptTokenType_Comma: | ||
case ScriptTokenType_End: | ||
break; | ||
} | ||
return TtyFgColor_Default; | ||
} | ||
|
||
typedef struct { | ||
String editPrevText; | ||
DynString* editBuffer; | ||
ScriptMem* scriptMem; | ||
} ReplState; | ||
|
||
static bool repl_edit_empty(const ReplState* state) { | ||
return string_is_empty(dynstring_view(state->editBuffer)); | ||
} | ||
|
||
static void repl_edit_prev(const ReplState* state) { | ||
if (!string_is_empty(state->editPrevText)) { | ||
dynstring_clear(state->editBuffer); | ||
dynstring_append(state->editBuffer, state->editPrevText); | ||
} | ||
} | ||
|
||
static void repl_edit_clear(const ReplState* state) { dynstring_clear(state->editBuffer); } | ||
|
||
static void repl_edit_insert(const ReplState* state, const Unicode cp) { | ||
utf8_cp_write(state->editBuffer, cp); | ||
} | ||
|
||
static void repl_edit_delete(const ReplState* state) { | ||
// Delete the last utf8 code-point. | ||
String str = dynstring_view(state->editBuffer); | ||
for (usize i = str.size; i-- > 0;) { | ||
if (!utf8_contchar(*string_at(str, i))) { | ||
dynstring_erase_chars(state->editBuffer, i, str.size - i); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
static void repl_edit_submit(ReplState* state) { | ||
repl_output(string_lit("\n")); // Preserve the input line. | ||
|
||
string_maybe_free(g_alloc_heap, state->editPrevText); | ||
state->editPrevText = string_maybe_dup(g_alloc_heap, dynstring_view(state->editBuffer)); | ||
|
||
ScriptDoc* script = script_create(g_alloc_heap); | ||
ScriptReadResult res; | ||
script_read_all(script, dynstring_view(state->editBuffer), &res); | ||
|
||
if (res.type == ScriptResult_Success) { | ||
const ScriptVal value = script_eval(script, state->scriptMem, res.expr); | ||
const String text = fmt_write_scratch("{}\n", fmt_text(script_val_str_scratch(value))); | ||
repl_output(text); | ||
} else { | ||
repl_output_error(script_error_str(res.error)); | ||
} | ||
|
||
script_destroy(script); | ||
dynstring_clear(state->editBuffer); | ||
} | ||
|
||
static void repl_edit_render(const ReplState* state) { | ||
Mem bufferMem = alloc_alloc(g_alloc_scratch, usize_kibibyte, 1); | ||
DynString buffer = dynstring_create_over(bufferMem); | ||
|
||
tty_write_clear_line_sequence(&buffer, TtyClearMode_All); // Clear line. | ||
tty_write_set_cursor_hor_sequence(&buffer, 0); // Move cursor to beginning of line. | ||
tty_write_line_wrap_sequence(&buffer, false); // Disable line wrap. | ||
|
||
// Render header. | ||
tty_write_style_sequence(&buffer, ttystyle(.flags = TtyStyleFlags_Faint)); | ||
dynstring_append(&buffer, string_lit("> ")); | ||
tty_write_style_sequence(&buffer, ttystyle()); | ||
|
||
// Render edit text. | ||
String editText = dynstring_view(state->editBuffer); | ||
ScriptToken token; | ||
for (;;) { | ||
const String remText = script_lex(editText, null, &token); | ||
const usize tokenSize = editText.size - remText.size; | ||
const String tokenText = string_slice(editText, 0, tokenSize); | ||
tty_write_style_sequence(&buffer, ttystyle(.fgColor = repl_token_color(token.type))); | ||
dynstring_append(&buffer, tokenText); | ||
if (token.type == ScriptTokenType_End) { | ||
break; | ||
} | ||
editText = remText; | ||
} | ||
|
||
tty_write_style_sequence(&buffer, ttystyle()); | ||
repl_output(dynstring_view(&buffer)); | ||
dynstring_destroy(&buffer); | ||
} | ||
|
||
static void repl_edit_render_cleanup() { | ||
Mem bufferMem = alloc_alloc(g_alloc_scratch, usize_kibibyte, 1); | ||
DynString buffer = dynstring_create_over(bufferMem); | ||
|
||
tty_write_clear_line_sequence(&buffer, TtyClearMode_All); | ||
tty_write_set_cursor_hor_sequence(&buffer, 0); | ||
tty_write_line_wrap_sequence(&buffer, true); // TODO: Only do this if it was originally enabled? | ||
|
||
repl_output(dynstring_view(&buffer)); | ||
dynstring_destroy(&buffer); | ||
} | ||
|
||
static bool repl_update(ReplState* state, TtyInputToken* input) { | ||
switch (input->type) { | ||
case TtyInputType_Interrupt: | ||
return false; // Stop. | ||
case TtyInputType_KeyEscape: | ||
repl_edit_clear(state); | ||
break; | ||
case TtyInputType_Text: | ||
repl_edit_insert(state, input->val_text); | ||
break; | ||
case TtyInputType_KeyBackspace: | ||
repl_edit_delete(state); | ||
break; | ||
case TtyInputType_KeyUp: | ||
repl_edit_prev(state); | ||
break; | ||
case TtyInputType_Accept: | ||
if (!repl_edit_empty(state)) { | ||
repl_edit_submit(state); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
repl_edit_render(state); | ||
return true; // Keep running. | ||
} | ||
|
||
static i32 repl_run_interactive() { | ||
if (!tty_isatty(g_file_stdin) || !tty_isatty(g_file_stdout)) { | ||
file_write_sync(g_file_stderr, string_lit("ERROR: REPL has to be ran interactively\n")); | ||
return 1; | ||
} | ||
|
||
DynString readBuffer = dynstring_create(g_alloc_heap, 32); | ||
DynString editBuffer = dynstring_create(g_alloc_heap, 128); | ||
|
||
ReplState state = { | ||
.editBuffer = &editBuffer, | ||
.scriptMem = script_mem_create(g_alloc_heap), | ||
}; | ||
|
||
tty_opts_set(g_file_stdin, TtyOpts_NoEcho | TtyOpts_NoBuffer | TtyOpts_NoSignals); | ||
repl_edit_render(&state); | ||
|
||
while (tty_read(g_file_stdin, &readBuffer, TtyReadFlags_None)) { | ||
String readStr = dynstring_view(&readBuffer); | ||
TtyInputToken input; | ||
for (;;) { | ||
readStr = tty_input_lex(readStr, &input); | ||
if (input.type == TtyInputType_End) { | ||
break; | ||
} | ||
if (!repl_update(&state, &input)) { | ||
goto Stop; | ||
} | ||
} | ||
dynstring_clear(&readBuffer); | ||
} | ||
|
||
Stop: | ||
repl_edit_render_cleanup(); | ||
tty_opts_set(g_file_stdin, TtyOpts_None); | ||
|
||
dynstring_destroy(&readBuffer); | ||
dynstring_destroy(&editBuffer); | ||
string_maybe_free(g_alloc_heap, state.editPrevText); | ||
script_mem_destroy(state.scriptMem); | ||
return 0; | ||
} | ||
|
||
static CliId g_helpFlag; | ||
|
||
void app_cli_configure(CliApp* app) { | ||
cli_app_register_desc(app, string_lit("Script ReadEvalPrintLoop utility.")); | ||
|
||
g_helpFlag = cli_register_flag(app, 'h', string_lit("help"), CliOptionFlags_None); | ||
cli_register_desc(app, g_helpFlag, string_lit("Display this help page.")); | ||
} | ||
|
||
i32 app_cli_run(const CliApp* app, const CliInvocation* invoc) { | ||
if (cli_parse_provided(invoc, g_helpFlag)) { | ||
cli_help_write_file(app, g_file_stdout); | ||
return 0; | ||
} | ||
return repl_run_interactive(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.