365 lines
11 KiB
C++
365 lines
11 KiB
C++
struct Lexer {
|
|
Allocator allocator;
|
|
char *at;
|
|
char *start;
|
|
char *end;
|
|
char *name;
|
|
int line, column;
|
|
};
|
|
|
|
enum TriggerKind {
|
|
TriggerKind_Error,
|
|
TriggerKind_Key,
|
|
TriggerKind_Mouse,
|
|
TriggerKind_Binary,
|
|
TriggerKind_CatchAll,
|
|
};
|
|
|
|
struct Trigger {
|
|
TriggerKind kind;
|
|
Trigger *left;
|
|
Trigger *right;
|
|
SDL_Keycode key;
|
|
struct {
|
|
EventKind event_kind : 8;
|
|
U32 ctrl : 1;
|
|
U32 alt : 1;
|
|
U32 shift : 1;
|
|
};
|
|
};
|
|
|
|
void Advance(Lexer *lex) {
|
|
if (lex->at < lex->end) {
|
|
if (lex->at[0] == '\n') {
|
|
lex->line += 1;
|
|
lex->column = 0;
|
|
} else {
|
|
lex->column += 1;
|
|
}
|
|
lex->at += 1;
|
|
}
|
|
}
|
|
|
|
void Advance(Lexer *lex, int n) {
|
|
for (int i = 0; i < n; i += 1) Advance(lex);
|
|
}
|
|
|
|
char At(Lexer *lex) {
|
|
if (lex->at < lex->end) {
|
|
return lex->at[0];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
String AsString(Lexer *lex) {
|
|
String result = {lex->at, lex->end - lex->at};
|
|
return result;
|
|
}
|
|
|
|
void EatWhitespace(Lexer *lex) {
|
|
while (At(lex) != '\n' && IsWhitespace(At(lex))) {
|
|
Advance(lex);
|
|
}
|
|
}
|
|
|
|
Trigger *TriggerBinary(Lexer *lex, Trigger *left, Trigger *right, int key) {
|
|
Trigger *result = AllocType(lex->allocator, Trigger);
|
|
result->kind = TriggerKind_Binary;
|
|
result->key = key;
|
|
result->left = left;
|
|
result->right = right;
|
|
return result;
|
|
}
|
|
|
|
Trigger *ParseKeyAtom(Lexer *lex) {
|
|
Trigger *result = AllocType(lex->allocator, Trigger);
|
|
result->kind = TriggerKind_Key;
|
|
EatWhitespace(lex);
|
|
for (;;) {
|
|
String lex_string = AsString(lex);
|
|
if (StartsWith(lex_string, "ctrl")) {
|
|
result->ctrl = true;
|
|
Advance(lex, 4);
|
|
} else if (StartsWith(lex_string, "shift")) {
|
|
result->shift = true;
|
|
Advance(lex, 5);
|
|
} else if (StartsWith(lex_string, "alt")) {
|
|
result->alt = true;
|
|
Advance(lex, 3);
|
|
} else if (IsAlphanumeric(At(lex))) {
|
|
char *start = lex->at;
|
|
while (IsAlphanumeric(At(lex))) {
|
|
Advance(lex);
|
|
}
|
|
String a = {start, lex->at - start};
|
|
Advance(lex);
|
|
|
|
bool found = false;
|
|
for (int i = 0; !found && i < Lengthof(MouseConversionTable); i += 1) {
|
|
if (a == MouseConversionTable[i].string) {
|
|
result->event_kind = MouseConversionTable[i].value;
|
|
result->kind = TriggerKind_Mouse;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; !found && i < Lengthof(SDLKeycodeConversionTable); i += 1) {
|
|
if (a == SDLKeycodeConversionTable[i].string) {
|
|
result->key = SDLKeycodeConversionTable[i].value;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
result->kind = TriggerKind_Error;
|
|
ReportErrorf("%s:%d:%d: Failed to parse key trigger, unexpected identifier: '%d'", lex->name, lex->line, lex->column, result->key);
|
|
return result;
|
|
}
|
|
} else {
|
|
result->kind = TriggerKind_Error;
|
|
ReportErrorf("%s:%d:%d: Failed to parse key trigger, unexpected character: '%c'", lex->name, lex->line, lex->column, At(lex));
|
|
return result;
|
|
}
|
|
|
|
if (At(lex) == '-') {
|
|
Advance(lex);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Trigger *ParseKeyChord(Lexer *lex) {
|
|
Trigger *left = ParseKeyAtom(lex);
|
|
EatWhitespace(lex);
|
|
while (IsAlphanumeric(At(lex))) {
|
|
left = TriggerBinary(lex, left, ParseKeyChord(lex), ' ');
|
|
EatWhitespace(lex);
|
|
}
|
|
return left;
|
|
}
|
|
|
|
Trigger *ParseKeyOr(Lexer *lex) {
|
|
Trigger *left = ParseKeyChord(lex);
|
|
EatWhitespace(lex);
|
|
while (At(lex) == '|') {
|
|
Advance(lex);
|
|
left = TriggerBinary(lex, left, ParseKeyOr(lex), '|');
|
|
EatWhitespace(lex);
|
|
}
|
|
return left;
|
|
}
|
|
|
|
Trigger *ParseKeyCatchAll(Lexer *lex) {
|
|
String string = AsString(lex);
|
|
if (string == "catch_all") {
|
|
Trigger *result = AllocType(lex->allocator, Trigger);
|
|
result->kind = TriggerKind_CatchAll;
|
|
return result;
|
|
} else {
|
|
return ParseKeyOr(lex);
|
|
}
|
|
}
|
|
|
|
Trigger *ParseKey(Allocator allocator, String key, char *debug_name) {
|
|
Lexer lex = {allocator, key.data, key.data, key.data + key.len, debug_name};
|
|
Trigger *result = ParseKeyCatchAll(&lex);
|
|
return result;
|
|
}
|
|
|
|
struct CachedTrigger {
|
|
Trigger *trigger;
|
|
String key;
|
|
};
|
|
Array<CachedTrigger> CachedTriggers;
|
|
|
|
Trigger *ParseKeyCached(String key) {
|
|
key = Trim(key);
|
|
if (key.len == 0) {
|
|
return NULL;
|
|
}
|
|
For (CachedTriggers) {
|
|
if (it.key == key) {
|
|
return it.trigger;
|
|
}
|
|
}
|
|
|
|
Allocator allocator = GetSystemAllocator();
|
|
Trigger *result = ParseKey(allocator, key, key.data);
|
|
if (!result) {
|
|
return NULL;
|
|
}
|
|
|
|
Add(&CachedTriggers, {result, key});
|
|
return result;
|
|
}
|
|
|
|
bool MatchEvent(Trigger *trigger, Event *event) {
|
|
if (trigger->kind == TriggerKind_Key) {
|
|
if (trigger->key == event->key && trigger->ctrl == event->ctrl && trigger->alt == event->alt && trigger->shift == event->shift) {
|
|
return true;
|
|
}
|
|
} else if (trigger->kind == TriggerKind_Mouse) {
|
|
if (trigger->event_kind == event->kind) {
|
|
return true;
|
|
}
|
|
} else if (trigger->kind == TriggerKind_Binary) {
|
|
if (trigger->key == ' ') {
|
|
return false;
|
|
} else if (trigger->key == '|') {
|
|
bool ok = MatchEvent(trigger->left, event);
|
|
if (ok) return ok;
|
|
ok = MatchEvent(trigger->right, event);
|
|
if (ok) return ok;
|
|
} ElseInvalidCodepath();
|
|
} else if (trigger->kind == TriggerKind_CatchAll) {
|
|
if (event->kind == EVENT_MOUSE_LEFT || event->kind == EVENT_MOUSE_RIGHT || event->kind == EVENT_MOUSE_MIDDLE || event->kind == EVENT_MOUSE_X1 || event->kind == EVENT_MOUSE_X2 || event->kind == EVENT_KEY_PRESS || event->kind == EVENT_TEXT_INPUT) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TestParser() {
|
|
Scratch scratch;
|
|
{
|
|
char *cmd = "ctrl-b";
|
|
Lexer base_lex = {scratch, cmd, cmd, cmd + strlen(cmd), "keybinding"};
|
|
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
|
Assert(trigger->kind == TriggerKind_Key);
|
|
Assert(trigger->key == SDLK_B);
|
|
Assert(trigger->ctrl);
|
|
Assert(trigger->shift == 0);
|
|
}
|
|
|
|
{
|
|
char *cmd = "ctrl-b shift-ctrl-a";
|
|
Lexer base_lex = {scratch, cmd, cmd, cmd + strlen(cmd), "keybinding"};
|
|
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
|
Assert(trigger->kind == TriggerKind_Binary);
|
|
Assert(trigger->key == ' ');
|
|
Assert(trigger->left->kind == TriggerKind_Key);
|
|
Assert(trigger->left->key == SDLK_B);
|
|
Assert(trigger->left->ctrl);
|
|
Assert(trigger->left->shift == 0);
|
|
Assert(trigger->right->kind == TriggerKind_Key);
|
|
Assert(trigger->right->key == SDLK_A);
|
|
Assert(trigger->right->ctrl);
|
|
Assert(trigger->right->shift);
|
|
}
|
|
|
|
{
|
|
char *cmd = "ctrl-b shift-ctrl-a | ctrl-c | ctrl-d";
|
|
Lexer base_lex = {scratch, cmd, cmd, cmd + strlen(cmd), "keybinding"};
|
|
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
|
Assert(trigger->kind == TriggerKind_Binary);
|
|
Assert(trigger->key == '|');
|
|
|
|
Assert(trigger->left->kind == TriggerKind_Binary);
|
|
Assert(trigger->left->key == ' ');
|
|
|
|
Assert(trigger->right->kind == TriggerKind_Binary);
|
|
Assert(trigger->right->key == '|');
|
|
|
|
|
|
Event event = {};
|
|
event.kind = EVENT_KEY_PRESS;
|
|
event.key = SDLK_D;
|
|
event.ctrl = 1;
|
|
bool ok = MatchEvent(trigger, &event);
|
|
Assert(ok);
|
|
|
|
event.key = SDLK_F1;
|
|
ok = MatchEvent(trigger, &event);
|
|
Assert(ok == 0);
|
|
|
|
event.ctrl = 1;
|
|
event.shift = 1;
|
|
event.key = SDLK_A;
|
|
ok = MatchEvent(trigger, &event);
|
|
Assert(!ok);
|
|
}
|
|
|
|
|
|
} RegisterFunction(&TestFunctions, TestParser);
|
|
|
|
|
|
void CMD_OpenConfig() {
|
|
Buffer *buffer = GetBuffer(GlobalConfigBufferID);
|
|
Open(buffer->name);
|
|
} RegisterCommand(CMD_OpenConfig, "", "Open the global config file");
|
|
|
|
void CMD_OpenConfigOptions() {
|
|
if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_OpenConfigOptions) {
|
|
NextActiveWindowID = PrimaryWindowID;
|
|
return;
|
|
}
|
|
ProfileFunction();
|
|
|
|
BSet command_bar = GetBSet(CommandWindowID);
|
|
command_bar.window->visible = true;
|
|
NextActiveWindowID = command_bar.window->id;
|
|
ResetBuffer(command_bar.buffer);
|
|
For (Variables) {
|
|
RawAppendf(command_bar.buffer, "\n:Set %-50S ", it.name);
|
|
switch(it.type) {
|
|
case VariableType_Color: RawAppendf(command_bar.buffer, "%x", it.color->value); break;
|
|
case VariableType_String: RawAppendf(command_bar.buffer, "'%S'", *it.string); break;
|
|
case VariableType_Int: RawAppendf(command_bar.buffer, "%lld", (long long)*it.i); break;
|
|
case VariableType_Float: RawAppendf(command_bar.buffer, "%f", *it.f); break;
|
|
default: InvalidCodepath();
|
|
}
|
|
}
|
|
For (CommandFunctions) {
|
|
RawAppendf(command_bar.buffer, "\n:Set %-50S '%S'", it.name, it.binding);
|
|
}
|
|
command_bar.view->update_scroll = true;
|
|
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
|
|
} RegisterCommand(CMD_OpenConfigOptions, "", "List available variables and associated documentation inside the command window");
|
|
|
|
void EvalCommandsLineByLine(BSet set) {
|
|
WindowID save_last = PrimaryWindowID;
|
|
WindowID save_active = ActiveWindowID;
|
|
WindowID save_next = NextActiveWindowID;
|
|
Caret save_caret = set.view->carets[0];
|
|
ActiveWindowID = set.window->id;
|
|
PrimaryWindowID = set.window->id;
|
|
NextActiveWindowID = set.window->id;
|
|
for (Int i = 0; i < set.buffer->line_starts.len; i += 1) {
|
|
Range range = GetLineRangeWithoutNL(set.buffer, i);
|
|
String16 string = GetString(set.buffer, range);
|
|
string = Trim(string);
|
|
if (string.len == 0) {
|
|
continue;
|
|
}
|
|
if (StartsWith(string, u"//")) {
|
|
continue;
|
|
}
|
|
Open(string);
|
|
}
|
|
set.view->carets[0] = save_caret;
|
|
PrimaryWindowID = save_last;
|
|
ActiveWindowID = save_active;
|
|
NextActiveWindowID = save_next;
|
|
}
|
|
|
|
void CMD_EvalCommandsLineByLine() {
|
|
BSet set = GetBSet(PrimaryWindowID);
|
|
EvalCommandsLineByLine(set);
|
|
} RegisterCommand(CMD_EvalCommandsLineByLine, "", "Goes line by line over a buffer and evaluates every line as a command, ignores empty or lines starting with '//'");
|
|
|
|
BufferID LoadConfig(String config_path) {
|
|
ReportConsolef("Loading config %S...", config_path);
|
|
Window *window = GetWindow(NullWindowID);
|
|
View *view = WindowOpenBufferView(window, config_path);
|
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
|
buffer->special = true;
|
|
EvalCommandsLineByLine({window, view, buffer});
|
|
if (window->active_view == view->id) {
|
|
window->active_view = NullViewID;
|
|
}
|
|
return buffer->id;
|
|
} |