From 94852f59c35d9dba0f2fde2d95f8b484997c3007 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Thu, 29 Jan 2026 15:02:42 +0100 Subject: [PATCH 001/113] Fix compilation (bad conflict resolution) --- right/src/usb_report_updater.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/usb_report_updater.c b/right/src/usb_report_updater.c index d198d2d96..24e94ba67 100644 --- a/right/src/usb_report_updater.c +++ b/right/src/usb_report_updater.c @@ -469,13 +469,13 @@ void ApplyKeyAction(key_state_t *keyState, key_action_cached_t *cachedAction, ke case KeyActionType_PlayMacro: if (KeyState_ActivatedNow(keyState)) { resetStickyMods(cachedAction); - Macros_StartMacro(action->playMacro.macroId, keyState, action->playMacro.offset, keyState->activationTimestamp, 255, true); + Macros_StartMacro(action->playMacro.macroId, keyState, action->playMacro.offset, keyState->activationTimestamp, 255, true, NULL); } break; case KeyActionType_InlineMacro: if (KeyState_ActivatedNow(keyState)) { resetStickyMods(cachedAction); - Macros_StartInlineMacro(action->inlineMacro.text, keyState, keyState->timestamp); + Macros_StartInlineMacro(action->inlineMacro.text, keyState, keyState->activationTimestamp); } break; case KeyActionType_Connections: From 1a608186a2bcc910c4daa11ecef08e79d0d6676f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Feb 2026 14:49:28 +0100 Subject: [PATCH 002/113] add macroArg command (ignored for now) --- right/src/macros/command_ids.h | 1 + right/src/macros/commands.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/right/src/macros/command_ids.h b/right/src/macros/command_ids.h index 038eb7cc5..37ef03908 100644 --- a/right/src/macros/command_ids.h +++ b/right/src/macros/command_ids.h @@ -107,6 +107,7 @@ typedef enum { CommandId_ifRegLt, // deprecated // 'm' commands + CommandId_macroArg, CommandId_mulReg, // deprecated // 'n' commands diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 7fbf5ea70..a356a1063 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -945,6 +945,19 @@ static macro_result_t processPlayMacroCommand(parser_context_t* ctx) return res ? MacroResult_Blocking : MacroResult_Finished; } +static macro_result_t processMacroArgCommand(uint32_t time) +{ + // ignore macroArg command for now, eat rest of line + + uint16_t stringOffset = 0; + uint16_t textIndex = 0; + uint16_t textSubIndex = 0; + + while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; + + return MacroResult_Finished; +} + static macro_result_t processWriteCommand(parser_context_t* ctx) { if (Macros_DryRun) { @@ -2229,6 +2242,8 @@ static macro_result_t processCommand(parser_context_t* ctx) return MacroResult_Finished; // 'm' commands + case CommandId_macroArg: + return processMacroArgCommand(ctx); case CommandId_mulReg: Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName*2)`."); return MacroResult_Finished; From befb7dc124b771007dd208a9571bb1a7aeb9e617 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 08:29:26 +0100 Subject: [PATCH 003/113] fix params --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index a356a1063..f1913284a 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -945,7 +945,7 @@ static macro_result_t processPlayMacroCommand(parser_context_t* ctx) return res ? MacroResult_Blocking : MacroResult_Finished; } -static macro_result_t processMacroArgCommand(uint32_t time) +static macro_result_t processMacroArgCommand(parser_context_t* ctx) { // ignore macroArg command for now, eat rest of line From 79308c119b849e2347072f7928425bbbc692b090 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 09:05:42 +0100 Subject: [PATCH 004/113] update command hash table --- right/src/macros/command_hash.gperf | 1 + 1 file changed, 1 insertion(+) diff --git a/right/src/macros/command_hash.gperf b/right/src/macros/command_hash.gperf index 9148cbb5c..3c58a722d 100644 --- a/right/src/macros/command_hash.gperf +++ b/right/src/macros/command_hash.gperf @@ -88,6 +88,7 @@ ifRegEq, CommandId_ifRegEq ifNotRegEq, CommandId_ifNotRegEq ifRegGt, CommandId_ifRegGt ifRegLt, CommandId_ifRegLt +macroArg, CommandId_macroArg mulReg, CommandId_mulReg noOp, CommandId_noOp notify, CommandId_notify From 7c89196cc7098a0399265467b6c5b9f4ba2aeb63 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 14:49:52 +0100 Subject: [PATCH 005/113] attempt at macroArg parser --- right/src/macros/commands.c | 57 +++++++++++++++++++++++++++++++++++-- right/src/macros/typedefs.h | 11 ++++++- right/src/str_utils.c | 5 ++++ right/src/str_utils.h | 2 +- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index f1913284a..e82ea25a2 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -947,15 +947,68 @@ static macro_result_t processPlayMacroCommand(parser_context_t* ctx) static macro_result_t processMacroArgCommand(parser_context_t* ctx) { - // ignore macroArg command for now, eat rest of line - uint16_t stringOffset = 0; uint16_t textIndex = 0; uint16_t textSubIndex = 0; + if (Macros_DryRun) { + // parse macroArg command but ignore it for now + + while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; + + return MacroResult_Finished; + } + + // parse the argument name (identifier) + const char *idStart = ctx->at; + const char *idEnd = IdentifierEnd(ctx->at); + + if (idEnd == idStart) { + Macros_ReportErrorTok(ctx, "Expected identifier"); + return MacroResult_Finished; + } + ctx->at = idEnd; + + // see if the argument has a type + macro_arg_type_t argType; + + if (*ctx->at == ':') { + ctx->at++; + const char *typeStart = ctx->at; + + if (ConsumeToken(ctx, "int")) { + argType = MacroArgType_Int; + } + else if (ConsumeToken(ctx, "float")) { + argType = MacroArgType_Float; + } + else if (ConsumeToken(ctx, "string")) { + argType = MacroArgType_String; + } + else if (ConsumeToken(ctx, "keyid")) { + argType = MacroArgType_KeyId; + } + else if (ConsumeToken(ctx, "scancode")) { + argType = MacroArgType_ScanCode; + } + else if (ConsumeToken(ctx, "any")) { + argType = MacroArgType_Any; + } + else { + Macros_ReportErrorPos(ctx, "Unrecognized macroArg argument type:"); + } + } + else { + argType = MacroArgType_Any; + ConsumeWhite(ctx); + } + + // rest of command is descriptive label, ignored by firmware while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; return MacroResult_Finished; + +// Macros_ReportErrorPrintf(ctx->at, "Parsing failed at '%s'?", OneWord(ctx)); } static macro_result_t processWriteCommand(parser_context_t* ctx) diff --git a/right/src/macros/typedefs.h b/right/src/macros/typedefs.h index 67854c16f..3fdad13fd 100644 --- a/right/src/macros/typedefs.h +++ b/right/src/macros/typedefs.h @@ -14,7 +14,6 @@ // Typedefs: - typedef enum { MacroSubAction_Tap, MacroSubAction_Press, @@ -48,6 +47,16 @@ uint8_t inputModifierMask; } macro_usb_keyboard_reports_t; + typedef enum { + MacroArgType_Any, + MacroArgType_Int, + MacroArgType_Float, + MacroArgType_Bool, + MacroArgType_String, + MacroArgType_KeyId, + MacroArgType_ScanCode + } macro_arg_type_t; + // Variables: // Functions: diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 0f5ccf0b4..f59e1a098 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -319,6 +319,11 @@ void ConsumeAnyIdentifier(parser_context_t* ctx) consumeWhite(ctx); } +void ConsumeIdentifier(parser_context_t* ctx) +{ + ctx->at = IdentifierEnd(ctx); +} + void ConsumeUntilDot(parser_context_t* ctx) { while(*ctx->at > 32 && *ctx->at != '.' && !isEnd(ctx)) { diff --git a/right/src/str_utils.h b/right/src/str_utils.h index bc72fb3e1..ac3e4f4cf 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -39,7 +39,7 @@ const char* at; const char* end; uint8_t nestingLevel; - uint8_t nestingBound; // This context can't be popped bellow this bound, because it is a copy. + uint8_t nestingBound; // This context can't be popped below this bound, because it is a copy. } parser_context_t; typedef struct { From aeab45cdba686d06d496a955814e4487d027b8ca Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 15:19:28 +0100 Subject: [PATCH 006/113] fix? debug? --- right/src/macros/commands.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index e82ea25a2..dc6e2fbad 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -951,6 +951,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) uint16_t textIndex = 0; uint16_t textSubIndex = 0; +/* if (Macros_DryRun) { // parse macroArg command but ignore it for now @@ -958,7 +959,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) return MacroResult_Finished; } - + */ // parse the argument name (identifier) const char *idStart = ctx->at; const char *idEnd = IdentifierEnd(ctx->at); @@ -969,6 +970,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } ctx->at = idEnd; +/* // see if the argument has a type macro_arg_type_t argType; @@ -996,13 +998,14 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } else { Macros_ReportErrorPos(ctx, "Unrecognized macroArg argument type:"); + return MacroResult_Finished; } } else { argType = MacroArgType_Any; ConsumeWhite(ctx); } - + */ // rest of command is descriptive label, ignored by firmware while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; From 1a8800db00afc57139138b2a3ec92b5a1dee658d Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 15:25:55 +0100 Subject: [PATCH 007/113] should have read compiler warnings that already told me where I was wrong --- right/src/macros/commands.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index dc6e2fbad..6bddb1672 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -951,7 +951,6 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) uint16_t textIndex = 0; uint16_t textSubIndex = 0; -/* if (Macros_DryRun) { // parse macroArg command but ignore it for now @@ -959,10 +958,9 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) return MacroResult_Finished; } - */ // parse the argument name (identifier) const char *idStart = ctx->at; - const char *idEnd = IdentifierEnd(ctx->at); + const char *idEnd = IdentifierEnd(ctx); if (idEnd == idStart) { Macros_ReportErrorTok(ctx, "Expected identifier"); @@ -970,7 +968,6 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } ctx->at = idEnd; -/* // see if the argument has a type macro_arg_type_t argType; @@ -1005,7 +1002,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) argType = MacroArgType_Any; ConsumeWhite(ctx); } - */ + // rest of command is descriptive label, ignored by firmware while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; From 4a944e1c9066202c6195429c6319a5449293c028 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 15:55:53 +0100 Subject: [PATCH 008/113] improved error messages --- right/src/macros/commands.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 6bddb1672..4ba525c13 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -963,7 +963,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) const char *idEnd = IdentifierEnd(ctx); if (idEnd == idStart) { - Macros_ReportErrorTok(ctx, "Expected identifier"); + Macros_ReportErrorPos(ctx, "Expected identifier"); return MacroResult_Finished; } ctx->at = idEnd; @@ -994,7 +994,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) argType = MacroArgType_Any; } else { - Macros_ReportErrorPos(ctx, "Unrecognized macroArg argument type:"); + Macros_ReportErrorTok(ctx, "Unrecognized macroArg argument type: "); return MacroResult_Finished; } } From 20d37d352bbd82e89c7896ea75ec243985783b45 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Feb 2026 17:06:28 +0100 Subject: [PATCH 009/113] message beautification --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 4ba525c13..8721e8aa5 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -994,7 +994,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) argType = MacroArgType_Any; } else { - Macros_ReportErrorTok(ctx, "Unrecognized macroArg argument type: "); + Macros_ReportErrorTok(ctx, "Unrecognized macroArg argument type:"); return MacroResult_Finished; } } From e2735e57c3727aaa73198e58ecbe758ba74cfdeb Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Tue, 3 Feb 2026 17:51:28 +0100 Subject: [PATCH 010/113] Fix gperf token vs argument expansion bug. --- right/src/macros/commands.c | 17 +++++------------ right/src/str_utils.c | 13 +++++++++++++ right/src/str_utils.h | 1 + 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 7fbf5ea70..3c811bbd7 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2015,29 +2015,22 @@ static macro_result_t processCommand(parser_context_t* ctx) const char* cmdTokEnd = TokEnd(ctx->at, ctx->end); if (cmdTokEnd > ctx->at && cmdTokEnd[-1] == ':') { //skip labels - ctx->at = NextTok(ctx->at, ctx->end); - if (ctx->at == ctx->end) { + ConsumeAnyToken(ctx); + if (ctx->at == ctx->end && IsEnd(ctx)) { return MacroResult_Finished; } } while(ctx->at < ctx->end || !IsEnd(ctx)) { - // Get the current token for hash lookup - const char* tokStart = ctx->at; - const char* tokEnd = TokEnd(tokStart, ctx->end); - size_t tokLen = tokEnd - tokStart; - // Look up the command in the hash table - const struct command_entry* entry = command_lookup(tokStart, tokLen); + const char* cmdAt = ctx->at; + const struct command_entry* entry = ConsumeGperfToken(ctx); if (entry == NULL) { - Macros_ReportErrorTok(ctx, "Unrecognized command:"); + Macros_ReportError("Unrecognized command:", cmdAt, TokEnd(cmdAt, ctx->end)); return MacroResult_Finished; } - // Consume the token - ctx->at = NextTok(tokStart, ctx->end); - // Dispatch based on command ID switch (entry->id) { // 'a' commands diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 48213b282..d8e11a8a6 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -6,6 +6,7 @@ #include "module.h" #include "slave_protocol.h" #include "macros/vars.h" +#include "macros/command_hash.h" #include "trace.h" #if !defined(MIN) @@ -351,6 +352,7 @@ const char* TokEnd(const char* cmd, const char *cmdEnd) return cmd; } +// This doesn't handle expansions. Don't use it in actual macro context. const char* NextTok(const char* cmd, const char *cmdEnd) { while(*cmd > 32 && cmd < cmdEnd) { @@ -373,6 +375,17 @@ void ConsumeAnyToken(parser_context_t* ctx) consumeWhite(ctx); } +struct command_entry* ConsumeGperfToken(parser_context_t* ctx) +{ + const char* start = ctx->at; + while (*ctx->at > 32 && ctx->at < ctx->end) { + ctx->at++; + } + struct command_entry* result = command_lookup(start, ctx->at - start); + consumeWhite(ctx); + return result; +} + const char* NextCmd(const char* cmd, const char *cmdEnd) { while(*cmd != '\n' && *cmd != '\r' && cmd < cmdEnd) { diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 82614f432..f7a62291e 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -63,6 +63,7 @@ const char* FindChar(char c, const char* str, const char* strEnd); bool ConsumeToken(parser_context_t* ctx, const char *b); void ConsumeAnyToken(parser_context_t* ctx); + struct command_entry* ConsumeGperfToken(parser_context_t* ctx); void ConsumeCommentsAsWhite(bool consume); bool ConsumeTokenByRef(parser_context_t* ctx, string_ref_t ref); bool ConsumeIdentifierByRef(parser_context_t* ctx, string_ref_t ref); From 402af5196dbbff6b6a0b9cbe7625dfb47737328c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Feb 2026 17:47:15 +0100 Subject: [PATCH 011/113] too memory intensive implementation of macro arguments storage, needs refactoring --- right/src/macros/core.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 5be31bfa3..ff1d5a2f8 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -21,6 +21,7 @@ #define MACRO_HISTORY_POOL_SIZE 16 #define MACRO_SCOPE_STATE_POOL_SIZE (MACRO_STATE_POOL_SIZE*2) #define MAX_REG_COUNT 32 + #define MAX_MACRO_ARGUMENT_SIZE 8 #define ALTMASK (HID_KEYBOARD_MODIFIER_LEFTALT | HID_KEYBOARD_MODIFIER_RIGHTALT) #define CTRLMASK (HID_KEYBOARD_MODIFIER_LEFTCTRL | HID_KEYBOARD_MODIFIER_RIGHTCTRL) @@ -135,6 +136,11 @@ uint8_t macroIndex; } macro_history_t; + typedef struct { + string_ref_t id; + macro_arg_type_t type; + } ATTR_PACKED macro_arg_t; + struct macro_state_t { // local scope data macro_scope_state_t *ls; @@ -167,6 +173,7 @@ bool autoRepeatInitialDelayPassed: 1; macro_autorepeat_state_t autoRepeatPhase: 1; // ---- 4-aligned ---- + macro_arg_t arguments[MAX_MACRO_ARGUMENT_SIZE]; macro_usb_keyboard_reports_t reports; } ms; From 877f3b4733ffe4a87bd5378ffd7dc5a0cf1fe415 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 5 Feb 2026 19:09:36 +0100 Subject: [PATCH 012/113] allow macroArg command only at start of macro --- right/src/macros/commands.c | 779 ++++++++++++++++++------------------ right/src/macros/core.c | 1 - right/src/macros/core.h | 2 + 3 files changed, 401 insertions(+), 381 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 05b5cdce8..6f088c5ef 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -951,6 +951,11 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) uint16_t textIndex = 0; uint16_t textSubIndex = 0; + if (S->ms.macroHeadersProcessed) { + Macros_ReportErrorPos(ctx, "macroArg commands must be placed before any other commands in the macro"); + return MacroResult_Finished; + } + if (Macros_DryRun) { // parse macroArg command but ignore it for now @@ -2076,6 +2081,393 @@ static macro_result_t processZephyrCommand(parser_context_t* ctx) { } \ break; +static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t commandId, bool *headersDone) { + // Dispatch based on command ID + switch (commandId) { + // 'a' commands + case CommandId_activateKeyPostponed: + return processActivateKeyPostponedCommand(ctx); + case CommandId_autoRepeat: + return processAutoRepeatCommand(ctx); + case CommandId_addReg: + Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName+1)`."); + return MacroResult_Finished; + + // 'b' commands + case CommandId_break: + return processBreakCommand(ctx); + case CommandId_bluetooth: + return processBluetoothCommand(ctx); + + // 'c' commands + case CommandId_consumePending: + return processConsumePendingCommand(ctx); + case CommandId_clearStatus: + return Macros_ProcessClearStatusCommand(true); + case CommandId_call: + return processCallCommand(ctx); + + // 'd' commands + case CommandId_delayUntilRelease: + return processDelayUntilReleaseCommand(); + case CommandId_delayUntilReleaseMax: + return processDelayUntilReleaseMaxCommand(ctx); + case CommandId_delayUntil: + return processDelayUntilCommand(ctx); + case CommandId_diagnose: + return Macros_ProcessDiagnoseCommand(); + + // 'e' commands + case CommandId_exec: + return processExecCommand(ctx); + case CommandId_else: + if (!Macros_DryRun && S->ls->ms.lastIfSucceeded) { + return MacroResult_Finished; + } + break; + case CommandId_exit: + return processExitCommand(ctx); + + // 'f' commands + case CommandId_final: + return processFinalCommand(ctx); + case CommandId_fork: + return processForkCommand(ctx); + case CommandId_freeze: + return processFreezeCommand(ctx); + + // 'g' commands + case CommandId_goTo: + return processGoToCommand(ctx); + + // 'h' commands + case CommandId_holdLayer: + return processHoldLayerCommand(ctx); + case CommandId_holdLayerMax: + return processHoldLayerMaxCommand(ctx); + case CommandId_holdKeymapLayer: + return processHoldKeymapLayerCommand(ctx); + case CommandId_holdKeymapLayerMax: + return processHoldKeymapLayerMaxCommand(ctx); + case CommandId_holdKey: + return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Hold, &S->ms.reports); + + // 'i' commands - conditionals + case CommandId_if: + PROCESS_CONDITION(processIfCommand(ctx)) + case CommandId_ifDoubletap: + PROCESS_CONDITION(processIfDoubletapCommand(false)) + case CommandId_ifNotDoubletap: + PROCESS_CONDITION(processIfDoubletapCommand(true)) + case CommandId_ifInterrupted: + PROCESS_CONDITION(processIfInterruptedCommand(false)) + case CommandId_ifNotInterrupted: + PROCESS_CONDITION(processIfInterruptedCommand(true)) + case CommandId_ifReleased: + PROCESS_CONDITION(processIfReleasedCommand(false)) + case CommandId_ifNotReleased: + PROCESS_CONDITION(processIfReleasedCommand(true)) + case CommandId_ifKeymap: + PROCESS_CONDITION(processIfKeymapCommand(ctx, false)) + case CommandId_ifNotKeymap: + PROCESS_CONDITION(processIfKeymapCommand(ctx, true)) + case CommandId_ifLayer: + PROCESS_CONDITION(processIfLayerCommand(ctx, false)) + case CommandId_ifNotLayer: + PROCESS_CONDITION(processIfLayerCommand(ctx, true)) + case CommandId_ifLayerToggled: + PROCESS_CONDITION(processIfLayerToggledCommand(ctx, false)) + case CommandId_ifNotLayerToggled: + PROCESS_CONDITION(processIfLayerToggledCommand(ctx, true)) + case CommandId_ifPlaytime: + PROCESS_CONDITION(processIfPlaytimeCommand(ctx, false)) + case CommandId_ifNotPlaytime: + PROCESS_CONDITION(processIfPlaytimeCommand(ctx, true)) + case CommandId_ifAnyMod: + PROCESS_CONDITION(processIfModifierCommand(false, 0xFF)) + case CommandId_ifNotAnyMod: + PROCESS_CONDITION(processIfModifierCommand(true, 0xFF)) + case CommandId_ifShift: + PROCESS_CONDITION(processIfModifierCommand(false, SHIFTMASK)) + case CommandId_ifNotShift: + PROCESS_CONDITION(processIfModifierCommand(true, SHIFTMASK)) + case CommandId_ifCtrl: + PROCESS_CONDITION(processIfModifierCommand(false, CTRLMASK)) + case CommandId_ifNotCtrl: + PROCESS_CONDITION(processIfModifierCommand(true, CTRLMASK)) + case CommandId_ifAlt: + PROCESS_CONDITION(processIfModifierCommand(false, ALTMASK)) + case CommandId_ifNotAlt: + PROCESS_CONDITION(processIfModifierCommand(true, ALTMASK)) + case CommandId_ifGui: + PROCESS_CONDITION(processIfModifierCommand(false, GUIMASK)) + case CommandId_ifNotGui: + PROCESS_CONDITION(processIfModifierCommand(true, GUIMASK)) + case CommandId_ifCapsLockOn: + PROCESS_CONDITION(processIfStateKeyCommand(false, &UsbBasicKeyboard_CapsLockOn)) + case CommandId_ifNotCapsLockOn: + PROCESS_CONDITION(processIfStateKeyCommand(true, &UsbBasicKeyboard_CapsLockOn)) + case CommandId_ifNumLockOn: + PROCESS_CONDITION(processIfStateKeyCommand(false, &UsbBasicKeyboard_NumLockOn)) + case CommandId_ifNotNumLockOn: + PROCESS_CONDITION(processIfStateKeyCommand(true, &UsbBasicKeyboard_NumLockOn)) + case CommandId_ifScrollLockOn: + PROCESS_CONDITION(processIfStateKeyCommand(false, &UsbBasicKeyboard_ScrollLockOn)) + case CommandId_ifNotScrollLockOn: + PROCESS_CONDITION(processIfStateKeyCommand(true, &UsbBasicKeyboard_ScrollLockOn)) + case CommandId_ifRecording: + PROCESS_CONDITION(processIfRecordingCommand(false)) + case CommandId_ifNotRecording: + PROCESS_CONDITION(processIfRecordingCommand(true)) + case CommandId_ifRecordingId: + PROCESS_CONDITION(processIfRecordingIdCommand(ctx, false)) + case CommandId_ifNotRecordingId: + PROCESS_CONDITION(processIfRecordingIdCommand(ctx, true)) + case CommandId_ifNotPending: + PROCESS_CONDITION(processIfPendingCommand(ctx, true)) + case CommandId_ifPending: + PROCESS_CONDITION(processIfPendingCommand(ctx, false)) + case CommandId_ifKeyPendingAt: + PROCESS_CONDITION(processIfKeyPendingAtCommand(ctx, false)) + case CommandId_ifNotKeyPendingAt: + PROCESS_CONDITION(processIfKeyPendingAtCommand(ctx, true)) + case CommandId_ifKeyActive: + PROCESS_CONDITION(processIfKeyActiveCommand(ctx, false)) + case CommandId_ifNotKeyActive: + PROCESS_CONDITION(processIfKeyActiveCommand(ctx, true)) + case CommandId_ifPendingKeyReleased: + PROCESS_CONDITION(processIfPendingKeyReleasedCommand(ctx, false)) + case CommandId_ifNotPendingKeyReleased: + PROCESS_CONDITION(processIfPendingKeyReleasedCommand(ctx, true)) + case CommandId_ifKeyDefined: + PROCESS_CONDITION(processIfKeyDefinedCommand(ctx, false)) + case CommandId_ifNotKeyDefined: + PROCESS_CONDITION(processIfKeyDefinedCommand(ctx, true)) + case CommandId_ifModuleConnected: + PROCESS_CONDITION(processIfModuleConnected(ctx, false)) + case CommandId_ifNotModuleConnected: + PROCESS_CONDITION(processIfModuleConnected(ctx, true)) + case CommandId_ifHold: + return processIfHoldCommand(ctx, false); + case CommandId_ifTap: + return processIfHoldCommand(ctx, true); + case CommandId_ifSecondary: + return processIfSecondaryCommand(ctx, false); + case CommandId_ifPrimary: + return processIfSecondaryCommand(ctx, true); + case CommandId_ifShortcut: + return processIfShortcutCommand(ctx, false, true); + case CommandId_ifNotShortcut: + return processIfShortcutCommand(ctx, true, true); + case CommandId_ifGesture: + return processIfShortcutCommand(ctx, false, false); + case CommandId_ifNotGesture: + return processIfShortcutCommand(ctx, true, false); + case CommandId_ifRegEq: + case CommandId_ifNotRegEq: + Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `if ($varName == 1)`."); + return MacroResult_Finished; + case CommandId_ifRegGt: + case CommandId_ifRegLt: + Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `if ($varName >= 1)`."); + return MacroResult_Finished; + + // 'm' commands + case CommandId_macroArg: + *headersFinished = false; // this is a valid header command, stay in header mode + return processMacroArgCommand(ctx); + case CommandId_mulReg: + Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName*2)`."); + return MacroResult_Finished; + + // 'n' commands + case CommandId_noOp: + return processNoOpCommand(); + case CommandId_notify: + return Macros_ProcessNotifyCommand(ctx); + + // 'o' commands + case CommandId_oneShot: + return processOneShotCommand(ctx); + case CommandId_overlayLayer: + return processOverlayLayerCommand(ctx); + case CommandId_overlayKeymap: + return processOverlayKeymapCommand(ctx); + + // 'p' commands + case CommandId_printStatus: + return Macros_ProcessPrintStatusCommand(); + case CommandId_playMacro: + return processPlayMacroCommand(ctx); + case CommandId_pressKey: + return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Press, &S->ms.reports); + case CommandId_postponeKeys: + processPostponeKeysCommand(); + break; + case CommandId_postponeNext: + return processPostponeNextNCommand(ctx); + case CommandId_progressHue: + return processProgressHueCommand(); + case CommandId_powerMode: + return processPowerModeCommand(ctx); + case CommandId_panic: + return processPanicCommand(ctx); + + // 'r' commands + case CommandId_recordMacro: + return processRecordMacroCommand(ctx, false); + case CommandId_recordMacroBlind: + return processRecordMacroCommand(ctx, true); + case CommandId_recordMacroDelay: + return processRecordMacroDelayCommand(); + case CommandId_resolveNextKeyId: + return processResolveNextKeyIdCommand(); + case CommandId_releaseKey: + return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Release, &S->ms.reports); + case CommandId_repeatFor: + return processRepeatForCommand(ctx); + case CommandId_resetTrackpoint: + return processResetTrackpointCommand(); + case CommandId_replaceLayer: + return processReplaceLayerCommand(ctx); + case CommandId_replaceKeymap: + return processReplaceKeymapCommand(ctx); + case CommandId_resolveNextKeyEq: + Macros_ReportErrorPos(ctx, "Command deprecated. Please, replace resolveNextKeyEq by ifShortcut or ifGesture, or complain at github that you actually need this."); + return MacroResult_Finished; + case CommandId_resolveSecondary: + Macros_ReportErrorPos(ctx, "Command deprecated. Please, replace resolveSecondary by `ifPrimary advancedStrategy goTo ...` or `ifSecondary advancedStrategy goTo ...`."); + return MacroResult_Finished; + case CommandId_resetConfiguration: + return processResetConfigurationCommand(ctx); + case CommandId_reboot: + return processRebootCommand(); + case CommandId_reconnect: + return processReconnectCommand(); + + // 's' commands + case CommandId_set: + return Macro_ProcessSetCommand(ctx); + case CommandId_setVar: + return Macros_ProcessSetVarCommand(ctx); + case CommandId_setStatus: + return Macros_ProcessSetStatusCommand(ctx, true); + case CommandId_startRecording: + return processStartRecordingCommand(ctx, false); + case CommandId_startRecordingBlind: + return processStartRecordingCommand(ctx, true); + case CommandId_setLedTxt: + return Macros_ProcessSetLedTxtCommand(ctx); + case CommandId_statsRuntime: + return Macros_ProcessStatsRuntimeCommand(); + case CommandId_statsRecordKeyTiming: + return Macros_ProcessStatsRecordKeyTimingCommand(); + case CommandId_statsLayerStack: + return Macros_ProcessStatsLayerStackCommand(); + case CommandId_statsActiveKeys: + return Macros_ProcessStatsActiveKeysCommand(); + case CommandId_statsActiveMacros: + return Macros_ProcessStatsActiveMacrosCommand(); + case CommandId_statsPostponerStack: + return Macros_ProcessStatsPostponerStackCommand(); + case CommandId_statsVariables: + return Macros_ProcessStatsVariablesCommand(); + case CommandId_statsBattery: + return Macros_ProcessStatsBatteryCommand(); + case CommandId_switchKeymap: + return processSwitchKeymapCommand(ctx); + case CommandId_startMouse: + return processMouseCommand(ctx, true); + case CommandId_stopMouse: + return processMouseCommand(ctx, false); + case CommandId_stopRecording: + case CommandId_stopRecordingBlind: + return processStopRecordingCommand(); + case CommandId_stopAllMacros: + return processStopAllMacrosCommand(); + case CommandId_suppressMods: + processSuppressModsCommand(); + break; + case CommandId_setReg: + Macros_ReportErrorPos(ctx, "Command was removed, please use named variables. E.g., `setVar myVar 1` and `write \"$myVar\"`"); + return MacroResult_Finished; + case CommandId_subReg: + Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName+1)`."); + return MacroResult_Finished; + case CommandId_setStatusPart: + Macros_ReportErrorPos(ctx, "Command was removed, please use string interpolated setStatus."); + return MacroResult_Finished; + case CommandId_switchKeymapLayer: + case CommandId_switchLayer: + Macros_ReportErrorPos(ctx, "Command deprecated. Please, replace switchKeymapLayer by toggleKeymapLayer or holdKeymapLayer. Or complain on github that you actually need this command."); + return MacroResult_Finished; + case CommandId_switchHost: + return processSwitchHostCommand(ctx); + + // 't' commands + case CommandId_toggleKeymapLayer: + return processToggleKeymapLayerCommand(ctx); + case CommandId_toggleLayer: + return processToggleLayerCommand(ctx); + case CommandId_tapKey: + return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Tap, &S->ms.reports); + case CommandId_tapKeySeq: + return Macros_ProcessTapKeySeqCommand(ctx); + case CommandId_toggleKey: + return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Toggle, &S->ms.reports); + case CommandId_trackpoint: + return processTrackpointCommand(ctx); + case CommandId_trace: + if (!Macros_DryRun) { + Trace_Print(LogTarget_ErrorBuffer, "Triggered by macro command"); + } + return MacroResult_Finished; + case CommandId_testLeakage: + return processTestLeakageCommand(ctx); + case CommandId_testSuite: + return processTestSuiteCommand(ctx); + + // 'u' commands + case CommandId_unToggleLayer: + case CommandId_untoggleLayer: + return processUnToggleLayerCommand(); + case CommandId_unpairHost: + return Macros_ProcessUnpairHostCommand(ctx); + + // 'v' commands + case CommandId_validateUserConfig: + case CommandId_validateMacros: + return processValidateMacrosCommand(ctx); + + // 'w' commands + case CommandId_write: + return processWriteCommand(ctx); + case CommandId_while: + return processWhileCommand(ctx); + case CommandId_writeExpr: + Macros_ReportErrorPos(ctx, "writeExpr is now deprecated, please migrate to interpolated strings"); + return MacroResult_Finished; + + // 'y' commands + case CommandId_yield: + return processYieldCommand(ctx); + + // 'z' commands + case CommandId_zephyr: + return processZephyrCommand(ctx); + + // brace commands + case CommandId_openBrace: + return processOpeningBraceCommand(ctx); + case CommandId_closeBrace: + return processClosingBraceCommand(ctx); + + default: + Macros_ReportErrorTok(ctx, "Unrecognized command:"); + return MacroResult_Finished; + } +} + static macro_result_t processCommand(parser_context_t* ctx) { const char* cmdTokEnd = TokEnd(ctx->at, ctx->end); @@ -2097,390 +2489,17 @@ static macro_result_t processCommand(parser_context_t* ctx) return MacroResult_Finished; } - // Dispatch based on command ID - switch (entry->id) { - // 'a' commands - case CommandId_activateKeyPostponed: - return processActivateKeyPostponedCommand(ctx); - case CommandId_autoRepeat: - return processAutoRepeatCommand(ctx); - case CommandId_addReg: - Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName+1)`."); - return MacroResult_Finished; - - // 'b' commands - case CommandId_break: - return processBreakCommand(ctx); - case CommandId_bluetooth: - return processBluetoothCommand(ctx); - - // 'c' commands - case CommandId_consumePending: - return processConsumePendingCommand(ctx); - case CommandId_clearStatus: - return Macros_ProcessClearStatusCommand(true); - case CommandId_call: - return processCallCommand(ctx); - - // 'd' commands - case CommandId_delayUntilRelease: - return processDelayUntilReleaseCommand(); - case CommandId_delayUntilReleaseMax: - return processDelayUntilReleaseMaxCommand(ctx); - case CommandId_delayUntil: - return processDelayUntilCommand(ctx); - case CommandId_diagnose: - return Macros_ProcessDiagnoseCommand(); - - // 'e' commands - case CommandId_exec: - return processExecCommand(ctx); - case CommandId_else: - if (!Macros_DryRun && S->ls->ms.lastIfSucceeded) { - return MacroResult_Finished; - } - break; - case CommandId_exit: - return processExitCommand(ctx); - - // 'f' commands - case CommandId_final: - return processFinalCommand(ctx); - case CommandId_fork: - return processForkCommand(ctx); - case CommandId_freeze: - return processFreezeCommand(ctx); - - // 'g' commands - case CommandId_goTo: - return processGoToCommand(ctx); - - // 'h' commands - case CommandId_holdLayer: - return processHoldLayerCommand(ctx); - case CommandId_holdLayerMax: - return processHoldLayerMaxCommand(ctx); - case CommandId_holdKeymapLayer: - return processHoldKeymapLayerCommand(ctx); - case CommandId_holdKeymapLayerMax: - return processHoldKeymapLayerMaxCommand(ctx); - case CommandId_holdKey: - return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Hold, &S->ms.reports); - - // 'i' commands - conditionals - case CommandId_if: - PROCESS_CONDITION(processIfCommand(ctx)) - case CommandId_ifDoubletap: - PROCESS_CONDITION(processIfDoubletapCommand(false)) - case CommandId_ifNotDoubletap: - PROCESS_CONDITION(processIfDoubletapCommand(true)) - case CommandId_ifInterrupted: - PROCESS_CONDITION(processIfInterruptedCommand(false)) - case CommandId_ifNotInterrupted: - PROCESS_CONDITION(processIfInterruptedCommand(true)) - case CommandId_ifReleased: - PROCESS_CONDITION(processIfReleasedCommand(false)) - case CommandId_ifNotReleased: - PROCESS_CONDITION(processIfReleasedCommand(true)) - case CommandId_ifKeymap: - PROCESS_CONDITION(processIfKeymapCommand(ctx, false)) - case CommandId_ifNotKeymap: - PROCESS_CONDITION(processIfKeymapCommand(ctx, true)) - case CommandId_ifLayer: - PROCESS_CONDITION(processIfLayerCommand(ctx, false)) - case CommandId_ifNotLayer: - PROCESS_CONDITION(processIfLayerCommand(ctx, true)) - case CommandId_ifLayerToggled: - PROCESS_CONDITION(processIfLayerToggledCommand(ctx, false)) - case CommandId_ifNotLayerToggled: - PROCESS_CONDITION(processIfLayerToggledCommand(ctx, true)) - case CommandId_ifPlaytime: - PROCESS_CONDITION(processIfPlaytimeCommand(ctx, false)) - case CommandId_ifNotPlaytime: - PROCESS_CONDITION(processIfPlaytimeCommand(ctx, true)) - case CommandId_ifAnyMod: - PROCESS_CONDITION(processIfModifierCommand(false, 0xFF)) - case CommandId_ifNotAnyMod: - PROCESS_CONDITION(processIfModifierCommand(true, 0xFF)) - case CommandId_ifShift: - PROCESS_CONDITION(processIfModifierCommand(false, SHIFTMASK)) - case CommandId_ifNotShift: - PROCESS_CONDITION(processIfModifierCommand(true, SHIFTMASK)) - case CommandId_ifCtrl: - PROCESS_CONDITION(processIfModifierCommand(false, CTRLMASK)) - case CommandId_ifNotCtrl: - PROCESS_CONDITION(processIfModifierCommand(true, CTRLMASK)) - case CommandId_ifAlt: - PROCESS_CONDITION(processIfModifierCommand(false, ALTMASK)) - case CommandId_ifNotAlt: - PROCESS_CONDITION(processIfModifierCommand(true, ALTMASK)) - case CommandId_ifGui: - PROCESS_CONDITION(processIfModifierCommand(false, GUIMASK)) - case CommandId_ifNotGui: - PROCESS_CONDITION(processIfModifierCommand(true, GUIMASK)) - case CommandId_ifCapsLockOn: - PROCESS_CONDITION(processIfStateKeyCommand(false, &UsbBasicKeyboard_CapsLockOn)) - case CommandId_ifNotCapsLockOn: - PROCESS_CONDITION(processIfStateKeyCommand(true, &UsbBasicKeyboard_CapsLockOn)) - case CommandId_ifNumLockOn: - PROCESS_CONDITION(processIfStateKeyCommand(false, &UsbBasicKeyboard_NumLockOn)) - case CommandId_ifNotNumLockOn: - PROCESS_CONDITION(processIfStateKeyCommand(true, &UsbBasicKeyboard_NumLockOn)) - case CommandId_ifScrollLockOn: - PROCESS_CONDITION(processIfStateKeyCommand(false, &UsbBasicKeyboard_ScrollLockOn)) - case CommandId_ifNotScrollLockOn: - PROCESS_CONDITION(processIfStateKeyCommand(true, &UsbBasicKeyboard_ScrollLockOn)) - case CommandId_ifRecording: - PROCESS_CONDITION(processIfRecordingCommand(false)) - case CommandId_ifNotRecording: - PROCESS_CONDITION(processIfRecordingCommand(true)) - case CommandId_ifRecordingId: - PROCESS_CONDITION(processIfRecordingIdCommand(ctx, false)) - case CommandId_ifNotRecordingId: - PROCESS_CONDITION(processIfRecordingIdCommand(ctx, true)) - case CommandId_ifNotPending: - PROCESS_CONDITION(processIfPendingCommand(ctx, true)) - case CommandId_ifPending: - PROCESS_CONDITION(processIfPendingCommand(ctx, false)) - case CommandId_ifKeyPendingAt: - PROCESS_CONDITION(processIfKeyPendingAtCommand(ctx, false)) - case CommandId_ifNotKeyPendingAt: - PROCESS_CONDITION(processIfKeyPendingAtCommand(ctx, true)) - case CommandId_ifKeyActive: - PROCESS_CONDITION(processIfKeyActiveCommand(ctx, false)) - case CommandId_ifNotKeyActive: - PROCESS_CONDITION(processIfKeyActiveCommand(ctx, true)) - case CommandId_ifPendingKeyReleased: - PROCESS_CONDITION(processIfPendingKeyReleasedCommand(ctx, false)) - case CommandId_ifNotPendingKeyReleased: - PROCESS_CONDITION(processIfPendingKeyReleasedCommand(ctx, true)) - case CommandId_ifKeyDefined: - PROCESS_CONDITION(processIfKeyDefinedCommand(ctx, false)) - case CommandId_ifNotKeyDefined: - PROCESS_CONDITION(processIfKeyDefinedCommand(ctx, true)) - case CommandId_ifModuleConnected: - PROCESS_CONDITION(processIfModuleConnected(ctx, false)) - case CommandId_ifNotModuleConnected: - PROCESS_CONDITION(processIfModuleConnected(ctx, true)) - case CommandId_ifHold: - return processIfHoldCommand(ctx, false); - case CommandId_ifTap: - return processIfHoldCommand(ctx, true); - case CommandId_ifSecondary: - return processIfSecondaryCommand(ctx, false); - case CommandId_ifPrimary: - return processIfSecondaryCommand(ctx, true); - case CommandId_ifShortcut: - return processIfShortcutCommand(ctx, false, true); - case CommandId_ifNotShortcut: - return processIfShortcutCommand(ctx, true, true); - case CommandId_ifGesture: - return processIfShortcutCommand(ctx, false, false); - case CommandId_ifNotGesture: - return processIfShortcutCommand(ctx, true, false); - case CommandId_ifRegEq: - case CommandId_ifNotRegEq: - Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `if ($varName == 1)`."); - return MacroResult_Finished; - case CommandId_ifRegGt: - case CommandId_ifRegLt: - Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `if ($varName >= 1)`."); - return MacroResult_Finished; - - // 'm' commands - case CommandId_macroArg: - return processMacroArgCommand(ctx); - case CommandId_mulReg: - Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName*2)`."); - return MacroResult_Finished; + // We assume the next command is a non-header command and will therefore + // finish the header block. + bool headersProcessed = true; - // 'n' commands - case CommandId_noOp: - return processNoOpCommand(); - case CommandId_notify: - return Macros_ProcessNotifyCommand(ctx); - - // 'o' commands - case CommandId_oneShot: - return processOneShotCommand(ctx); - case CommandId_overlayLayer: - return processOverlayLayerCommand(ctx); - case CommandId_overlayKeymap: - return processOverlayKeymapCommand(ctx); - - // 'p' commands - case CommandId_printStatus: - return Macros_ProcessPrintStatusCommand(); - case CommandId_playMacro: - return processPlayMacroCommand(ctx); - case CommandId_pressKey: - return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Press, &S->ms.reports); - case CommandId_postponeKeys: - processPostponeKeysCommand(); - break; - case CommandId_postponeNext: - return processPostponeNextNCommand(ctx); - case CommandId_progressHue: - return processProgressHueCommand(); - case CommandId_powerMode: - return processPowerModeCommand(ctx); - case CommandId_panic: - return processPanicCommand(ctx); - - // 'r' commands - case CommandId_recordMacro: - return processRecordMacroCommand(ctx, false); - case CommandId_recordMacroBlind: - return processRecordMacroCommand(ctx, true); - case CommandId_recordMacroDelay: - return processRecordMacroDelayCommand(); - case CommandId_resolveNextKeyId: - return processResolveNextKeyIdCommand(); - case CommandId_releaseKey: - return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Release, &S->ms.reports); - case CommandId_repeatFor: - return processRepeatForCommand(ctx); - case CommandId_resetTrackpoint: - return processResetTrackpointCommand(); - case CommandId_replaceLayer: - return processReplaceLayerCommand(ctx); - case CommandId_replaceKeymap: - return processReplaceKeymapCommand(ctx); - case CommandId_resolveNextKeyEq: - Macros_ReportErrorPos(ctx, "Command deprecated. Please, replace resolveNextKeyEq by ifShortcut or ifGesture, or complain at github that you actually need this."); - return MacroResult_Finished; - case CommandId_resolveSecondary: - Macros_ReportErrorPos(ctx, "Command deprecated. Please, replace resolveSecondary by `ifPrimary advancedStrategy goTo ...` or `ifSecondary advancedStrategy goTo ...`."); - return MacroResult_Finished; - case CommandId_resetConfiguration: - return processResetConfigurationCommand(ctx); - case CommandId_reboot: - return processRebootCommand(); - case CommandId_reconnect: - return processReconnectCommand(); - - // 's' commands - case CommandId_set: - return Macro_ProcessSetCommand(ctx); - case CommandId_setVar: - return Macros_ProcessSetVarCommand(ctx); - case CommandId_setStatus: - return Macros_ProcessSetStatusCommand(ctx, true); - case CommandId_startRecording: - return processStartRecordingCommand(ctx, false); - case CommandId_startRecordingBlind: - return processStartRecordingCommand(ctx, true); - case CommandId_setLedTxt: - return Macros_ProcessSetLedTxtCommand(ctx); - case CommandId_statsRuntime: - return Macros_ProcessStatsRuntimeCommand(); - case CommandId_statsRecordKeyTiming: - return Macros_ProcessStatsRecordKeyTimingCommand(); - case CommandId_statsLayerStack: - return Macros_ProcessStatsLayerStackCommand(); - case CommandId_statsActiveKeys: - return Macros_ProcessStatsActiveKeysCommand(); - case CommandId_statsActiveMacros: - return Macros_ProcessStatsActiveMacrosCommand(); - case CommandId_statsPostponerStack: - return Macros_ProcessStatsPostponerStackCommand(); - case CommandId_statsVariables: - return Macros_ProcessStatsVariablesCommand(); - case CommandId_statsBattery: - return Macros_ProcessStatsBatteryCommand(); - case CommandId_switchKeymap: - return processSwitchKeymapCommand(ctx); - case CommandId_startMouse: - return processMouseCommand(ctx, true); - case CommandId_stopMouse: - return processMouseCommand(ctx, false); - case CommandId_stopRecording: - case CommandId_stopRecordingBlind: - return processStopRecordingCommand(); - case CommandId_stopAllMacros: - return processStopAllMacrosCommand(); - case CommandId_suppressMods: - processSuppressModsCommand(); - break; - case CommandId_setReg: - Macros_ReportErrorPos(ctx, "Command was removed, please use named variables. E.g., `setVar myVar 1` and `write \"$myVar\"`"); - return MacroResult_Finished; - case CommandId_subReg: - Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName+1)`."); - return MacroResult_Finished; - case CommandId_setStatusPart: - Macros_ReportErrorPos(ctx, "Command was removed, please use string interpolated setStatus."); - return MacroResult_Finished; - case CommandId_switchKeymapLayer: - case CommandId_switchLayer: - Macros_ReportErrorPos(ctx, "Command deprecated. Please, replace switchKeymapLayer by toggleKeymapLayer or holdKeymapLayer. Or complain on github that you actually need this command."); - return MacroResult_Finished; - case CommandId_switchHost: - return processSwitchHostCommand(ctx); - - // 't' commands - case CommandId_toggleKeymapLayer: - return processToggleKeymapLayerCommand(ctx); - case CommandId_toggleLayer: - return processToggleLayerCommand(ctx); - case CommandId_tapKey: - return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Tap, &S->ms.reports); - case CommandId_tapKeySeq: - return Macros_ProcessTapKeySeqCommand(ctx); - case CommandId_toggleKey: - return Macros_ProcessKeyCommandAndConsume(ctx, MacroSubAction_Toggle, &S->ms.reports); - case CommandId_trackpoint: - return processTrackpointCommand(ctx); - case CommandId_trace: - if (!Macros_DryRun) { - Trace_Print(LogTarget_ErrorBuffer, "Triggered by macro command"); - } - return MacroResult_Finished; - case CommandId_testLeakage: - return processTestLeakageCommand(ctx); - case CommandId_testSuite: - return processTestSuiteCommand(ctx); - - // 'u' commands - case CommandId_unToggleLayer: - case CommandId_untoggleLayer: - return processUnToggleLayerCommand(); - case CommandId_unpairHost: - return Macros_ProcessUnpairHostCommand(ctx); - - // 'v' commands - case CommandId_validateUserConfig: - case CommandId_validateMacros: - return processValidateMacrosCommand(ctx); - - // 'w' commands - case CommandId_write: - return processWriteCommand(ctx); - case CommandId_while: - return processWhileCommand(ctx); - case CommandId_writeExpr: - Macros_ReportErrorPos(ctx, "writeExpr is now deprecated, please migrate to interpolated strings"); - return MacroResult_Finished; - - // 'y' commands - case CommandId_yield: - return processYieldCommand(ctx); + macro_result_t res = dispatchCommand(ctx, entry->id, &headersProcessed); - // 'z' commands - case CommandId_zephyr: - return processZephyrCommand(ctx); - - // brace commands - case CommandId_openBrace: - return processOpeningBraceCommand(ctx); - case CommandId_closeBrace: - return processClosingBraceCommand(ctx); - - default: - Macros_ReportErrorTok(ctx, "Unrecognized command:"); - return MacroResult_Finished; + if (headersProcessed) { + S->ms.macroHeadersProcessed = true; } } + //this is reachable if there is a train of conditions/modifiers/labels without any command return MacroResult_Finished; } diff --git a/right/src/macros/core.c b/right/src/macros/core.c index b30d4c763..ce745c755 100644 --- a/right/src/macros/core.c +++ b/right/src/macros/core.c @@ -31,7 +31,6 @@ macro_reference_t AllMacros[MacroIndex_MaxCount] = { }; uint8_t AllMacrosCount; - bool Macros_WakedBecauseOfOneShot = false; bool Macros_WakedBecauseOfTime = false; bool Macros_WakedBecauseOfKeystateChange = false; diff --git a/right/src/macros/core.h b/right/src/macros/core.h index ff1d5a2f8..babc46053 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -176,6 +176,8 @@ macro_arg_t arguments[MAX_MACRO_ARGUMENT_SIZE]; macro_usb_keyboard_reports_t reports; + + bool macroHeadersProcessed : 1; } ms; // action scope data From f5f33021c013cc9786bd7cd2ac5f56fd36fd7bd4 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 5 Feb 2026 19:17:30 +0100 Subject: [PATCH 013/113] fix typo --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 6f088c5ef..d57ad1489 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2081,7 +2081,7 @@ static macro_result_t processZephyrCommand(parser_context_t* ctx) { } \ break; -static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t commandId, bool *headersDone) { +static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t commandId, bool *headersFinished) { // Dispatch based on command ID switch (commandId) { // 'a' commands From b81f9b3758318fd335add220d713460a8b77b7b9 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 5 Feb 2026 19:26:15 +0100 Subject: [PATCH 014/113] actually return the return value --- right/src/macros/commands.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index d57ad1489..8bd4b6558 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2467,7 +2467,7 @@ static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t comman return MacroResult_Finished; } } - + static macro_result_t processCommand(parser_context_t* ctx) { const char* cmdTokEnd = TokEnd(ctx->at, ctx->end); @@ -2498,6 +2498,7 @@ static macro_result_t processCommand(parser_context_t* ctx) if (headersProcessed) { S->ms.macroHeadersProcessed = true; } + return res; } //this is reachable if there is a train of conditions/modifiers/labels without any command From 9239daadd8895f1c7574962e856de1c473f82b1f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 10:38:28 +0100 Subject: [PATCH 015/113] figure why some parsing broke --- right/src/macros/commands.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 8bd4b6558..f86daea60 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2466,6 +2466,9 @@ static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t comman Macros_ReportErrorTok(ctx, "Unrecognized command:"); return MacroResult_Finished; } + + Macros_ReportErrorTok(ctx, "Fell through the dispatch at:"); + return MacroResult_Finished; } static macro_result_t processCommand(parser_context_t* ctx) From 8da0330424d188d106b1c9f393b29286ae5b892e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 11:17:55 +0100 Subject: [PATCH 016/113] fix the reachable unreachable case --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index f86daea60..79ed029ba 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2467,7 +2467,7 @@ static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t comman return MacroResult_Finished; } - Macros_ReportErrorTok(ctx, "Fell through the dispatch at:"); + //this is reachable in some cases return MacroResult_Finished; } From ab5c4323532bd5dbf9a31dd1a6825fcf4898a948 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 12:04:10 +0100 Subject: [PATCH 017/113] introducing MacroResult_None to fix 'if' commands that need to continue the processCommand() loop --- right/src/macros/commands.c | 8 +++++--- right/src/macros/typedefs.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 79ed029ba..0e3ed5315 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2467,8 +2467,8 @@ static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t comman return MacroResult_Finished; } - //this is reachable in some cases - return MacroResult_Finished; + // this is reachable when 'ifXxx' conditions pass; processCommand() should continue with further commands. + return MacroResult_None; } static macro_result_t processCommand(parser_context_t* ctx) @@ -2501,7 +2501,9 @@ static macro_result_t processCommand(parser_context_t* ctx) if (headersProcessed) { S->ms.macroHeadersProcessed = true; } - return res; + if (res != MacroResult_None) { + return res; + } } //this is reachable if there is a train of conditions/modifiers/labels without any command diff --git a/right/src/macros/typedefs.h b/right/src/macros/typedefs.h index 3fdad13fd..c4349c319 100644 --- a/right/src/macros/typedefs.h +++ b/right/src/macros/typedefs.h @@ -23,6 +23,7 @@ } macro_sub_action_t; typedef enum { + MacroResult_None = 0, MacroResult_InProgressFlag = 1, MacroResult_ActionFinishedFlag = 2, MacroResult_DoneFlag = 4, From 458afe1bdd4660b7e7c0f1b3ed30246d8911a73c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 15:05:41 +0100 Subject: [PATCH 018/113] removing unused code --- right/src/str_utils.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/right/src/str_utils.c b/right/src/str_utils.c index e7651a024..2446cc3d3 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -320,11 +320,6 @@ void ConsumeAnyIdentifier(parser_context_t* ctx) consumeWhite(ctx); } -void ConsumeIdentifier(parser_context_t* ctx) -{ - ctx->at = IdentifierEnd(ctx); -} - void ConsumeUntilDot(parser_context_t* ctx) { while(*ctx->at > 32 && *ctx->at != '.' && !isEnd(ctx)) { From 2b989253e5a6d06935844e9736df3011ba9c380e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 15:55:19 +0100 Subject: [PATCH 019/113] cleaner handling of returns from dispatching, especially for header commands --- right/src/macros/commands.c | 23 ++++++++++------------- right/src/macros/typedefs.h | 1 + 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 0e3ed5315..ca6e669a7 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -961,7 +961,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; - return MacroResult_Finished; + return MacroResult_Header; } // parse the argument name (identifier) const char *idStart = ctx->at; @@ -969,7 +969,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) if (idEnd == idStart) { Macros_ReportErrorPos(ctx, "Expected identifier"); - return MacroResult_Finished; + return MacroResult_Header; } ctx->at = idEnd; @@ -1000,7 +1000,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } else { Macros_ReportErrorTok(ctx, "Unrecognized macroArg argument type:"); - return MacroResult_Finished; + return MacroResult_Header; } } else { @@ -1011,7 +1011,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) // rest of command is descriptive label, ignored by firmware while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; - return MacroResult_Finished; + return MacroResult_Header; // Macros_ReportErrorPrintf(ctx->at, "Parsing failed at '%s'?", OneWord(ctx)); } @@ -2081,7 +2081,7 @@ static macro_result_t processZephyrCommand(parser_context_t* ctx) { } \ break; -static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t commandId, bool *headersFinished) { +static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t commandId) { // Dispatch based on command ID switch (commandId) { // 'a' commands @@ -2274,7 +2274,6 @@ static macro_result_t dispatchCommand(parser_context_t* ctx, command_id_t comman // 'm' commands case CommandId_macroArg: - *headersFinished = false; // this is a valid header command, stay in header mode return processMacroArgCommand(ctx); case CommandId_mulReg: Macros_ReportErrorPos(ctx, "Command was removed, please use command similar to `setVar varName ($varName*2)`."); @@ -2492,14 +2491,12 @@ static macro_result_t processCommand(parser_context_t* ctx) return MacroResult_Finished; } - // We assume the next command is a non-header command and will therefore - // finish the header block. - bool headersProcessed = true; + macro_result_t res = dispatchCommand(ctx, entry->id); - macro_result_t res = dispatchCommand(ctx, entry->id, &headersProcessed); - - if (headersProcessed) { - S->ms.macroHeadersProcessed = true; + if (res == MacroResult_Header) { + return MacroResult_Finished; + } else { + S->ms.macroHeadersProcessed = true; // non-header commands mark the header as finished } if (res != MacroResult_None) { return res; diff --git a/right/src/macros/typedefs.h b/right/src/macros/typedefs.h index c4349c319..32ac69ca9 100644 --- a/right/src/macros/typedefs.h +++ b/right/src/macros/typedefs.h @@ -36,6 +36,7 @@ MacroResult_Waiting = MacroResult_InProgressFlag | MacroResult_YieldFlag, MacroResult_Sleeping = MacroResult_InProgressFlag | MacroResult_YieldFlag, MacroResult_Finished = MacroResult_ActionFinishedFlag, + MacroResult_Header = MacroResult ActionFinishedFlag | MacroResult_InProgressFlag, MacroResult_JumpedForward = MacroResult_DoneFlag, MacroResult_JumpedBackward = MacroResult_DoneFlag | MacroResult_YieldFlag, } macro_result_t; From e51d414a6b9e8afa065e6e909edccf817debaafe Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 16:15:26 +0100 Subject: [PATCH 020/113] syntax fix --- right/src/macros/typedefs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/typedefs.h b/right/src/macros/typedefs.h index 32ac69ca9..818bee0bb 100644 --- a/right/src/macros/typedefs.h +++ b/right/src/macros/typedefs.h @@ -36,7 +36,7 @@ MacroResult_Waiting = MacroResult_InProgressFlag | MacroResult_YieldFlag, MacroResult_Sleeping = MacroResult_InProgressFlag | MacroResult_YieldFlag, MacroResult_Finished = MacroResult_ActionFinishedFlag, - MacroResult_Header = MacroResult ActionFinishedFlag | MacroResult_InProgressFlag, + MacroResult_Header = MacroResult_ActionFinishedFlag | MacroResult_InProgressFlag, MacroResult_JumpedForward = MacroResult_DoneFlag, MacroResult_JumpedBackward = MacroResult_DoneFlag | MacroResult_YieldFlag, } macro_result_t; From d540053a15e5adea6213a78ed14aff90a061c671 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Feb 2026 18:36:37 +0100 Subject: [PATCH 021/113] moving arg storage, but I'm not happy yet --- right/src/macros/core.c | 2 ++ right/src/macros/core.h | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/right/src/macros/core.c b/right/src/macros/core.c index ce745c755..ea7700c04 100644 --- a/right/src/macros/core.c +++ b/right/src/macros/core.c @@ -60,6 +60,8 @@ macro_state_t *S = NULL; macro_history_t MacroHistory[MACRO_HISTORY_POOL_SIZE]; uint8_t MacroHistoryPosition = 0; +macro_argument_t MacroArguments[MAX_MACRO_ARGUMENT_POOL_SIZE]; + static void checkSchedulerHealth(const char* tag); static void wakeMacroInSlot(uint8_t slotIdx); static void scheduleSlot(uint8_t slotIdx); diff --git a/right/src/macros/core.h b/right/src/macros/core.h index babc46053..377dab97e 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -21,7 +21,9 @@ #define MACRO_HISTORY_POOL_SIZE 16 #define MACRO_SCOPE_STATE_POOL_SIZE (MACRO_STATE_POOL_SIZE*2) #define MAX_REG_COUNT 32 - #define MAX_MACRO_ARGUMENT_SIZE 8 + + #define MAX_MACRO_ARGUMENT_COUNT 8 + #define MAX_MACRO_ARGUMENT_POOL_SIZE 32 #define ALTMASK (HID_KEYBOARD_MODIFIER_LEFTALT | HID_KEYBOARD_MODIFIER_RIGHTALT) #define CTRLMASK (HID_KEYBOARD_MODIFIER_LEFTCTRL | HID_KEYBOARD_MODIFIER_RIGHTCTRL) @@ -141,6 +143,11 @@ macro_arg_type_t type; } ATTR_PACKED macro_arg_t; + typedef struct { + uint8 id; + macro_arg_type_t type; + } ATTR_PACKED macro_argref_t; + struct macro_state_t { // local scope data macro_scope_state_t *ls; @@ -173,7 +180,7 @@ bool autoRepeatInitialDelayPassed: 1; macro_autorepeat_state_t autoRepeatPhase: 1; // ---- 4-aligned ---- - macro_arg_t arguments[MAX_MACRO_ARGUMENT_SIZE]; + macro_argref_t arguments[MAX_MACRO_ARGUMENT_COUNT]; macro_usb_keyboard_reports_t reports; From aa66476b357ab426a762f02544ec365b814adbbf Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sat, 7 Feb 2026 17:22:29 +0100 Subject: [PATCH 022/113] refactor & fix ConsumeUntilDot --- right/src/macros/commands.c | 3 ++- right/src/macros/vars.c | 3 ++- right/src/str_utils.c | 39 +++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index ca6e669a7..b360aba52 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -976,6 +976,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) // see if the argument has a type macro_arg_type_t argType; + // TODO: use ConsumeToken(ctx, ":") instead if (*ctx->at == ':') { ctx->at++; const char *typeStart = ctx->at; @@ -1008,7 +1009,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) ConsumeWhite(ctx); } - // rest of command is descriptive label, ignored by firmware + // rest of command is descriptive label, ignore while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; return MacroResult_Header; diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 727bf8889..d8df05628 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -198,7 +198,8 @@ static macro_variable_t* consumeVarAndAllocate(parser_context_t* ctx) } } - CTX_COPY(bakCtx, *ctx); + // TODO: Is this needed at all? Looks like something left over + // CTX_COPY(bakCtx, *ctx); macro_variable_t configVal = Macro_TryReadConfigVal(ctx); if (configVal.type != MacroVariableType_None) { diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 2446cc3d3..7fce0075e 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -271,13 +271,11 @@ static bool isIdentifierChar(char c) } } - bool IsIdentifierChar(char c) { return isIdentifierChar(c); } - bool ConsumeIdentifierByRef(parser_context_t* ctx, string_ref_t ref) { const char* at = ctx->at; @@ -320,6 +318,9 @@ void ConsumeAnyIdentifier(parser_context_t* ctx) consumeWhite(ctx); } +#if 0 +/* old ConsumeUntilDot (replaced below) */ + void ConsumeUntilDot(parser_context_t* ctx) { while(*ctx->at > 32 && *ctx->at != '.' && !isEnd(ctx)) { @@ -330,6 +331,40 @@ void ConsumeUntilDot(parser_context_t* ctx) } ctx->at++; } +#endif + +// Consume characters until a specific character is found or whitespace is hit. +// If end of context is reached, report an error. +// If the character is found, consume it. +// If whitespace is found, and failOnWhite is true, report an error. +void ConsumeUntilCharOrWhite(parser_context_t* ctx, char c, bool failOnWhite) +{ + while(*ctx->at > 32 && *ctx->at != c && !isEnd(ctx)) { + ctx->at++; + } + if (IsEnd(ctx)) { + Macros_ReportError("unexpected end of statement", ctx->at, ctx->at); + return; + } + if (*ctx->at == c) { + ctx->at++; + return; + } + if (failOnWhite) { + Macros_ReportErrorPrintf(ctx->at, "'%c' expected", c); + return; + } +} + +void ConsumeUntilDot(parser_context_t* ctx) +{ + ConsumeUntilCharOrWhite(ctx, '.', true); +} + +void ConsumeUntilColon(parser_context_t* ctx) +{ + ConsumeUntilCharOrWhite(ctx, ':', false); +} bool TokenMatches(const char *a, const char *aEnd, const char *b) { From f1d866b7b9565d758ee9f882eab57b8a037ad36a Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sat, 7 Feb 2026 17:30:02 +0100 Subject: [PATCH 023/113] fix typo --- right/src/macros/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 377dab97e..54a626a97 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -144,7 +144,7 @@ } ATTR_PACKED macro_arg_t; typedef struct { - uint8 id; + uint8_t id; macro_arg_type_t type; } ATTR_PACKED macro_argref_t; From 04d5aad45af8697e4ec04d213bca53c5f008ce35 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sat, 7 Feb 2026 17:35:04 +0100 Subject: [PATCH 024/113] fix typo --- right/src/macros/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 54a626a97..831ffa3ad 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -141,7 +141,7 @@ typedef struct { string_ref_t id; macro_arg_type_t type; - } ATTR_PACKED macro_arg_t; + } ATTR_PACKED macro_argument_t; typedef struct { uint8_t id; From 36ff834c3951683a3126c31a48f9b4afdd8f39ec Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 8 Feb 2026 12:01:58 +0100 Subject: [PATCH 025/113] simplify processMacroArgCommand; refrain from accessing *ctx->at directly --- right/src/macros/commands.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index b360aba52..fc2859d03 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -976,11 +976,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) // see if the argument has a type macro_arg_type_t argType; - // TODO: use ConsumeToken(ctx, ":") instead - if (*ctx->at == ':') { - ctx->at++; - const char *typeStart = ctx->at; - + if (ConsumeToken(ctx, ":")) { if (ConsumeToken(ctx, "int")) { argType = MacroArgType_Int; } @@ -1006,7 +1002,6 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } else { argType = MacroArgType_Any; - ConsumeWhite(ctx); } // rest of command is descriptive label, ignore From 3c96cec9b05fad54acb37faeb80c25495431c4fc Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 8 Feb 2026 13:20:29 +0100 Subject: [PATCH 026/113] refactor white processing for macroArg, safer check for comment --- right/src/macros/commands.c | 8 ++++++++ right/src/str_utils.c | 23 +++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index fc2859d03..6837da447 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1001,9 +1001,17 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } } else { + if (!IsWhite(ctx)) { + Macros_ReportErrorTok(ctx, "Superfluous non-identifier characters:"); + return MacroResult_Header; + } + ConsumeWhite(ctx); argType = MacroArgType_Any; } + // now allocate argument slot and store the argument info + // TODO: ... + // rest of command is descriptive label, ignore while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 7fce0075e..10533a667 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -13,7 +13,7 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif -ATTR_UNUSED static parser_context_t parserContextStack[PARSER_CONTEXT_STACK_SIZE]; +static parser_context_t parserContextStack[PARSER_CONTEXT_STACK_SIZE]; static bool consumeCommentsAsWhite = true; @@ -133,13 +133,31 @@ bool IsEnd(parser_context_t* ctx) { return isEnd(ctx); } +static bool isCommentLeader(parser_context_t* ctx) { + return ctx->at + 1 < ctx->end && ctx->at[0] == '/' && ctx->at[1] == '/'; +} + +static bool isWhite(parser_context_t* ctx) { + if (*ctx->at <= 32) { + return true; + } + if (isCommentLeader(ctx)) { + return true; + } + return false; +} + +bool IsWhite(parser_context_t* ctx) { + return isWhite(ctx); +} + static void consumeWhite(parser_context_t* ctx) { while (!isEnd(ctx)) { while (*ctx->at <= 32 && !isEnd(ctx)) { ctx->at++; } - if (ctx->at[0] == '/' && ctx->at[1] == '/' && consumeCommentsAsWhite) { + if (isCommentLeader(ctx) && consumeCommentsAsWhite) { while (*ctx->at != '\n' && !isEnd(ctx)) { ctx->at++; } @@ -176,6 +194,7 @@ void UnconsumeWhite(parser_context_t* ctx) } } +// dangerous due to static return buffer; only use for error messages! const char* OneWord(parser_context_t* ctx) { static char buffer[20]; From 167459012cce4c3b07b2ac1fe84c351677cb1499 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 10 Feb 2026 02:39:39 +0100 Subject: [PATCH 027/113] trial commit for arg storage --- right/src/macros/commands.c | 29 +++++++++++++-- right/src/macros/core.c | 2 -- right/src/macros/core.h | 12 +------ right/src/macros/typedefs.h | 10 ------ right/src/macros/vars.c | 70 +++++++++++++++++++++++++++++++++++++ right/src/macros/vars.h | 30 ++++++++++++++++ right/src/str_utils.c | 9 +++-- 7 files changed, 131 insertions(+), 31 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 6837da447..d5c228fe4 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -974,7 +974,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) ctx->at = idEnd; // see if the argument has a type - macro_arg_type_t argType; + macro_argument_type_t argType; if (ConsumeToken(ctx, ":")) { if (ConsumeToken(ctx, "int")) { @@ -983,6 +983,9 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) else if (ConsumeToken(ctx, "float")) { argType = MacroArgType_Float; } + else if (ConsumeToken(ctx, "bool")) { + argType = MacroArgType_Float; + } else if (ConsumeToken(ctx, "string")) { argType = MacroArgType_String; } @@ -1009,10 +1012,30 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) argType = MacroArgType_Any; } - // now allocate argument slot and store the argument info + // allocate an argument slot if there is room + if (S->ms.argumentCount >= MACRO_MAX_ARGUMENTS) { + Macros_ReportErrorPos(ctx, "Maximum number of macro arguments exceeded"); + return MacroResult_Header; + } + + // check whether the argument name has already been used in this macro // TODO: ... - // rest of command is descriptive label, ignore + // allocate a pool slot for the argument and store its metadata (name and type) there + macro_argument_alloc_result_t res = Macros_AllocateMacroArg(S, idStart, idEnd, argType, S->ms.argumentCount+1, &(S->ms.arguments[S->ms.argumentCount])); + switch (res) { + case MacroArgumentAllocResult_Success: + S->ms.argumentCount++; + break; + case MacroArgumentAllocResult_PoolLimitExceeded: + Macros_ReportErrorPos(ctx, "Too many arguments across simultaneously active macros (argument pool exhausted)"); + return MacroResult_Header; + case MacroArgumentAllocResult_DuplicateArgumentName: + Macros_ReportErrorPos(ctx, "Duplicate argument name"); + return MacroResult_Header; + } + + // rest of command is descriptive label, ignore. TODO: Should be parsed as string literal. while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; return MacroResult_Header; diff --git a/right/src/macros/core.c b/right/src/macros/core.c index ea7700c04..ce745c755 100644 --- a/right/src/macros/core.c +++ b/right/src/macros/core.c @@ -60,8 +60,6 @@ macro_state_t *S = NULL; macro_history_t MacroHistory[MACRO_HISTORY_POOL_SIZE]; uint8_t MacroHistoryPosition = 0; -macro_argument_t MacroArguments[MAX_MACRO_ARGUMENT_POOL_SIZE]; - static void checkSchedulerHealth(const char* tag); static void wakeMacroInSlot(uint8_t slotIdx); static void scheduleSlot(uint8_t slotIdx); diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 831ffa3ad..6ad57f0af 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -23,7 +23,6 @@ #define MAX_REG_COUNT 32 #define MAX_MACRO_ARGUMENT_COUNT 8 - #define MAX_MACRO_ARGUMENT_POOL_SIZE 32 #define ALTMASK (HID_KEYBOARD_MODIFIER_LEFTALT | HID_KEYBOARD_MODIFIER_RIGHTALT) #define CTRLMASK (HID_KEYBOARD_MODIFIER_LEFTCTRL | HID_KEYBOARD_MODIFIER_RIGHTCTRL) @@ -138,16 +137,6 @@ uint8_t macroIndex; } macro_history_t; - typedef struct { - string_ref_t id; - macro_arg_type_t type; - } ATTR_PACKED macro_argument_t; - - typedef struct { - uint8_t id; - macro_arg_type_t type; - } ATTR_PACKED macro_argref_t; - struct macro_state_t { // local scope data macro_scope_state_t *ls; @@ -184,6 +173,7 @@ macro_usb_keyboard_reports_t reports; + uint8_t argumentCount : 4; bool macroHeadersProcessed : 1; } ms; diff --git a/right/src/macros/typedefs.h b/right/src/macros/typedefs.h index 818bee0bb..6a493806b 100644 --- a/right/src/macros/typedefs.h +++ b/right/src/macros/typedefs.h @@ -49,16 +49,6 @@ uint8_t inputModifierMask; } macro_usb_keyboard_reports_t; - typedef enum { - MacroArgType_Any, - MacroArgType_Int, - MacroArgType_Float, - MacroArgType_Bool, - MacroArgType_String, - MacroArgType_KeyId, - MacroArgType_ScanCode - } macro_arg_type_t; - // Variables: // Functions: diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index d8df05628..1f18eb4b2 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -50,6 +50,9 @@ typedef enum { macro_variable_t macroVariables[MACRO_VARIABLE_COUNT_MAX]; uint8_t macroVariableCount = 0; +macro_argument_t macroArguments[MAX_MACRO_ARGUMENT_POOL_SIZE]; +// uint8_t macroArgumentCount = 0; + static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx); static macro_variable_t consumeParenthessExpression(parser_context_t* ctx); static macro_variable_t consumeValue(parser_context_t* ctx); @@ -1084,3 +1087,70 @@ bool TryExpandMacroTemplateOnce(parser_context_t* ctx) { ctx->at--; return false; } + +// ---------------------------------------- +// macroArguments allocation and processing +// ---------------------------------------- + +string_ref_t createStringRef(const char *start, const char *end) { + return (string_ref_t) { + .offset = start - (const char*)ValidatedUserConfigBuffer.buffer, + .len = (uint8_t)(end - start), + }; +} + +string_segment_t stringRefToSegment(string_ref_t ref) { + return (string_segment_t) { + .start = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset), + .end = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len), + }; +} + +const char *stringRefStart(string_ref_t ref) { + return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset); +} + +const char *stringRefEnd(string_ref_t ref) { + return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); +} + +macro_argument_alloc_result_t Macros_AllocateMacroArgument( + macro_state_t *owner, + const char *idStart, + const char *idEnd, + macro_argument_type_t type, + uint8_t argNumber, + macro_argref_t* outArgRef +) { + // search for existing argument of same owner with the same identifier, error if found + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && + SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = idStart, .end = idEnd }) { + return MacroArgAllocResult_DuplicateArgumentName; + } + } + + // search for an unused slot in the pool + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + if (macroArguments[i].type == MacroArgType_Unused) { + macroArguments[i].owner = owner; + macroArguments[i].type = type; + macroArguments[i].id = argNumber; + macroArguments[i].name = createStringRef(idStart, idEnd); + *outArgRef = i; + return MacroArgAllocResult_Success; + } + } + + return MacroArgAllocResult_PoolLimitExceeded; +} + +macro_argument_t *Macros_FindMacroArgumentByName(macro_state_t *owner, const char *nameStart, const char *nameEnd) { + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && + SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = nameStart, .end = nameEnd })) { + return ¯oArguments[i]; + } + } + return NULL; +} diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 2fee6fb7f..1f13d8ff5 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -18,6 +18,8 @@ #define MACRO_VARIABLE_COUNT_MAX 32 #define TRY_EXPAND_TEMPLATE(CTX) (*ctx->at == '&' && TryExpandMacroTemplateOnce(CTX)) + #define MACRO_ARGUMENT_POOL_SIZE 32 + // Typedefs: typedef enum { @@ -39,6 +41,34 @@ macro_variable_type_t type; } ATTR_PACKED macro_variable_t; + typedef enum { + MacroArgType_Unused = 0, + MacroArgType_Any, + MacroArgType_Int, + MacroArgType_Float, + MacroArgType_Bool, + MacroArgType_String, + MacroArgType_KeyId, + MacroArgType_ScanCode + } macro_argument_type_t; + + typedef struct { + macro_state_t *owner; + macro_argument_type_t type; + uint8_t id; + string_ref_t name; + } macro_argument_t; + + typedef enum { + MacroArgAllocResult_Success, + MacroArgAllocResult_PoolLimitExceeded, + MacroArgAllocResult_DuplicateArgumentName, + } macro_argument_alloc_result_t; + + typedef struct { + uint8_t poolId; + } macro_argref_t; + // Variables: diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 10533a667..b6f83f496 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -170,7 +170,6 @@ static void consumeWhite(parser_context_t* ctx) } } - void ConsumeCommentsAsWhite(bool consume) { consumeCommentsAsWhite = consume; @@ -380,10 +379,10 @@ void ConsumeUntilDot(parser_context_t* ctx) ConsumeUntilCharOrWhite(ctx, '.', true); } -void ConsumeUntilColon(parser_context_t* ctx) -{ - ConsumeUntilCharOrWhite(ctx, ':', false); -} +//void ConsumeUntilColon(parser_context_t* ctx) +//{ +// ConsumeUntilCharOrWhite(ctx, ':', false); +//} bool TokenMatches(const char *a, const char *aEnd, const char *b) { From 292135723f8a6ad7e229a1d3fcc5dd9b9ab44128 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 10 Feb 2026 02:46:02 +0100 Subject: [PATCH 028/113] trial 2 --- right/src/macros/core.h | 1 + right/src/macros/vars.h | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 6ad57f0af..1d94d31b3 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -11,6 +11,7 @@ #include "key_states.h" #include "str_utils.h" #include "macros/typedefs.h" + #include "macros/vars.h" #include "event_scheduler.h" // Macros: diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 1f13d8ff5..2e8535bba 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -1,7 +1,6 @@ #ifndef __MACROS_VARS_H__ #define __MACROS_VARS_H__ - // Includes: #include @@ -87,4 +86,3 @@ bool TryExpandMacroTemplateOnce(parser_context_t* ctx); #endif - From d086ac71514ffd9d1f69b4a3c2103181715c5e6b Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 10 Feb 2026 03:09:12 +0100 Subject: [PATCH 029/113] fixes 3 --- right/src/macros/commands.c | 10 +++++----- right/src/str_utils.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index d5c228fe4..bdb30e111 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1013,7 +1013,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // allocate an argument slot if there is room - if (S->ms.argumentCount >= MACRO_MAX_ARGUMENTS) { + if (S->ms.argumentCount >= MAX_MACRO_ARGUMENT_COUNT) { Macros_ReportErrorPos(ctx, "Maximum number of macro arguments exceeded"); return MacroResult_Header; } @@ -1022,15 +1022,15 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) // TODO: ... // allocate a pool slot for the argument and store its metadata (name and type) there - macro_argument_alloc_result_t res = Macros_AllocateMacroArg(S, idStart, idEnd, argType, S->ms.argumentCount+1, &(S->ms.arguments[S->ms.argumentCount])); + macro_argument_alloc_result_t res = Macros_AllocateMacroArgument(S, idStart, idEnd, argType, S->ms.argumentCount+1, &(S->ms.arguments[S->ms.argumentCount])); switch (res) { - case MacroArgumentAllocResult_Success: + case MacroArgAllocResult_Success: S->ms.argumentCount++; break; - case MacroArgumentAllocResult_PoolLimitExceeded: + case MacroArgAllocResult_PoolLimitExceeded: Macros_ReportErrorPos(ctx, "Too many arguments across simultaneously active macros (argument pool exhausted)"); return MacroResult_Header; - case MacroArgumentAllocResult_DuplicateArgumentName: + case MacroArgAllocResult_DuplicateArgumentName: Macros_ReportErrorPos(ctx, "Duplicate argument name"); return MacroResult_Header; } diff --git a/right/src/str_utils.h b/right/src/str_utils.h index a282bdef8..751f2ac2b 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -57,6 +57,7 @@ uint8_t SegmentLen(string_segment_t str); bool IsEnd(parser_context_t* ctx); + bool IsWhite(parser_context_t* ctx); bool SegmentEqual(string_segment_t str1, string_segment_t str2); bool StrLessOrEqual(const char* a, const char* aEnd, const char* b, const char* bEnd); bool StrEqual(const char* a, const char* aEnd, const char* b, const char* bEnd); From b6dc03b531a00169220445488d4846f2f438792e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 10 Feb 2026 03:18:45 +0100 Subject: [PATCH 030/113] trial 4 --- right/src/macros/vars.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 1f18eb4b2..74f1d44e7 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -50,7 +50,7 @@ typedef enum { macro_variable_t macroVariables[MACRO_VARIABLE_COUNT_MAX]; uint8_t macroVariableCount = 0; -macro_argument_t macroArguments[MAX_MACRO_ARGUMENT_POOL_SIZE]; +macro_argument_t macroArguments[MACRO_ARGUMENT_POOL_SIZE]; // uint8_t macroArgumentCount = 0; static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx); @@ -1125,7 +1125,7 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( // search for existing argument of same owner with the same identifier, error if found for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && - SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = idStart, .end = idEnd }) { + SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = idStart, .end = idEnd })) { return MacroArgAllocResult_DuplicateArgumentName; } } @@ -1137,7 +1137,7 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( macroArguments[i].type = type; macroArguments[i].id = argNumber; macroArguments[i].name = createStringRef(idStart, idEnd); - *outArgRef = i; + *outArgRef = (macro_argref_t) { .poolId = i }; return MacroArgAllocResult_Success; } } From 9b7e657f76cec9104648e45b3174fd4d2df3ef04 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 12 Feb 2026 12:22:06 +0100 Subject: [PATCH 031/113] bool should actually be bool and not float --- right/src/macros/commands.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index bdb30e111..cc91ac31c 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -984,7 +984,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) argType = MacroArgType_Float; } else if (ConsumeToken(ctx, "bool")) { - argType = MacroArgType_Float; + argType = MacroArgType_Bool; } else if (ConsumeToken(ctx, "string")) { argType = MacroArgType_String; @@ -1019,7 +1019,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // check whether the argument name has already been used in this macro - // TODO: ... + // TODO: ... check "locally" // allocate a pool slot for the argument and store its metadata (name and type) there macro_argument_alloc_result_t res = Macros_AllocateMacroArgument(S, idStart, idEnd, argType, S->ms.argumentCount+1, &(S->ms.arguments[S->ms.argumentCount])); @@ -1040,6 +1040,8 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) return MacroResult_Header; +// TODO: when macro ends, free the macroArgs that have been allocated for this owner in vars.c + // Macros_ReportErrorPrintf(ctx->at, "Parsing failed at '%s'?", OneWord(ctx)); } From 03beaf1216ff814016aff4c280842e7689314481 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 13 Feb 2026 14:07:06 +0100 Subject: [PATCH 032/113] some comments after discussion with karel --- right/src/macros/commands.c | 2 +- right/src/macros/core.h | 2 +- right/src/macros/vars.c | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index cc91ac31c..93cbfdcea 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -965,7 +965,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // parse the argument name (identifier) const char *idStart = ctx->at; - const char *idEnd = IdentifierEnd(ctx); + const char *idEnd = IdentifierEnd(ctx); // possibly use ConslmeIdentifier here if (idEnd == idStart) { Macros_ReportErrorPos(ctx, "Expected identifier"); diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 1d94d31b3..67dbfe927 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -170,7 +170,7 @@ bool autoRepeatInitialDelayPassed: 1; macro_autorepeat_state_t autoRepeatPhase: 1; // ---- 4-aligned ---- - macro_argref_t arguments[MAX_MACRO_ARGUMENT_COUNT]; + macro_argref_t arguments[MAX_MACRO_ARGUMENT_COUNT]; // remove these since owner is indicated in the pool macro_usb_keyboard_reports_t reports; diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 74f1d44e7..cee05187d 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1154,3 +1154,5 @@ macro_argument_t *Macros_FindMacroArgumentByName(macro_state_t *owner, const cha } return NULL; } + +// add: Macros_FindMacroArgumentsByIndex() to find the nth argument for this owner in the pool From be4c443a56cbfa2368866befc81803dc931f41c5 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 23 Feb 2026 18:28:30 +0100 Subject: [PATCH 033/113] rework of macro arguments code --- right/src/macros/commands.c | 13 ++---- right/src/macros/core.c | 3 ++ right/src/macros/core.h | 4 +- right/src/macros/vars.c | 82 +++++++++++++++++++++++++++++++++---- right/src/macros/vars.h | 6 +-- 5 files changed, 85 insertions(+), 23 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 93cbfdcea..74f0680a0 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -957,7 +957,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } if (Macros_DryRun) { - // parse macroArg command but ignore it for now + // TODO: actually parse macroArg command while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; @@ -1013,19 +1013,16 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // allocate an argument slot if there is room - if (S->ms.argumentCount >= MAX_MACRO_ARGUMENT_COUNT) { + if (Macros_CountMacroArgumentsByOwner(S) >= MAX_MACRO_ARGUMENT_COUNT) { Macros_ReportErrorPos(ctx, "Maximum number of macro arguments exceeded"); return MacroResult_Header; } - // check whether the argument name has already been used in this macro - // TODO: ... check "locally" - // allocate a pool slot for the argument and store its metadata (name and type) there macro_argument_alloc_result_t res = Macros_AllocateMacroArgument(S, idStart, idEnd, argType, S->ms.argumentCount+1, &(S->ms.arguments[S->ms.argumentCount])); switch (res) { case MacroArgAllocResult_Success: - S->ms.argumentCount++; + // macro arggument successfully allocated break; case MacroArgAllocResult_PoolLimitExceeded: Macros_ReportErrorPos(ctx, "Too many arguments across simultaneously active macros (argument pool exhausted)"); @@ -1039,10 +1036,6 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; return MacroResult_Header; - -// TODO: when macro ends, free the macroArgs that have been allocated for this owner in vars.c - -// Macros_ReportErrorPrintf(ctx->at, "Parsing failed at '%s'?", OneWord(ctx)); } static macro_result_t processWriteCommand(parser_context_t* ctx) diff --git a/right/src/macros/core.c b/right/src/macros/core.c index ce745c755..3b1dfa879 100644 --- a/right/src/macros/core.c +++ b/right/src/macros/core.c @@ -277,6 +277,9 @@ static macro_result_t endMacro(void) EventVector_Set(EventVector_SendUsbReports); } + // Deallocate all macro arguments owned by this macro + Macros_DeallocateMacroArgumentsByOwner(S); + freeLocalScopes(); S->ms.macroSleeping = false; diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 67dbfe927..5c57e17ec 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -17,6 +17,8 @@ // Macros: #define MACRO_CYCLES_TO_POSTPONE 4 + #define MACRO_STATE_INDEX(S) (S - MacroState) + #define MAX_MACRO_NUM 255 #define MACRO_STATE_POOL_SIZE 16 #define MACRO_HISTORY_POOL_SIZE 16 @@ -170,8 +172,6 @@ bool autoRepeatInitialDelayPassed: 1; macro_autorepeat_state_t autoRepeatPhase: 1; // ---- 4-aligned ---- - macro_argref_t arguments[MAX_MACRO_ARGUMENT_COUNT]; // remove these since owner is indicated in the pool - macro_usb_keyboard_reports_t reports; uint8_t argumentCount : 4; diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index cee05187d..5edcc57fa 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1092,6 +1092,8 @@ bool TryExpandMacroTemplateOnce(parser_context_t* ctx) { // macroArguments allocation and processing // ---------------------------------------- +// helper functions to convert to and from StringRefs and StringSegments + string_ref_t createStringRef(const char *start, const char *end) { return (string_ref_t) { .offset = start - (const char*)ValidatedUserConfigBuffer.buffer, @@ -1114,20 +1116,28 @@ const char *stringRefEnd(string_ref_t ref) { return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); } +// Allocates a macro argument in the pool and returns a reference to it. +// Fails if an argument with the same name already exists for this owner, +// or if the pool limit is exceeded. +// +// Returns: +// MacroArgAllocResult_Success and sets *outArgRef to new allocated argument on success. +// MacroArgAllocResult_DuplicateArgumentName if an argument with the same name already exists for this owner. +// MacroArgAllocResult_PoolLimitExceeded if there are no free slots in the pool. + +// TODO: replace owner pointer with macro slot id + macro_argument_alloc_result_t Macros_AllocateMacroArgument( macro_state_t *owner, const char *idStart, const char *idEnd, macro_argument_type_t type, uint8_t argNumber, - macro_argref_t* outArgRef + macro_argref_t *outArgRef ) { // search for existing argument of same owner with the same identifier, error if found - for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { - if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && - SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = idStart, .end = idEnd })) { - return MacroArgAllocResult_DuplicateArgumentName; - } + if (Macros_FindMacroArgumentByName(owner, idStart, idEnd) { + return MacroArgAllocResult_DuplicateArgumentName; } // search for an unused slot in the pool @@ -1135,7 +1145,7 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( if (macroArguments[i].type == MacroArgType_Unused) { macroArguments[i].owner = owner; macroArguments[i].type = type; - macroArguments[i].id = argNumber; + macroArguments[i].idx = argNumber; macroArguments[i].name = createStringRef(idStart, idEnd); *outArgRef = (macro_argref_t) { .poolId = i }; return MacroArgAllocResult_Success; @@ -1145,6 +1155,18 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( return MacroArgAllocResult_PoolLimitExceeded; } +// Deallocates all macro arguments for the given owner. + +void Macros_DeallocateMacroArgumentsByOwner(macro_state_t *owner) { + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner) { + macroArguments[i].type = MacroArgType_Unused; + } + } +} + +// Finds a macro argument by name for the given owner. Returns NULL if not found. + macro_argument_t *Macros_FindMacroArgumentByName(macro_state_t *owner, const char *nameStart, const char *nameEnd) { for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && @@ -1155,4 +1177,48 @@ macro_argument_t *Macros_FindMacroArgumentByName(macro_state_t *owner, const cha return NULL; } -// add: Macros_FindMacroArgumentsByIndex() to find the nth argument for this owner in the pool +// Retrieve number of arguments allocated for the given owner. + +uint8_t Macros_CountMacroArgumentsByOwner(macro_state_t *owner) { + uint8_t count = 0; + + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner) { + count++; + } + } + return count; +} + +// Finds a macro argument index by name for the given owner. Returns 0 if not found. +// Returns the argument index (1-based) if found, or 0 if not found. +// The index is determined by the order of arguments for the same owner in the pool. + +uint8_t Macros_FindMacroArgumentIndexByName(macro_state_t *owner, const char *nameStart, const char *nameEnd) { + uint8_t idx = 0; // argument index for this owner + + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner) { + idx++; + if (SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = nameStart, .end = nameEnd })) { + return idx; + } + } + } + return 0; // return 0 if not found; argument indices are 1-based +} + +// Finds a macro argument by index for the given owner. + +macro_argument_t *Macros_FindMacroArgumentByIndex(macro_state_t *owner, uint8_t argIndex) { + uint8_t idx = 0; // argument index for this owner + + for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { + idx++; + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && + idx == argIndex) { + return ¯oArguments[i]; + } + } + return NULL; +} diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 2e8535bba..895323246 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -52,10 +52,10 @@ } macro_argument_type_t; typedef struct { - macro_state_t *owner; + macro_state_t *owner; // TODO: replace with macro state index macro_argument_type_t type; - uint8_t id; - string_ref_t name; + uint8_t idx; // TODO: we don't need this; index of the argument in the macro's argument list (1-based) + string_ref_t name; // macro argument name (identifier) } macro_argument_t; typedef enum { From db8937a32100733012819d25838d001c3ff92d4d Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 23 Feb 2026 23:10:12 +0100 Subject: [PATCH 034/113] next attempt --- right/src/macros/commands.c | 5 +++-- right/src/macros/core.h | 4 ++-- right/src/macros/vars.c | 40 +++++++++++++++++-------------------- right/src/macros/vars.h | 11 ++++------ 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 74f0680a0..46e891376 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1013,13 +1013,14 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // allocate an argument slot if there is room - if (Macros_CountMacroArgumentsByOwner(S) >= MAX_MACRO_ARGUMENT_COUNT) { + uint8_t argNumber = Macros_CountMacroArgumentsByOwner(MACRO_STATE_SLOT(S)); + if (argNumber >= MAX_MACRO_ARGUMENT_COUNT) { Macros_ReportErrorPos(ctx, "Maximum number of macro arguments exceeded"); return MacroResult_Header; } // allocate a pool slot for the argument and store its metadata (name and type) there - macro_argument_alloc_result_t res = Macros_AllocateMacroArgument(S, idStart, idEnd, argType, S->ms.argumentCount+1, &(S->ms.arguments[S->ms.argumentCount])); + macro_argument_alloc_result_t res = Macros_AllocateMacroArgument(MACRO_STATE_SLOT(S), idStart, idEnd, argType, argNumber+1); switch (res) { case MacroArgAllocResult_Success: // macro arggument successfully allocated diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 5c57e17ec..a30f7b1fc 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -17,7 +17,7 @@ // Macros: #define MACRO_CYCLES_TO_POSTPONE 4 - #define MACRO_STATE_INDEX(S) (S - MacroState) + #define MACRO_STATE_SLOT(S) ((S) - MacroState) #define MAX_MACRO_NUM 255 #define MACRO_STATE_POOL_SIZE 16 @@ -174,7 +174,7 @@ // ---- 4-aligned ---- macro_usb_keyboard_reports_t reports; - uint8_t argumentCount : 4; + uint8_t argumentCount : 4; // TODO: we don't need this; we can calculate it using Macros_CountMacroArgumentsByOwner() bool macroHeadersProcessed : 1; } ms; diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 5edcc57fa..1cdcd914e 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1125,15 +1125,12 @@ const char *stringRefEnd(string_ref_t ref) { // MacroArgAllocResult_DuplicateArgumentName if an argument with the same name already exists for this owner. // MacroArgAllocResult_PoolLimitExceeded if there are no free slots in the pool. -// TODO: replace owner pointer with macro slot id - macro_argument_alloc_result_t Macros_AllocateMacroArgument( - macro_state_t *owner, + uint8_t owner, const char *idStart, const char *idEnd, macro_argument_type_t type, uint8_t argNumber, - macro_argref_t *outArgRef ) { // search for existing argument of same owner with the same identifier, error if found if (Macros_FindMacroArgumentByName(owner, idStart, idEnd) { @@ -1147,7 +1144,6 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( macroArguments[i].type = type; macroArguments[i].idx = argNumber; macroArguments[i].name = createStringRef(idStart, idEnd); - *outArgRef = (macro_argref_t) { .poolId = i }; return MacroArgAllocResult_Success; } } @@ -1155,9 +1151,9 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( return MacroArgAllocResult_PoolLimitExceeded; } -// Deallocates all macro arguments for the given owner. +// Deallocates all macro arguments for the given owner. Used when a macro ends. -void Macros_DeallocateMacroArgumentsByOwner(macro_state_t *owner) { +void Macros_DeallocateMacroArgumentsByOwner(uint8_t owner) { for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner) { macroArguments[i].type = MacroArgType_Unused; @@ -1165,36 +1161,36 @@ void Macros_DeallocateMacroArgumentsByOwner(macro_state_t *owner) { } } -// Finds a macro argument by name for the given owner. Returns NULL if not found. +// Retrieve the number of arguments allocated for the given owner. + +uint8_t Macros_CountMacroArgumentsByOwner(uint8_t owner) { + uint8_t count = 0; -macro_argument_t *Macros_FindMacroArgumentByName(macro_state_t *owner, const char *nameStart, const char *nameEnd) { for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { - if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && - SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = nameStart, .end = nameEnd })) { - return ¯oArguments[i]; + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner) { + count++; } } - return NULL; + return count; } -// Retrieve number of arguments allocated for the given owner. - -uint8_t Macros_CountMacroArgumentsByOwner(macro_state_t *owner) { - uint8_t count = 0; +// Finds a macro argument by name for the given owner. Returns NULL if not found. +macro_argument_t *Macros_FindMacroArgumentByName(uint8_t owner, const char *nameStart, const char *nameEnd) { for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { - if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner) { - count++; + if (macroArguments[i].type != MacroArgType_Unused && macroArguments[i].owner == owner && + SegmentEqual(stringRefToSegment(macroArguments[i].name), (string_segment_t){ .start = nameStart, .end = nameEnd })) { + return ¯oArguments[i]; } } - return count; + return NULL; } // Finds a macro argument index by name for the given owner. Returns 0 if not found. // Returns the argument index (1-based) if found, or 0 if not found. // The index is determined by the order of arguments for the same owner in the pool. -uint8_t Macros_FindMacroArgumentIndexByName(macro_state_t *owner, const char *nameStart, const char *nameEnd) { +uint8_t Macros_FindMacroArgumentIndexByName(uint8_t owner, const char *nameStart, const char *nameEnd) { uint8_t idx = 0; // argument index for this owner for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { @@ -1210,7 +1206,7 @@ uint8_t Macros_FindMacroArgumentIndexByName(macro_state_t *owner, const char *na // Finds a macro argument by index for the given owner. -macro_argument_t *Macros_FindMacroArgumentByIndex(macro_state_t *owner, uint8_t argIndex) { +macro_argument_t *Macros_FindMacroArgumentByIndex(uint8_t owner, uint8_t argIndex) { uint8_t idx = 0; // argument index for this owner for (uint8_t i = 0; i < MACRO_ARGUMENT_POOL_SIZE; i++) { diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 895323246..3a4007588 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -52,9 +52,11 @@ } macro_argument_type_t; typedef struct { - macro_state_t *owner; // TODO: replace with macro state index + uint8_t owner; // MACRO_STATE_SLOT() of the macro that owns this argument macro_argument_type_t type; - uint8_t idx; // TODO: we don't need this; index of the argument in the macro's argument list (1-based) + uint8_t idx; // index of the argument in the macro's argument list (1-based) + // (we could always calculate idx by looping through the pool, + // but returning argument+index separately everywhere becomes a nightmare...) string_ref_t name; // macro argument name (identifier) } macro_argument_t; @@ -64,11 +66,6 @@ MacroArgAllocResult_DuplicateArgumentName, } macro_argument_alloc_result_t; - typedef struct { - uint8_t poolId; - } macro_argref_t; - - // Variables: // Functions: From 24276bee9a5ff1667f872f6ab70b94c51e450eb3 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 23 Feb 2026 23:58:23 +0100 Subject: [PATCH 035/113] corrections --- right/src/macros/core.c | 3 ++- right/src/macros/vars.c | 10 +++++----- right/src/macros/vars.h | 7 +++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/right/src/macros/core.c b/right/src/macros/core.c index 3b1dfa879..10dffe5e9 100644 --- a/right/src/macros/core.c +++ b/right/src/macros/core.c @@ -10,6 +10,7 @@ #include "macros/scancode_commands.h" #include "macros/status_buffer.h" #include "macros/typedefs.h" +#include "macros/vars.h" #include "module.h" #include "postponer.h" #include @@ -278,7 +279,7 @@ static macro_result_t endMacro(void) } // Deallocate all macro arguments owned by this macro - Macros_DeallocateMacroArgumentsByOwner(S); + Macros_DeallocateMacroArgumentsByOwner(MACRO_STATE_SLOT(S)); freeLocalScopes(); diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 1cdcd914e..f5a92319b 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1094,25 +1094,25 @@ bool TryExpandMacroTemplateOnce(parser_context_t* ctx) { // helper functions to convert to and from StringRefs and StringSegments -string_ref_t createStringRef(const char *start, const char *end) { +static string_ref_t createStringRef(const char *start, const char *end) { return (string_ref_t) { .offset = start - (const char*)ValidatedUserConfigBuffer.buffer, .len = (uint8_t)(end - start), }; } -string_segment_t stringRefToSegment(string_ref_t ref) { +static string_segment_t stringRefToSegment(string_ref_t ref) { return (string_segment_t) { .start = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset), .end = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len), }; } -const char *stringRefStart(string_ref_t ref) { +static const char *stringRefStart(string_ref_t ref) { return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset); } -const char *stringRefEnd(string_ref_t ref) { +staticconst char *stringRefEnd(string_ref_t ref) { return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); } @@ -1130,7 +1130,7 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( const char *idStart, const char *idEnd, macro_argument_type_t type, - uint8_t argNumber, + uint8_t argNumber ) { // search for existing argument of same owner with the same identifier, error if found if (Macros_FindMacroArgumentByName(owner, idStart, idEnd) { diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 3a4007588..48521473b 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -82,4 +82,11 @@ void Macros_SerializeVar(char* buffer, uint8_t len, macro_variable_t var); bool TryExpandMacroTemplateOnce(parser_context_t* ctx); + macro_argument_alloc_result_t Macros_AllocateMacroArgument(uint8_t owner, const char *idStart, const char *idEnd, macro_argument_type_t type, uint8_t argNumber); + void Macros_DeallocateMacroArgumentsByOwner(uint8_t owner); + uint8_t Macros_CountMacroArgumentsByOwner(uint8_t owner); + macro_argument_t *Macros_FindMacroArgumentByName(uint8_t owner, const char *nameStart, const char *nameEnd); + uint8_t Macros_FindMacroArgumentIndexByName(uint8_t owner, const char *nameStart, const char *nameEnd); + macro_argument_t *Macros_FindMacroArgumentByIndex(uint8_t owner, uint8_t argIndex); + #endif From 27220af3d3760b983dac03a2c702662ec8374665 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 00:16:16 +0100 Subject: [PATCH 036/113] syntax fix --- right/src/macros/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index f5a92319b..853cbd849 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1112,7 +1112,7 @@ static const char *stringRefStart(string_ref_t ref) { return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset); } -staticconst char *stringRefEnd(string_ref_t ref) { +static const char *stringRefEnd(string_ref_t ref) { return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); } From fbb96d618d0c9a5528c122c95e13b5596754174f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 00:24:10 +0100 Subject: [PATCH 037/113] missing parenthesis --- right/src/macros/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 853cbd849..4f52468ae 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1133,7 +1133,7 @@ macro_argument_alloc_result_t Macros_AllocateMacroArgument( uint8_t argNumber ) { // search for existing argument of same owner with the same identifier, error if found - if (Macros_FindMacroArgumentByName(owner, idStart, idEnd) { + if (Macros_FindMacroArgumentByName(owner, idStart, idEnd)) { return MacroArgAllocResult_DuplicateArgumentName; } From b4fa0f0401c98f25e4d9db83b699f61140c5b5e4 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 11:04:07 +0100 Subject: [PATCH 038/113] prevent $macroArg/.1 bug --- right/src/macros/commands.c | 2 +- right/src/macros/vars.c | 4 ++-- right/src/str_utils.c | 31 +++++++++++++++++++++++-------- right/src/str_utils.h | 2 ++ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 46e891376..aa36be069 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -965,7 +965,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // parse the argument name (identifier) const char *idStart = ctx->at; - const char *idEnd = IdentifierEnd(ctx); // possibly use ConslmeIdentifier here + const char *idEnd = IdentifierEnd(ctx); // possibly use ConsumeIdentifier here if (idEnd == idStart) { Macros_ReportErrorPos(ctx, "Expected identifier"); diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 4f52468ae..bcfcda40d 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1026,8 +1026,8 @@ void MacroVariables_RunTests(void) { } static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { - ConsumeUntilDot(ctx); - uint8_t argId = Macros_ConsumeInt(ctx); + ConsumeOneDot(ctx); + uint8_t argId = Macros_ConsumeInt(ctx); // TODO: parse macro argument names in addition to numbers if (S->ms.currentMacroArgumentOffset == 0) { Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d, because this macro doesn't seem to have arguments assigned!", argId); diff --git a/right/src/str_utils.c b/right/src/str_utils.c index b6f83f496..edcb522ab 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -207,12 +207,14 @@ const char* ConsumedToken(parser_context_t* ctx) { const char* at = ctx->at; - at--; + if(at > ctx->begin) { + at--; + } - while (*at <= 32) { + while (at > ctx->begin && *at <= 32) { at--; } - while (*at > 32 && *at != '.') { + while (at > ctx->begin && *at > 32 && *at != '.') { at--; } @@ -357,7 +359,7 @@ void ConsumeUntilDot(parser_context_t* ctx) // If whitespace is found, and failOnWhite is true, report an error. void ConsumeUntilCharOrWhite(parser_context_t* ctx, char c, bool failOnWhite) { - while(*ctx->at > 32 && *ctx->at != c && !isEnd(ctx)) { + while(!isEnd(ctx) && *ctx->at > 32 && *ctx->at != c) { ctx->at++; } if (IsEnd(ctx)) { @@ -379,10 +381,23 @@ void ConsumeUntilDot(parser_context_t* ctx) ConsumeUntilCharOrWhite(ctx, '.', true); } -//void ConsumeUntilColon(parser_context_t* ctx) -//{ -// ConsumeUntilCharOrWhite(ctx, ':', false); -//} +// will not consume whitespace after the character. +// returns true if the character was found, false otherwise. +bool ConsumeOneChar(parser_context_t* ctx, char c) +{ + if (!isEnd(ctx) && *ctx->at == c) { + ctx->at++; + return true; + } + return false; +} + +// will not consume whitespace after the dot. +// returns true if the dot was found, false otherwise. +bool ConsumeOneDot(parser_context_t* ctx) +{ + return ConsumeOneChar(ctx, '.'); +} bool TokenMatches(const char *a, const char *aEnd, const char *b) { diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 751f2ac2b..2e4a0b0fa 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -83,6 +83,8 @@ const char* NextTok(const char* cmd, const char *cmdEnd); const char* NextCmd(const char* cmd, const char *cmdEnd); const char* CmdEnd(const char* cmd, const char *cmdEnd); + bool ConsumeOneChar(parser_context_t* ctx, char c); + bool ConsumeOneDot(parser_context_t* ctx); void ConsumeUntilDot(parser_context_t* ctx); void ConsumeWhiteAt(parser_context_t* ctx, const char* at); const char* SkipWhite(const char* cmd, const char *cmdEnd); From 21f61442fe6434e6afbc907e8170648862bde1dc Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 11:05:58 +0100 Subject: [PATCH 039/113] add error message for missing '.' after $macroArg --- right/src/macros/vars.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index bcfcda40d..4c03b4b83 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1026,7 +1026,11 @@ void MacroVariables_RunTests(void) { } static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { - ConsumeOneDot(ctx); + if (!ConsumeOneDot(ctx)) { + Macros_ReportErrorTok(ctx, "Expected '.' after 'macroArg'"); + return noneVar(); + }; + uint8_t argId = Macros_ConsumeInt(ctx); // TODO: parse macro argument names in addition to numbers if (S->ms.currentMacroArgumentOffset == 0) { From 139f987fdf70b46fc3b83412a793bde87bb2eb7e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 11:41:16 +0100 Subject: [PATCH 040/113] better parsing & errors --- right/src/macros/vars.c | 9 +++++++-- right/src/str_utils.c | 8 ++++++++ right/src/str_utils.h | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 4c03b4b83..bc7017a15 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1027,11 +1027,16 @@ void MacroVariables_RunTests(void) { static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { if (!ConsumeOneDot(ctx)) { - Macros_ReportErrorTok(ctx, "Expected '.' after 'macroArg'"); + Macros_ReportErrorPos(ctx, "Expected '.' after '$macroArg'"); return noneVar(); }; - uint8_t argId = Macros_ConsumeInt(ctx); // TODO: parse macro argument names in addition to numbers + if (!IsDigit(ctx)) { + // TODO: parse macro argument name and convert to number + Macros_ReportErrorPos(ctx, "Expected argument number after '$macroArg.'"); + return noneVar(); + } + uint8_t argId = Macros_ConsumeInt(ctx); if (S->ms.currentMacroArgumentOffset == 0) { Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d, because this macro doesn't seem to have arguments assigned!", argId); diff --git a/right/src/str_utils.c b/right/src/str_utils.c index edcb522ab..d04bdba94 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -133,6 +133,14 @@ bool IsEnd(parser_context_t* ctx) { return isEnd(ctx); } +static bool isDigit(parser_context_t* ctx) { + return *ctx->at >= '0' && *ctx->at <= '9'; +} + +bool IsDigit(parser_context_t* ctx) { + return isDigit(ctx); +} + static bool isCommentLeader(parser_context_t* ctx) { return ctx->at + 1 < ctx->end && ctx->at[0] == '/' && ctx->at[1] == '/'; } diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 2e4a0b0fa..82028aaf3 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -57,6 +57,7 @@ uint8_t SegmentLen(string_segment_t str); bool IsEnd(parser_context_t* ctx); + bool IsDigit(parser_context_t* ctx); bool IsWhite(parser_context_t* ctx); bool SegmentEqual(string_segment_t str1, string_segment_t str2); bool StrLessOrEqual(const char* a, const char* aEnd, const char* b, const char* bEnd); From 23f8094b529152d67df8d6410e688ba4f4fffca8 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 12:26:39 +0100 Subject: [PATCH 041/113] some comments to guide next TODOs --- right/src/macros/vars.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index bc7017a15..1d23cfc30 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -100,7 +100,7 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) macro_variable_t res = { .type = MacroVariableType_Int, .asInt = 0 }; bool numFound = false; - while(*ctx->at > 47 && *ctx->at < 58 && ctx->at < ctx->end) { + while(*ctx->at >= '0' && *ctx->at <= '9' && ctx->at < ctx->end) { res.asInt = res.asInt*10 + ((uint8_t)(*ctx->at))-48; ctx->at++; numFound = true; @@ -1032,7 +1032,9 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { }; if (!IsDigit(ctx)) { - // TODO: parse macro argument name and convert to number + // TODO: parse macro argument name and convert to number. + // basically, Macros_FindMacroArgumentByName(), error if not found. + // if found, consume the name and retrieve argument number and argument type. Macros_ReportErrorPos(ctx, "Expected argument number after '$macroArg.'"); return noneVar(); } @@ -1058,6 +1060,8 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { .nestingBound = ctx->nestingBound, }; + // TODO: if argument type is known, parse value accordingly. + // if type == any, then TryExpandMacroTemplateOnce(). macro_variable_t res = consumeValue(&varCtx); return res; From f8340d9c5cbcd7ab15cdbef98bdc6a9016e0573b Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 14:34:29 +0100 Subject: [PATCH 042/113] better DryRun for macroArg command --- right/src/macros/commands.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 4a84bf424..8a975f752 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -952,13 +952,6 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) return MacroResult_Finished; } - if (Macros_DryRun) { - // TODO: actually parse macroArg command - - while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; - - return MacroResult_Header; - } // parse the argument name (identifier) const char *idStart = ctx->at; const char *idEnd = IdentifierEnd(ctx); // possibly use ConsumeIdentifier here From 206141bbb965e2f3c89bf1d812b001acf31d1209 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 16:55:06 +0100 Subject: [PATCH 043/113] first attempt at named arguments parsing --- right/src/macros/vars.c | 98 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 1d23cfc30..b0cf1f7e5 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -101,7 +101,7 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) bool numFound = false; while(*ctx->at >= '0' && *ctx->at <= '9' && ctx->at < ctx->end) { - res.asInt = res.asInt*10 + ((uint8_t)(*ctx->at))-48; + res.asInt = res.asInt*10 + ((uint8_t)(*ctx->at))-'0'; ctx->at++; numFound = true; } @@ -111,8 +111,8 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) ctx->at++; float b = 0.1; - while(*ctx->at > 47 && *ctx->at < 58 && ctx->at < ctx->end) { - res.asFloat += (((uint8_t)(*ctx->at))-48)*b; + while(*ctx->at >= '0' && *ctx->at <= '9' && ctx->at < ctx->end) { + res.asFloat += (((uint8_t)(*ctx->at))-'0')*b; b = b*0.1f; ctx->at++; numFound = true; @@ -127,6 +127,19 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) return res; } +static macro_variable_t consumeBool(parser_context_t* ctx) +{ + if (ConsumeToken(ctx, "false")) { + return (macro_variable_t){ .type = MacroVariableType_Bool, .asBool = false }; + } + else if (ConsumeToken(ctx, "true")) { + return (macro_variable_t){ .type = MacroVariableType_Bool, .asBool = true }; + } + + Macros_ReportErrorTok(ctx, "Boolean value (true/false) expected"); + return noneVar(); +} + static macro_variable_t consumeStringLiteral(parser_context_t* ctx) { const char* stringStart = ctx->at; @@ -145,7 +158,6 @@ static macro_variable_t consumeStringLiteral(parser_context_t* ctx) return stringVar((string_ref_t){ .offset = offset, .len = len }); } - macro_variable_t* Macros_ConsumeExistingWritableVariable(parser_context_t* ctx) { if (Macros_DryRun) { @@ -1026,31 +1038,75 @@ void MacroVariables_RunTests(void) { } static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { + uint8_t argIdx; + macro_argument_type_t argType; + if (!ConsumeOneDot(ctx)) { Macros_ReportErrorPos(ctx, "Expected '.' after '$macroArg'"); return noneVar(); }; if (!IsDigit(ctx)) { + // argument accessed by name, e.g., $macroArg.my_param + + const char *idStart = ctx->at; + const char *idEnd = IdentifierEnd(ctx); + if (idStart == idEnd) { + Macros_ReportErrorPos(ctx, "Expected identifier after '$macroArg.'"); + return noneVar(); + } + // TODO: parse macro argument name and convert to number. // basically, Macros_FindMacroArgumentByName(), error if not found. // if found, consume the name and retrieve argument number and argument type. - Macros_ReportErrorPos(ctx, "Expected argument number after '$macroArg.'"); - return noneVar(); + + macro_argument_t *arg = Macros_FindMacroArgumentByName(MACRO_STATE_SLOT(S), idStart, idEnd); + if (arg == NULL) { + Macros_ReportErrorPrintf(ctx, "Argument with name '$macroArg.%s' not found!", OneWord(ctx)); + return noneVar(); + } + argIdx = arg->idx; + argType = arg->type; + ConsumeWhiteAt(ctx, idEnd); + } else { + // argument accessed by number, e.g., $macroArg.1 + argIdx = Macros_ConsumeInt(ctx); + macro_argument_t *arg = Macros_FindMacroArgumentByIndex(MACRO_STATE_SLOT(S), argIdx); + if (arg == NULL) { + argType = MacroArgType_Any; + // TODO: assume type 'any' for this argument; + // it has probably not been declared in any macroArg statement. + Macros_ReportErrorPrintf(ctx->at, "Argument with id %d not found!", argIdx); + return noneVar(); + } else { + argType = arg->type; + } } - uint8_t argId = Macros_ConsumeInt(ctx); + + // at this point, we have argument index and argument type. if (S->ms.currentMacroArgumentOffset == 0) { Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d, because this macro doesn't seem to have arguments assigned!", argId); } - string_segment_t str = ParseMacroArgument(S->ms.currentMacroArgumentOffset, argId); + string_segment_t str = ParseMacroArgument(S->ms.currentMacroArgumentOffset, argIdx); if (str.start == NULL) { Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d. Argument not found!", argId); return noneVar(); } + // TODO: if argument type is known, parse value accordingly. + // if type == any, then expand?? + +// if (argType == MacroArgType_Any) { +// Trace_Printc("Argument type is any, trying to parse as template."); +// PushParserContext(ctx, str.start, str.start, str.end); +// if (Macros_ParserError) { +// return noneVar(); +// } +// } + parser_context_t varCtx = (parser_context_t) { .at = str.start, .begin = str.start, @@ -1060,10 +1116,30 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { .nestingBound = ctx->nestingBound, }; - // TODO: if argument type is known, parse value accordingly. - // if type == any, then TryExpandMacroTemplateOnce(). - macro_variable_t res = consumeValue(&varCtx); +// old code: macro_variable_t res = consumeValue(&varCtx); +// new code: + if (argType == MacroArgType_Any) { + // for type 'any', consume the value the "old way" + // (compatibility with existing macros that don't declare their argument types). + macro_variable_t res = consumeValue(&varCtx); + } else { + // for declared types, consume the value according to type. + switch (argType) { + case MacroArgType_Int: + return consumeNumericValue(&varCtx); // should be: consumeIntValue() + case MacroArgType_Float: + return consumeNumericValue(&varCtx); // should be: consumeFloatValue() + case MacroArgType_Bool: + return consumeBool(&varCtx); + case MacroArgType_String: { + return consumeStringLiteral(&varCtx); + } + default: + Macros_ReportErrorNum("Unexpected argument type:", argType, NULL); + return noneVar(); + } + } return res; } From 8e250ec4ac380be95d4c87a0c79bb0d44f77aa29 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 24 Feb 2026 18:11:20 +0100 Subject: [PATCH 044/113] fix compilation --- right/src/macros/vars.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index b0cf1f7e5..f7762d2a1 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1062,7 +1062,7 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { macro_argument_t *arg = Macros_FindMacroArgumentByName(MACRO_STATE_SLOT(S), idStart, idEnd); if (arg == NULL) { - Macros_ReportErrorPrintf(ctx, "Argument with name '$macroArg.%s' not found!", OneWord(ctx)); + Macros_ReportErrorPrintf(ctx->at, "Argument with name '$macroArg.%s' not found!", OneWord(ctx)); return noneVar(); } argIdx = arg->idx; @@ -1086,13 +1086,13 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // at this point, we have argument index and argument type. if (S->ms.currentMacroArgumentOffset == 0) { - Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d, because this macro doesn't seem to have arguments assigned!", argId); + Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d, because this macro doesn't seem to have arguments assigned!", argIdx); } string_segment_t str = ParseMacroArgument(S->ms.currentMacroArgumentOffset, argIdx); if (str.start == NULL) { - Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d. Argument not found!", argId); + Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d. Argument not found!", argIdx); return noneVar(); } @@ -1122,7 +1122,7 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { if (argType == MacroArgType_Any) { // for type 'any', consume the value the "old way" // (compatibility with existing macros that don't declare their argument types). - macro_variable_t res = consumeValue(&varCtx); + return consumeValue(&varCtx); } else { // for declared types, consume the value according to type. switch (argType) { @@ -1140,7 +1140,6 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { return noneVar(); } } - return res; } static bool expandArgumentInplace(parser_context_t* ctx, uint8_t argNumber) { From d085aeff0083136b15526dda485027ed887bd0d2 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 09:55:59 +0100 Subject: [PATCH 045/113] spelling error in comment --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 8a975f752..29e491c6c 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1012,7 +1012,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) macro_argument_alloc_result_t res = Macros_AllocateMacroArgument(MACRO_STATE_SLOT(S), idStart, idEnd, argType, argNumber+1); switch (res) { case MacroArgAllocResult_Success: - // macro arggument successfully allocated + // macro argument successfully allocated break; case MacroArgAllocResult_PoolLimitExceeded: Macros_ReportErrorPos(ctx, "Too many arguments across simultaneously active macros (argument pool exhausted)"); From bc1ecb6c9b70d75c01377eaf90c6a957c2cb0be6 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 13:47:52 +0100 Subject: [PATCH 046/113] fix some TODOs; compatibility-parsing of undeclared $macroArg.1 --- right/src/macros/commands.c | 7 ++++++- right/src/macros/core.h | 2 +- right/src/macros/vars.c | 31 +++++++------------------------ right/src/macros/vars.h | 3 ++- 4 files changed, 16 insertions(+), 27 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 29e491c6c..a85c6e716 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1001,7 +1001,12 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) argType = MacroArgType_Any; } - // allocate an argument slot if there is room + // The following two blocks (counting arguments, then allocating argument) + // could be optimised to walk the macro arg pool only once. + // The current implementation is straightforward to read, and the slight performance + // decrease at the start of a macro (when macroArg commands are processed) seems negligible. + + // check whether we are exceeding the arguments for this macro uint8_t argNumber = Macros_CountMacroArgumentsByOwner(MACRO_STATE_SLOT(S)); if (argNumber >= MAX_MACRO_ARGUMENT_COUNT) { Macros_ReportErrorPos(ctx, "Maximum number of macro arguments exceeded"); diff --git a/right/src/macros/core.h b/right/src/macros/core.h index fd04f76c3..0d0e34c72 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -176,7 +176,7 @@ // ---- 4-aligned ---- macro_usb_keyboard_reports_t reports; - uint8_t argumentCount : 4; // TODO: we don't need this; we can calculate it using Macros_CountMacroArgumentsByOwner() + //uint8_t argumentCount : 4; // TODO: we don't need this; we can calculate it using Macros_CountMacroArgumentsByOwner() bool macroHeadersProcessed : 1; } ms; diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index f7762d2a1..561f07a67 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1056,10 +1056,8 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { return noneVar(); } - // TODO: parse macro argument name and convert to number. - // basically, Macros_FindMacroArgumentByName(), error if not found. - // if found, consume the name and retrieve argument number and argument type. - + // parse macro argument name and convert to number; error if not found. + // if found, consume the name and retrieve argument number and argument type. macro_argument_t *arg = Macros_FindMacroArgumentByName(MACRO_STATE_SLOT(S), idStart, idEnd); if (arg == NULL) { Macros_ReportErrorPrintf(ctx->at, "Argument with name '$macroArg.%s' not found!", OneWord(ctx)); @@ -1070,14 +1068,13 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { ConsumeWhiteAt(ctx, idEnd); } else { // argument accessed by number, e.g., $macroArg.1 + argIdx = Macros_ConsumeInt(ctx); macro_argument_t *arg = Macros_FindMacroArgumentByIndex(MACRO_STATE_SLOT(S), argIdx); if (arg == NULL) { + // if not found (= undeclared), assume type 'any' for this argument + // (backwards compatibility to macro arguments without macroArg declaration). argType = MacroArgType_Any; - // TODO: assume type 'any' for this argument; - // it has probably not been declared in any macroArg statement. - Macros_ReportErrorPrintf(ctx->at, "Argument with id %d not found!", argIdx); - return noneVar(); } else { argType = arg->type; } @@ -1096,17 +1093,6 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { return noneVar(); } - // TODO: if argument type is known, parse value accordingly. - // if type == any, then expand?? - -// if (argType == MacroArgType_Any) { -// Trace_Printc("Argument type is any, trying to parse as template."); -// PushParserContext(ctx, str.start, str.start, str.end); -// if (Macros_ParserError) { -// return noneVar(); -// } -// } - parser_context_t varCtx = (parser_context_t) { .at = str.start, .begin = str.start, @@ -1116,9 +1102,6 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { .nestingBound = ctx->nestingBound, }; -// old code: macro_variable_t res = consumeValue(&varCtx); - -// new code: if (argType == MacroArgType_Any) { // for type 'any', consume the value the "old way" // (compatibility with existing macros that don't declare their argument types). @@ -1127,9 +1110,9 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // for declared types, consume the value according to type. switch (argType) { case MacroArgType_Int: - return consumeNumericValue(&varCtx); // should be: consumeIntValue() + return consumeNumericValue(&varCtx); // TODO: should be: consumeIntValue() case MacroArgType_Float: - return consumeNumericValue(&varCtx); // should be: consumeFloatValue() + return consumeNumericValue(&varCtx); // TODO: should be: consumeFloatValue() case MacroArgType_Bool: return consumeBool(&varCtx); case MacroArgType_String: { diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 48521473b..e472ed8ec 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -56,7 +56,8 @@ macro_argument_type_t type; uint8_t idx; // index of the argument in the macro's argument list (1-based) // (we could always calculate idx by looping through the pool, - // but returning argument+index separately everywhere becomes a nightmare...) + // but returning argument+index separately everywhere becomes + // a nightmare...) string_ref_t name; // macro argument name (identifier) } macro_argument_t; From 11d2d790fb93b98056d5e22d9f953f6dc3bfbb9f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 14:43:11 +0100 Subject: [PATCH 047/113] numerical value parsing per declared type --- right/src/macros/vars.c | 30 +++++++++++++++++++++++++++--- right/src/macros/vars.h | 6 ++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 561f07a67..c36d29aa3 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -95,7 +95,7 @@ static macro_variable_t noneVar() return (macro_variable_t) { .asInt = 1, .type = MacroVariableType_None }; } -static macro_variable_t consumeNumericValue(parser_context_t* ctx) +static macro_variable_t consumeNumericValueOfType(parser_context_t* ctx, macro_numericalvalue_type_t expectedType) { macro_variable_t res = { .type = MacroVariableType_Int, .asInt = 0 }; @@ -106,6 +106,10 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) numFound = true; } if (*ctx->at == '.') { + if (expectedType == MacroNumericalValueType_Int) { + Macros_ReportErrorTok(ctx, "Integer value expected"); + return noneVar(); + } res.type = MacroVariableType_Float; res.asFloat = (float) res.asInt; ctx->at++; @@ -123,10 +127,30 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) return noneVar(); } + if (expectedType == MacroNumericalValueType_Float && res.type == MacroVariableType_Int) { + res.asFloat = (float) res.asInt; + res.type = MacroVariableType_Float; + } + ConsumeWhite(ctx); return res; } +static macro_variable_t consumeIntValue(parser_context_t* ctx) +{ + return consumeNumericValueOfType(ctx, MacroNumericalValueType_Int; +} + +static macro_variable_t consumeFloatValue(parser_context_t* ctx) +{ + return consumeNumericValueOfType(ctx, MacroNumericalValueType_Float); +} + +static macro_variable_t consumeNumericValue(parser_context_t* ctx) +{ + return consumeNumericValueOfType(ctx, MacroNumericalValueType_Any); +} + static macro_variable_t consumeBool(parser_context_t* ctx) { if (ConsumeToken(ctx, "false")) { @@ -1110,9 +1134,9 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // for declared types, consume the value according to type. switch (argType) { case MacroArgType_Int: - return consumeNumericValue(&varCtx); // TODO: should be: consumeIntValue() + return consumeIntValue(&varCtx); case MacroArgType_Float: - return consumeNumericValue(&varCtx); // TODO: should be: consumeFloatValue() + return consumeFloatValue(&varCtx); case MacroArgType_Bool: return consumeBool(&varCtx); case MacroArgType_String: { diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index e472ed8ec..e25fe68b3 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -21,6 +21,12 @@ // Typedefs: + typedef enum { + MacroNumericalValueType_Any = 0, + MacroNumericalValueType_Int, + MacroNumericalValueType_Float, + } macro_numericalvalue_type_t; + typedef enum { MacroVariableType_Int, MacroVariableType_Float, From 894ab23c85d580f67c3dfae9a97842462d92ddc8 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 14:52:21 +0100 Subject: [PATCH 048/113] syntax fix --- right/src/macros/vars.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index c36d29aa3..25cd3590d 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -138,7 +138,7 @@ static macro_variable_t consumeNumericValueOfType(parser_context_t* ctx, macro_n static macro_variable_t consumeIntValue(parser_context_t* ctx) { - return consumeNumericValueOfType(ctx, MacroNumericalValueType_Int; + return consumeNumericValueOfType(ctx, MacroNumericalValueType_Int); } static macro_variable_t consumeFloatValue(parser_context_t* ctx) @@ -1203,13 +1203,15 @@ static string_segment_t stringRefToSegment(string_ref_t ref) { }; } -static const char *stringRefStart(string_ref_t ref) { - return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset); -} +// currently unused: +//static const char *stringRefStart(string_ref_t ref) { +// return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset); +//} -static const char *stringRefEnd(string_ref_t ref) { - return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); -} +// currently unused: +//static const char *stringRefEnd(string_ref_t ref) { +// return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); +//} // Allocates a macro argument in the pool and returns a reference to it. // Fails if an argument with the same name already exists for this owner, From 73186d68cfa0a270da7937ed7a1841871f1b52d3 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 15:20:07 +0100 Subject: [PATCH 049/113] consume descriptive comment as string literal --- right/src/macros/commands.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index a85c6e716..835774b42 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1028,7 +1028,8 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) } // rest of command is descriptive label, ignore. TODO: Should be parsed as string literal. - while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; + //while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; + (void) consumeStringLiteral(ctx); return MacroResult_Header; } From 2efc6dbf06595e7ca35664b1b390e85314905c9c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 16:01:17 +0100 Subject: [PATCH 050/113] parse macroArg description as string token --- right/src/macros/commands.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 835774b42..d1cbddbff 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1027,9 +1027,9 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) return MacroResult_Header; } - // rest of command is descriptive label, ignore. TODO: Should be parsed as string literal. + // rest of command is descriptive label, ignore. TODO: Should be parsed as string token. //while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; - (void) consumeStringLiteral(ctx); + Macros_ConsumeStringToken(ctx); return MacroResult_Header; } From ff5f44d8e0781fe9b12f004b65a43b1f19bdfb5a Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 18:44:32 +0100 Subject: [PATCH 051/113] some notes on where $macroArg.xxx should be parsed for keyid and scancode types, and where template expansion should probably be initiated for 'any' type. --- right/src/macros/commands.c | 2 ++ right/src/macros/keyid_parser.c | 1 + right/src/macros/scancode_commands.c | 2 ++ right/src/macros/vars.c | 5 ++++- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index d1cbddbff..55ce12000 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1373,6 +1373,8 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat uint8_t Macros_TryConsumeKeyId(parser_context_t* ctx) { + // TODO: allow $macroArg.xxx for type keyId here as well + uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); if (keyId == 255 && isNUM(ctx)) { diff --git a/right/src/macros/keyid_parser.c b/right/src/macros/keyid_parser.c index ba53fcf21..8514e8add 100644 --- a/right/src/macros/keyid_parser.c +++ b/right/src/macros/keyid_parser.c @@ -172,6 +172,7 @@ uint8_t MacroKeyIdParser_TryConsumeKeyId(parser_context_t* ctx) const char* end1 = IdentifierEnd(ctx); const lookup_record_t* record = lookup(0, lookup_size-1, ctx->at, end1); + // TODO: WHY this second attempt with dot?? // if failed, try consume with dot if (record == NULL && *end1 == '.' && end1+1 < ctx->end) { CTX_COPY(ctx2, *ctx); diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index 353305459..605804a6f 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -480,6 +480,8 @@ macro_result_t Macros_ProcessTextAction(void) static macro_action_t decodeKeyAndConsume(parser_context_t* ctx, macro_sub_action_t defaultSubAction) { + // TODO: allow $macroArg.xxx for type scancode ("modded scancode") here as well + macro_action_t action; const char* end = TokEnd(ctx->at, ctx->end); MacroShortcutParser_Parse(ctx->at, end, defaultSubAction, &action, NULL); diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 25cd3590d..6d19ebe51 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1129,7 +1129,10 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { if (argType == MacroArgType_Any) { // for type 'any', consume the value the "old way" // (compatibility with existing macros that don't declare their argument types). - return consumeValue(&varCtx); + // but that doesn't work well ($macroArg vs. ¯oArg). + + // TODO: really, we should probably handle template expansion here (treat is as ¯oArg). + return consumeValue(&varCtx); // this has limited functionality } else { // for declared types, consume the value according to type. switch (argType) { From b3f4e2072e5e912c0b78dc216f3ed608ee3a2190 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 25 Feb 2026 19:08:32 +0100 Subject: [PATCH 052/113] more TODO comments --- right/src/macros/vars.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 6d19ebe51..28c95dc87 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -107,7 +107,7 @@ static macro_variable_t consumeNumericValueOfType(parser_context_t* ctx, macro_n } if (*ctx->at == '.') { if (expectedType == MacroNumericalValueType_Int) { - Macros_ReportErrorTok(ctx, "Integer value expected"); + Macros_ReportErrorTok(ctx, "Integer value expected but found:"); return noneVar(); } res.type = MacroVariableType_Float; @@ -123,7 +123,7 @@ static macro_variable_t consumeNumericValueOfType(parser_context_t* ctx, macro_n } } if (!numFound) { - Macros_ReportErrorTok(ctx, "Numeric value expected"); + Macros_ReportErrorTok(ctx, "Numeric value expected but found:"); return noneVar(); } @@ -160,7 +160,7 @@ static macro_variable_t consumeBool(parser_context_t* ctx) return (macro_variable_t){ .type = MacroVariableType_Bool, .asBool = true }; } - Macros_ReportErrorTok(ctx, "Boolean value (true/false) expected"); + Macros_ReportErrorTok(ctx, "Boolean value (true/false) expected but found:"); return noneVar(); } @@ -430,6 +430,7 @@ static macro_variable_t consumeDollarExpression(parser_context_t* ctx) static macro_variable_t consumeValue(parser_context_t* ctx) { + // TODO: this shouldn't be here, when properly handled by $macroArg :any type. if (*ctx->at == '&') { TryExpandMacroTemplateOnce(ctx); if (Macros_ParserError) { From cb8c578262c6e216750c28becd7edca3c0457764 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 26 Feb 2026 09:12:57 +0100 Subject: [PATCH 053/113] cleanup --- right/src/macros/commands.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 55ce12000..39adf60c3 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -943,10 +943,6 @@ static macro_result_t processPlayMacroCommand(parser_context_t* ctx) static macro_result_t processMacroArgCommand(parser_context_t* ctx) { - uint16_t stringOffset = 0; - uint16_t textIndex = 0; - uint16_t textSubIndex = 0; - if (S->ms.macroHeadersProcessed) { Macros_ReportErrorPos(ctx, "macroArg commands must be placed before any other commands in the macro"); return MacroResult_Finished; @@ -978,10 +974,12 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) else if (ConsumeToken(ctx, "string")) { argType = MacroArgType_String; } - else if (ConsumeToken(ctx, "keyid")) { + else if (ConsumeToken(ctx, "keyid") || ConsumeToken(ctx, "keyId")) { argType = MacroArgType_KeyId; } - else if (ConsumeToken(ctx, "scancode")) { + else if (ConsumeToken(ctx, "scancode") || ConsumeToken(ctx, "scanCode") || + ConsumeToken(ctx, "moddedScanCode") || ConsumeToken(ctx, "moddedScancode") || + ConsumeToken(ctx, "shortcut")) { argType = MacroArgType_ScanCode; } else if (ConsumeToken(ctx, "any")) { @@ -1027,8 +1025,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) return MacroResult_Header; } - // rest of command is descriptive label, ignore. TODO: Should be parsed as string token. - //while (Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex) != '\0') {}; + // rest of command is descriptive label, ignore. Macros_ConsumeStringToken(ctx); return MacroResult_Header; From 4714f9f2eba46b58f3418c00f9917251432f330c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 26 Feb 2026 09:18:55 +0100 Subject: [PATCH 054/113] TODO comment --- right/src/macros/vars.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 28c95dc87..47e10a175 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1132,7 +1132,9 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // (compatibility with existing macros that don't declare their argument types). // but that doesn't work well ($macroArg vs. ¯oArg). - // TODO: really, we should probably handle template expansion here (treat is as ¯oArg). + // TODO: really, we should probably handle template expansion here (treat it as ¯oArg). + // Currently, it fails when unquoted strings are supplied as argument value, and + // it fails with a very weird error message. return consumeValue(&varCtx); // this has limited functionality } else { // for declared types, consume the value according to type. From 9e592f860453fc501c87f822b5b5b600b261d4e0 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 26 Feb 2026 13:30:41 +0100 Subject: [PATCH 055/113] attempt to expand $macroArg:any as ¯oArg. --- right/src/macros/vars.c | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 47e10a175..771f8cb80 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1109,6 +1109,7 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { if (S->ms.currentMacroArgumentOffset == 0) { Macros_ReportErrorPrintf(ctx->at, "Failed to retrieve argument %d, because this macro doesn't seem to have arguments assigned!", argIdx); + return noneVar(); } string_segment_t str = ParseMacroArgument(S->ms.currentMacroArgumentOffset, argIdx); @@ -1118,27 +1119,27 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { return noneVar(); } - parser_context_t varCtx = (parser_context_t) { - .at = str.start, - .begin = str.start, - .end = str.end, - .macroState = ctx->macroState, - .nestingLevel = ctx->nestingLevel, - .nestingBound = ctx->nestingBound, - }; - if (argType == MacroArgType_Any) { - // for type 'any', consume the value the "old way" - // (compatibility with existing macros that don't declare their argument types). - // but that doesn't work well ($macroArg vs. ¯oArg). - - // TODO: really, we should probably handle template expansion here (treat it as ¯oArg). - // Currently, it fails when unquoted strings are supplied as argument value, and - // it fails with a very weird error message. - return consumeValue(&varCtx); // this has limited functionality + // for type 'any', consume the value as a template expansion (i.e. ¯oArg) + // for compatibility with existing macros that don't declare their argument types. + + PushParserContext(ctx, str.start, str.start, str.end); + //if (Macros_ParserError) { + // return noneVar(); + // } + return consumeValue(ctx); } else { - // for declared types, consume the value according to type. - switch (argType) { + // for declared types, consume the value according to type. + parser_context_t varCtx = (parser_context_t) { + .at = str.start, + .begin = str.start, + .end = str.end, + .macroState = ctx->macroState, + .nestingLevel = ctx->nestingLevel, + .nestingBound = ctx->nestingBound, + }; + + switch (argType) { case MacroArgType_Int: return consumeIntValue(&varCtx); case MacroArgType_Float: From fe5e7e865c3d574aac3dae98d115fcbc4f5963bd Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 26 Feb 2026 16:19:31 +0100 Subject: [PATCH 056/113] arguments of type 'string' are now raw strings without quotes and without $-expansion. Use type 'any' for expansions. --- right/src/macros/string_reader.c | 4 +++- right/src/macros/vars.c | 28 +++++++++++++++++++--------- right/src/str_utils.c | 3 +++ right/src/str_utils.h | 1 - 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 509e72ff2..825c68850 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -148,6 +148,9 @@ static char consumeExpressionCharOfString(const macro_variable_t* variable, uint static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index) { char c; + + // TODO: this TRY_EXPAND_TEMPLATE won't be needed if we expand $macroArg:any correctly. + // It will be handled automatically in Macros_ConsumeAnyValue() below. if (TRY_EXPAND_TEMPLATE(ctx)) { // Call tree of this never expands or unexpands this context, so we can safely perform a pop after. // (If there is an expansion, it is handled within a new context copy.) @@ -331,7 +334,6 @@ char Macros_ConsumeCharOfString(parser_context_t* ctx, uint16_t* stringOffset, u } } - static char Macros_ConsumeCharInString(parser_context_t* ctx, string_type_t stringType, const char* at, uint16_t* index, uint16_t* subIndex) { if (at >= ctx->end) { diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 771f8cb80..cff2016eb 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -164,6 +164,16 @@ static macro_variable_t consumeBool(parser_context_t* ctx) return noneVar(); } +static macro_variable_t consumeRawStringNonExpanded(parser_context_t* ctx) +{ + // the remaining context is the string. No expansions. + uint16_t offset = ctx->at - (const char*)ValidatedUserConfigBuffer.buffer; + uint8_t len = ctx->end - ctx->at; + ctx->at = ctx->end; + + return stringVar((string_ref_t){ .offset = offset, .len = len }); +} + static macro_variable_t consumeStringLiteral(parser_context_t* ctx) { const char* stringStart = ctx->at; @@ -471,7 +481,6 @@ static macro_variable_t consumeValue(parser_context_t* ctx) else { goto failed; } - case 't': if (ConsumeToken(ctx, "true")) { return (macro_variable_t){ .type = MacroVariableType_Bool, .asBool = true }; @@ -479,7 +488,6 @@ static macro_variable_t consumeValue(parser_context_t* ctx) else { goto failed; } - case '$': ctx->at++; return consumeDollarExpression(ctx); @@ -497,10 +505,10 @@ static macro_variable_t consumeValue(parser_context_t* ctx) default: goto failed; } - +a a failed: if (IsIdentifierChar(*ctx->at)) { - Macros_ReportErrorPrintf(ctx->at, "Parsing failed, did you mean '$%s'?", OneWord(ctx)); + Macros_ReportErrorPrintf(ctx->at, "Parsing failed, did you mean '\"%s\"'?", OneWord(ctx)); } else { Macros_ReportErrorTok(ctx, "Could not parse"); } @@ -1120,13 +1128,10 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { } if (argType == MacroArgType_Any) { - // for type 'any', consume the value as a template expansion (i.e. ¯oArg) + // for type 'any', consume the value as a template expansion (i.e. like ¯oArg) // for compatibility with existing macros that don't declare their argument types. PushParserContext(ctx, str.start, str.start, str.end); - //if (Macros_ParserError) { - // return noneVar(); - // } return consumeValue(ctx); } else { // for declared types, consume the value according to type. @@ -1147,7 +1152,12 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { case MacroArgType_Bool: return consumeBool(&varCtx); case MacroArgType_String: { - return consumeStringLiteral(&varCtx); + // this used to be consumeStringLiteral, but that leads to $-expansions + // within the string, even if not enclosed in double-quotes. + // Values configured for arguments should be interpreted as raw strings + // without expansions. + // Use type 'any' if you want $-expansions in your arguments. + return consumeRawStringNonExpanded(&varCtx); } default: Macros_ReportErrorNum("Unexpected argument type:", argType, NULL); diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 374b20dde..1c0a6444e 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -170,6 +170,9 @@ static void consumeWhite(parser_context_t* ctx) ctx->at++; } } + // TODO: this TRY_EXPAND_TEMPLATE needs to be replaced with expansion of $macroArg:any here. + // Note: possible command injection vulnerability if we allow template expansion in white space. + // Do we want to allow this at all? if (TRY_EXPAND_TEMPLATE(ctx)) { continue; } else { diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 82028aaf3..7da321c42 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -30,7 +30,6 @@ // Typedefs: - typedef struct macro_state_t macro_state_t; typedef struct { From b132cd1bcf0b9a65ec113caaab6b18959c991fc1 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 26 Feb 2026 16:24:04 +0100 Subject: [PATCH 057/113] typo --- right/src/macros/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index cff2016eb..f5eebcc08 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -505,7 +505,7 @@ static macro_variable_t consumeValue(parser_context_t* ctx) default: goto failed; } -a a + failed: if (IsIdentifierChar(*ctx->at)) { Macros_ReportErrorPrintf(ctx->at, "Parsing failed, did you mean '\"%s\"'?", OneWord(ctx)); From 2fab9ecfc935c02578c7d2899a3856770d8a8290 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 26 Feb 2026 18:06:15 +0100 Subject: [PATCH 058/113] beginning of new StrRead* parser --- right/src/macros/string_reader.c | 159 ++++++++++++++++++++++++++++++- right/src/macros/string_reader.h | 5 +- right/src/macros/vars.h | 2 +- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 825c68850..d3f40c515 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -12,12 +12,165 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif +// new code: + typedef enum { - StringType_Raw, - StringType_DoubleQuote, - StringType_SingleQuote, + StringType_Undetermined = 0, + StringType_Raw, + StringType_DoubleQuote, + StringType_SingleQuote, + StringType_Verbatim, } string_type_t; +typedef struct { + const char* at; + uint16_t stringOffset; + uint16_t index; + uint16_t subIndex; + string_type_t stringType; +} string_reader_context_t; + +static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) +{ + stringCtx->at = ctx->at; + stringCtx->stringOffset = 0; + stringCtx->index = 0; + stringCtx->subIndex = 0; + if (mode == StrReadMode_Verbatim) { + stringCtx->stringType = StringType_Verbatim; + } else { + stringCtx->stringType = StringType_Undetermined; + } +} + +#if 0 +static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_context_t* stringCtx) +{ + if (at >= ctx->end) { + return '\0'; + } + + switch(*at) { + case '\\': + if (stringType == StringType_SingleQuote || at+1 == ctx->end) { + goto normalChar; + } else { + (*index)++; + at++; + switch (*at) { + case 'n': + (*index)++; + return '\n'; + default: + (*index)++; + return *at; + } + } + case '"': + if (stringType == StringType_DoubleQuote) { + at++; + (*index)++; + return '\0'; + } else { + goto normalChar; + } + case '\'': + if (stringType == StringType_SingleQuote) { + at++; + (*index)++; + return '\0'; + } else { + goto normalChar; + } + case '\n': + return '\0'; + case '$': + if (stringType == StringType_SingleQuote) { + goto normalChar; + } else { + parser_context_t ctx2 = { + .macroState = ctx->macroState, + .begin = ctx->begin, + .at = at, + .end = ctx->end, + .nestingLevel = ctx->nestingLevel, + .nestingBound = ctx->nestingLevel, + }; + ConsumeCommentsAsWhite(false); + char res = consumeExpressionChar(&ctx2, stringType, subIndex); + ConsumeCommentsAsWhite(true); + + if (ctx2.nestingLevel != ctx->nestingLevel) { + Macros_ReportError("Macro template has overflown expression boundary! Undefined behavior coming!", ctx2.at, ctx2.end); + while (ctx2.nestingLevel > ctx->nestingLevel && PopParserContext(&ctx2)) { + } + *index += 1; + return '$'; + } + + if (*subIndex == 0) { + *index += ctx2.at - at; + } + return res; + } + default: + normalChar: + (*index)++; + return *at; + } +} + +static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) +{ + const char* at = ctx->at; + + at += stringCtx->stringOffset; + + if (stringCtx->stringType == StringType_Verbatim) { + char res = StrRead_ConsumeCharInString(ctx, stringCtx); + if (res == '\0') { + stringCtx->stringOffset += stringCtx->index; + stringCtx->index = 0; + } + return res; + } + + string_type_t stringType; + switch (*at) { + case '\'': + stringType = StringType_SingleQuote; + break; + case '"': + stringType = StringType_DoubleQuote; + break; + default: + stringType = StringType_Raw; + break; + } + + if (*index == 0 && stringType != StringType_Raw) { + (*index)++; + } + + at += *index; + + // (This is correct, we don't want a context pop here.) + if (at == ctx->end) { + ctx->at = ctx->end; + return '\0'; + } + + char maybeRes = Macros_ConsumeCharInString(ctx, stringType, at, index, subIndex); + + if (maybeRes == '\0') { + return tryConsumeAnotherStringLiteral(ctx, stringOffset, index, subIndex); + } else { + return maybeRes; + } +} +#endif + +// existing code: static char Macros_ConsumeCharInString(parser_context_t* ctx, string_type_t stringType, const char* at, uint16_t* index, uint16_t* subIndex); diff --git a/right/src/macros/string_reader.h b/right/src/macros/string_reader.h index e25c072dc..ff8a666aa 100644 --- a/right/src/macros/string_reader.h +++ b/right/src/macros/string_reader.h @@ -19,7 +19,10 @@ // Typedefs: - +typedef enum { + StrReadMode_Verbatim, // do not expand anything in the string. Reads until end of context. + StrReadMode_Literal, // read a string literal, with support for escapes and $-expansions. +} string_reader_mode_t; // Variables: diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index e25fe68b3..9a388a4b2 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -28,11 +28,11 @@ } macro_numericalvalue_type_t; typedef enum { + MacroVariableType_None = 0, MacroVariableType_Int, MacroVariableType_Float, MacroVariableType_Bool, MacroVariableType_String, - MacroVariableType_None, } macro_variable_type_t; typedef struct { From 3cd8bf3ace7699ec8a1b9182974bd5b49db3dca2 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sat, 28 Feb 2026 18:21:45 +0100 Subject: [PATCH 059/113] refactor StrRead_xxx first attempt --- right/src/macros/string_reader.c | 63 +++++++++++++++++++------------- right/src/macros/string_reader.h | 2 +- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index d3f40c515..df46c8f8d 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -43,7 +43,6 @@ static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* } } -#if 0 static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_context_t* stringCtx) { if (at >= ctx->end) { @@ -52,32 +51,32 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con switch(*at) { case '\\': - if (stringType == StringType_SingleQuote || at+1 == ctx->end) { + if (stringCtx->stringType == StringType_SingleQuote || at+1 == ctx->end) { goto normalChar; } else { - (*index)++; + stringCtx->index++; at++; switch (*at) { case 'n': - (*index)++; + stringCtx->index++; return '\n'; default: - (*index)++; + stringCtx->index++; return *at; } } case '"': - if (stringType == StringType_DoubleQuote) { + if (stringCtx->stringType == StringType_DoubleQuote) { at++; - (*index)++; + stringCtx->index++; return '\0'; } else { goto normalChar; } case '\'': - if (stringType == StringType_SingleQuote) { + if (stringCtx->stringType == StringType_SingleQuote) { at++; - (*index)++; + stringCtx->index++; return '\0'; } else { goto normalChar; @@ -85,7 +84,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con case '\n': return '\0'; case '$': - if (stringType == StringType_SingleQuote) { + if (stringCtx->stringType == StringType_SingleQuote) { goto normalChar; } else { parser_context_t ctx2 = { @@ -97,7 +96,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con .nestingBound = ctx->nestingLevel, }; ConsumeCommentsAsWhite(false); - char res = consumeExpressionChar(&ctx2, stringType, subIndex); + char res = consumeExpressionChar(&ctx2, stringCtx->stringType, stringCtx->subIndex); ConsumeCommentsAsWhite(true); if (ctx2.nestingLevel != ctx->nestingLevel) { @@ -108,26 +107,42 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con return '$'; } - if (*subIndex == 0) { - *index += ctx2.at - at; + if (stringCtx->subIndex == 0) { + stringCtx->index += ctx2.at - at; } return res; } default: normalChar: - (*index)++; + stringCtx->index++; return *at; } } +// A string can be either a verbatim string (without quotes, with no support for escapes and expansions), +// a raw string (without quotes, with support for escapes and expansions), or a series of string literals. +// Each literal is either a double-quoted string (with support for escapes and expansions), or +// a single-quoted string (with support for only a single-quote-escape but no expansions). +// Literals are concatenated without any interventing characters. + +// For example, the following are all valid strings: +// hello $worldname world => raw string with expansions, so $worldname is expanded. +// "hello"' $worldname '"world" => three literals: double-quoted string, single-quoted string, double-quoted string +// the single-quoted part prevents expansions in that part, so $worldname is not expanded. +// "hello \"$worldname\" world" => double-quoted string with escapes and expansions, so $worldname is expanded, and remains double-quoted due to the escapes. + +// If any of these examples are read as a verbatim string, all the characters will be read +// verbatim (as-is) until the end of the context, including all $ signs and quotes (no expansions, no escapes). + static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) { const char* at = ctx->at; - at += stringCtx->stringOffset; + at += stringCtx->stringOffset; // point to the next literal part of the string. if (stringCtx->stringType == StringType_Verbatim) { char res = StrRead_ConsumeCharInString(ctx, stringCtx); + // I don't think we even need this part; for verbatim strings, there cannot be another part. if (res == '\0') { stringCtx->stringOffset += stringCtx->index; stringCtx->index = 0; @@ -135,24 +150,23 @@ static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_con return res; } - string_type_t stringType; switch (*at) { case '\'': - stringType = StringType_SingleQuote; + stringCtx->stringType = StringType_SingleQuote; break; case '"': - stringType = StringType_DoubleQuote; + stringCtx->stringType = StringType_DoubleQuote; break; default: - stringType = StringType_Raw; + stringCtx->stringType = StringType_Raw; break; } - if (*index == 0 && stringType != StringType_Raw) { - (*index)++; + if (stringCtx->index == 0 && stringCtx->stringType != StringType_Raw) { + stringCtx->index++; } - at += *index; + at += stringCtx->index; // (This is correct, we don't want a context pop here.) if (at == ctx->end) { @@ -160,15 +174,14 @@ static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_con return '\0'; } - char maybeRes = Macros_ConsumeCharInString(ctx, stringType, at, index, subIndex); + char maybeRes = StrRead_ConsumeCharInString(ctx, stringCtx); if (maybeRes == '\0') { - return tryConsumeAnotherStringLiteral(ctx, stringOffset, index, subIndex); + return StrRead_tryConsumeAnotherStringLiteral(ctx, stringCtx); } else { return maybeRes; } } -#endif // existing code: diff --git a/right/src/macros/string_reader.h b/right/src/macros/string_reader.h index ff8a666aa..8c4344b3a 100644 --- a/right/src/macros/string_reader.h +++ b/right/src/macros/string_reader.h @@ -21,7 +21,7 @@ typedef enum { StrReadMode_Verbatim, // do not expand anything in the string. Reads until end of context. - StrReadMode_Literal, // read a string literal, with support for escapes and $-expansions. + StrReadMode_Literal, // read a string literal, with support for quotes, escapes and $-expansions. } string_reader_mode_t; // Variables: From b5034041562feca7b75b7b6032b5f2414a66dd12 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 12:07:28 +0100 Subject: [PATCH 060/113] check compilation --- right/src/macros/string_reader.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index df46c8f8d..b2808ccff 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -30,6 +30,8 @@ typedef struct { string_type_t stringType; } string_reader_context_t; +static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index); + static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) { stringCtx->at = ctx->at; @@ -43,20 +45,26 @@ static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* } } +static char StrRead_tryConsumeAnotherStringLiteral(parser_context_t *ctx, string_reader_context_t *stringCtx) +{ + return 0; +} + static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_context_t* stringCtx) { + char* at = stringCtx->at; if (at >= ctx->end) { return '\0'; } switch(*at) { case '\\': - if (stringCtx->stringType == StringType_SingleQuote || at+1 == ctx->end) { + if (stringCtx->stringType == StringType_SingleQuote || stringCtx->at+1 >= ctx->end) { goto normalChar; } else { stringCtx->index++; at++; - switch (*at) { + switch (*stringCtx->at) { case 'n': stringCtx->index++; return '\n'; @@ -103,7 +111,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con Macros_ReportError("Macro template has overflown expression boundary! Undefined behavior coming!", ctx2.at, ctx2.end); while (ctx2.nestingLevel > ctx->nestingLevel && PopParserContext(&ctx2)) { } - *index += 1; + stringCtx->subIndex += 1; return '$'; } @@ -169,17 +177,17 @@ static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_con at += stringCtx->index; // (This is correct, we don't want a context pop here.) - if (at == ctx->end) { + if (at >= ctx->end) { ctx->at = ctx->end; return '\0'; } - char maybeRes = StrRead_ConsumeCharInString(ctx, stringCtx); + char res = StrRead_ConsumeCharInString(ctx, stringCtx); - if (maybeRes == '\0') { + if (res == '\0') { return StrRead_tryConsumeAnotherStringLiteral(ctx, stringCtx); } else { - return maybeRes; + return res; } } @@ -575,4 +583,3 @@ static char Macros_ConsumeCharInString(parser_context_t* ctx, string_type_t stri return *at; } } - From a426843e6d537e90984744dc8f24ed70fa54f855 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 12:17:07 +0100 Subject: [PATCH 061/113] fixes --- right/src/macros/string_reader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index b2808ccff..efeab9e88 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -52,7 +52,7 @@ static char StrRead_tryConsumeAnotherStringLiteral(parser_context_t *ctx, string static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_context_t* stringCtx) { - char* at = stringCtx->at; + const char* at = stringCtx->at; if (at >= ctx->end) { return '\0'; } @@ -104,7 +104,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con .nestingBound = ctx->nestingLevel, }; ConsumeCommentsAsWhite(false); - char res = consumeExpressionChar(&ctx2, stringCtx->stringType, stringCtx->subIndex); + char res = consumeExpressionChar(&ctx2, stringCtx->stringType, &stringCtx->subIndex); ConsumeCommentsAsWhite(true); if (ctx2.nestingLevel != ctx->nestingLevel) { From 907cba66f6108bd8e1972b60e9dbfa42fbc437b4 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 13:51:48 +0100 Subject: [PATCH 062/113] StrRead* parser test in setLedTxt --- right/src/macros/display.c | 7 ++- right/src/macros/string_reader.c | 90 ++++++++++++++++++++++++++++++-- right/src/macros/vars.c | 4 ++ 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 7d7b87eb9..7ebaf147f 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -44,9 +44,12 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le #endif return textLen; } else if (ctx->at != ctx->end) { - uint16_t stringOffset = 0, textIndex = 0, textSubIndex = 0; + string_reader_context_t stringCtx; + + StrRead_InitContext(ctx, &stringCtx, StrReadMode_Undetermined); + for (uint8_t i = 0; true; i++) { - char c = Macros_ConsumeCharOfString(ctx, &stringOffset, &textIndex, &textSubIndex); + char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); if (c == '\0') { break; } diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index efeab9e88..8fc187298 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -31,6 +31,8 @@ typedef struct { } string_reader_context_t; static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index); +static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx); +static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variable, uint16_t* idx); static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) { @@ -57,14 +59,20 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con return '\0'; } + if(stringCtx->stringType == StringType_Verbatim) { + char res = *at; + stringCtx->at++; + return res; + } + switch(*at) { case '\\': - if (stringCtx->stringType == StringType_SingleQuote || stringCtx->at+1 >= ctx->end) { + if (stringCtx->stringType == StringType_SingleQuote || at+1 >= ctx->end) { goto normalChar; } else { stringCtx->index++; at++; - switch (*stringCtx->at) { + switch (*at) { case 'n': stringCtx->index++; return '\n'; @@ -95,6 +103,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con if (stringCtx->stringType == StringType_SingleQuote) { goto normalChar; } else { + // new context at $ (e.g. $macroArg.1 blah blah) parser_context_t ctx2 = { .macroState = ctx->macroState, .begin = ctx->begin, @@ -103,9 +112,10 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con .nestingLevel = ctx->nestingLevel, .nestingBound = ctx->nestingLevel, }; - ConsumeCommentsAsWhite(false); - char res = consumeExpressionChar(&ctx2, stringCtx->stringType, &stringCtx->subIndex); - ConsumeCommentsAsWhite(true); + + ConsumeCommentsAsWhite(false); // a bit of a hack, turn off comments processing + char res = StrRead_consumeExpressionChar(&ctx2, stringCtx); + ConsumeCommentsAsWhite(true); // turn it back on if (ctx2.nestingLevel != ctx->nestingLevel) { Macros_ReportError("Macro template has overflown expression boundary! Undefined behavior coming!", ctx2.at, ctx2.end); @@ -191,6 +201,76 @@ static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_con } } +extern string_segment_t StringRefToSegment(string_ref_t ref); + +static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variable, uint16_t* idx) +{ + // read the nth character of the string variable, where n is the value of *idx. + // If n exceeds the string length, return '\0' and reset *idx to 0. + + string_segment_t str = StringRefToSegment(variable->asStringRef); + uint8_t len = str.end - str.start; + + if (*idx < len) { + char c = str.start[*idx]; + (*idx)++; + return c; + } else { + *idx = 0; + return '\0'; + } +} + +static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx) +{ + char c; + + // TODO: this TRY_EXPAND_TEMPLATE won't be needed if we expand $macroArg:any correctly. + // It will be handled automatically in Macros_ConsumeAnyValue() below. + if (TRY_EXPAND_TEMPLATE(ctx)) { + // Call tree of this never expands or unexpands this context, so we can safely perform a pop after. + // (If there is an expansion, it is handled within a new context copy.) + c = consumeCharOfTemplate(ctx, stringCtx->stringType, &stringCtx->subIndex); + PopParserContext(ctx); + + if (stringCtx->subIndex == 0) { + UnconsumeWhite(ctx); + } + return c; + } else { + macro_variable_t res = Macros_ConsumeAnyValue(ctx); + UnconsumeWhite(ctx); + + switch (res.type) { + case MacroVariableType_Int: + c = consumeExpressionCharOfInt(&res, &stringCtx->subIndex); + break; + case MacroVariableType_Float: + c = consumeExpressionCharOfFloat(&res, &stringCtx->subIndex); + break; + case MacroVariableType_Bool: + c = consumeExpressionCharOfBool(&res, &stringCtx->subIndex); + break; + case MacroVariableType_String: + c = StrRead_consumeExpressionCharOfString(&res, &stringCtx->subIndex); + break; + case MacroVariableType_None: + c = '?'; + break; + default: + Macros_ReportErrorNum("Unrecognized variable type", res.type, ctx->at); + return '\0'; + } + } + + if (Macros_ParserError) { + ctx->at++; + stringCtx->subIndex = 0; + } + return c; +} + + // existing code: static char Macros_ConsumeCharInString(parser_context_t* ctx, string_type_t stringType, const char* at, uint16_t* index, uint16_t* subIndex); diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index f5eebcc08..01c9e6e45 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1220,6 +1220,10 @@ static string_segment_t stringRefToSegment(string_ref_t ref) { }; } +string_segment_t StringRefToSegment(string_ref_t ref) { + return stringRefToSegment(ref); +} + // currently unused: //static const char *stringRefStart(string_ref_t ref) { // return (const char *)(ValidatedUserConfigBuffer.buffer + ref.offset); From 0083e2cb8fbb6b637f15bdd61dd1770b3fc67d0c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 14:00:38 +0100 Subject: [PATCH 063/113] correct headers --- right/src/macros/display.c | 2 +- right/src/macros/string_reader.c | 11 ++--------- right/src/macros/string_reader.h | 11 +++++++++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 7ebaf147f..15c9afb34 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -46,7 +46,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le } else if (ctx->at != ctx->end) { string_reader_context_t stringCtx; - StrRead_InitContext(ctx, &stringCtx, StrReadMode_Undetermined); + StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); for (uint8_t i = 0; true; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 8fc187298..485d82f0c 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -22,15 +22,8 @@ typedef enum { StringType_Verbatim, } string_type_t; -typedef struct { - const char* at; - uint16_t stringOffset; - uint16_t index; - uint16_t subIndex; - string_type_t stringType; -} string_reader_context_t; - static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index); + static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx); static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variable, uint16_t* idx); @@ -152,7 +145,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con // If any of these examples are read as a verbatim string, all the characters will be read // verbatim (as-is) until the end of the context, including all $ signs and quotes (no expansions, no escapes). -static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) +static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx) { const char* at = ctx->at; diff --git a/right/src/macros/string_reader.h b/right/src/macros/string_reader.h index 8c4344b3a..6de6ec0c5 100644 --- a/right/src/macros/string_reader.h +++ b/right/src/macros/string_reader.h @@ -24,6 +24,14 @@ typedef enum { StrReadMode_Literal, // read a string literal, with support for quotes, escapes and $-expansions. } string_reader_mode_t; +typedef struct { + const char* at; + uint16_t stringOffset; + uint16_t index; + uint16_t subIndex; + string_type_t stringType; +} string_reader_context_t; + // Variables: // Functions: @@ -33,5 +41,8 @@ typedef enum { bool Macros_CompareStringToken(parser_context_t* ctx, string_segment_t str); void Macros_ConsumeStringToken(parser_context_t* ctx); + static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode); + static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx); + #endif From d0e3b2275f6618c4967d1c549edd67295f8529cd Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 14:06:49 +0100 Subject: [PATCH 064/113] fix to header --- right/src/macros/string_reader.c | 8 -------- right/src/macros/string_reader.h | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 485d82f0c..abf1755f3 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -14,14 +14,6 @@ // new code: -typedef enum { - StringType_Undetermined = 0, - StringType_Raw, - StringType_DoubleQuote, - StringType_SingleQuote, - StringType_Verbatim, -} string_type_t; - static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index); static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx); diff --git a/right/src/macros/string_reader.h b/right/src/macros/string_reader.h index 6de6ec0c5..9d6def166 100644 --- a/right/src/macros/string_reader.h +++ b/right/src/macros/string_reader.h @@ -24,6 +24,14 @@ typedef enum { StrReadMode_Literal, // read a string literal, with support for quotes, escapes and $-expansions. } string_reader_mode_t; +typedef enum { + StringType_Undetermined = 0, + StringType_Raw, + StringType_DoubleQuote, + StringType_SingleQuote, + StringType_Verbatim, +} string_type_t; + typedef struct { const char* at; uint16_t stringOffset; From 46a3c927c66cf61139aea9781d68f0289f64d5cd Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 15:45:25 +0100 Subject: [PATCH 065/113] static vs. extern --- right/src/macros/string_reader.c | 4 ++-- right/src/macros/string_reader.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index abf1755f3..06e033b42 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -19,7 +19,7 @@ static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringTyp static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx); static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variable, uint16_t* idx); -static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) +void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) { stringCtx->at = ctx->at; stringCtx->stringOffset = 0; @@ -137,7 +137,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con // If any of these examples are read as a verbatim string, all the characters will be read // verbatim (as-is) until the end of the context, including all $ signs and quotes (no expansions, no escapes). -static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx) +char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx) { const char* at = ctx->at; diff --git a/right/src/macros/string_reader.h b/right/src/macros/string_reader.h index 9d6def166..9d63a364b 100644 --- a/right/src/macros/string_reader.h +++ b/right/src/macros/string_reader.h @@ -49,8 +49,8 @@ typedef struct { bool Macros_CompareStringToken(parser_context_t* ctx, string_segment_t str); void Macros_ConsumeStringToken(parser_context_t* ctx); - static void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode); - static char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx); + void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode); + char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* stringCtx); #endif From 21881e221d13e39a9bbbb336fe330e4efc153b39 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 15:49:04 +0100 Subject: [PATCH 066/113] fix correct context --- right/src/macros/string_reader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 06e033b42..2193cb59c 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -16,7 +16,7 @@ static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index); -static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx); +static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_reader_context_t* stringCtx); static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variable, uint16_t* idx); void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringCtx, string_reader_mode_t mode) @@ -206,7 +206,7 @@ static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variab } } -static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_context_t* stringCtx) +static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_reader_context_t* stringCtx) { char c; From 2174ad3732dedbdc9ce123dbdae4c19955b931b8 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 15:55:47 +0100 Subject: [PATCH 067/113] function declarations --- right/src/macros/string_reader.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 2193cb59c..df9117323 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -14,6 +14,11 @@ // new code: +static char consumeExpressionCharOfInt(const macro_variable_t* variable, uint16_t* idx); +static char consumeExpressionCharOfFloat(const macro_variable_t* variable, uint16_t* idx); +static char consumeExpressionCharOfBool(const macro_variable_t* variable, uint16_t* idx); +static char consumeExpressionCharOfString(const macro_variable_t* variable, uint16_t* idx); +static char consumeCharOfTemplate(parser_context_t* ctx, string_type_t stringType, uint16_t* index); static char consumeExpressionChar(parser_context_t* ctx, string_type_t stringType, uint16_t* index); static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_reader_context_t* stringCtx); From 0ecb29584652a0a3a799587d21e02243dade3330 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 16:20:40 +0100 Subject: [PATCH 068/113] try with verbatim reader --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 15c9afb34..4de6c1e9e 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -46,7 +46,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le } else if (ctx->at != ctx->end) { string_reader_context_t stringCtx; - StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); + StrRead_InitContext(ctx, &stringCtx, StrReadMode_Verbatim); for (uint8_t i = 0; true; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); From 5373a6bbb08e2361434bccc16e16b3a83e6512f6 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 16:41:13 +0100 Subject: [PATCH 069/113] try --- right/src/macros/string_reader.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index df9117323..f125815b4 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -39,7 +39,7 @@ void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringC static char StrRead_tryConsumeAnotherStringLiteral(parser_context_t *ctx, string_reader_context_t *stringCtx) { - return 0; + return '\0'; } static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_context_t* stringCtx) @@ -146,7 +146,7 @@ char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* { const char* at = ctx->at; - at += stringCtx->stringOffset; // point to the next literal part of the string. + at += stringCtx->stringOffset; // point to the current literal of the string. if (stringCtx->stringType == StringType_Verbatim) { char res = StrRead_ConsumeCharInString(ctx, stringCtx); @@ -182,9 +182,10 @@ char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* return '\0'; } + stringCtx->at = at; char res = StrRead_ConsumeCharInString(ctx, stringCtx); - if (res == '\0') { + if (res == '\0' && stringCtx->stringType != StringType_Verbatim) { return StrRead_tryConsumeAnotherStringLiteral(ctx, stringCtx); } else { return res; From 0a54c33734f3929646f41cc3973ffc033b4b9aa7 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 16:51:51 +0100 Subject: [PATCH 070/113] next try of verbatim reading --- right/src/macros/string_reader.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index f125815b4..88c79e8da 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -51,7 +51,7 @@ static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_con if(stringCtx->stringType == StringType_Verbatim) { char res = *at; - stringCtx->at++; + stringCtx->index++; return res; } @@ -149,6 +149,7 @@ char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* at += stringCtx->stringOffset; // point to the current literal of the string. if (stringCtx->stringType == StringType_Verbatim) { + stringCtx->at = at + stringCtx->index; char res = StrRead_ConsumeCharInString(ctx, stringCtx); // I don't think we even need this part; for verbatim strings, there cannot be another part. if (res == '\0') { From 494a6db95b08b6f29ec75f5bc4fc6a4e27bf6c84 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 17:45:47 +0100 Subject: [PATCH 071/113] correctly advance ctx after a verbatim string --- right/src/macros/string_reader.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 88c79e8da..4a6e65564 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -151,8 +151,9 @@ char StrRead_ConsumeCharOfString(parser_context_t* ctx, string_reader_context_t* if (stringCtx->stringType == StringType_Verbatim) { stringCtx->at = at + stringCtx->index; char res = StrRead_ConsumeCharInString(ctx, stringCtx); - // I don't think we even need this part; for verbatim strings, there cannot be another part. + if (res == '\0') { + ctx->at += stringCtx->stringOffset + stringCtx->index; stringCtx->stringOffset += stringCtx->index; stringCtx->index = 0; } From 38df467da99b747ee897eb1c54f7f0b9472d77b3 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 17:56:17 +0100 Subject: [PATCH 072/113] correct advancing through multiple string literals, and also advance main parser context correctly --- right/src/macros/string_reader.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 4a6e65564..931507248 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -39,7 +39,31 @@ void StrRead_InitContext(parser_context_t* ctx, string_reader_context_t* stringC static char StrRead_tryConsumeAnotherStringLiteral(parser_context_t *ctx, string_reader_context_t *stringCtx) { - return '\0'; + const char* at = ctx->at + stringCtx->stringOffset + stringCtx->index; + + if (at >= ctx->end) { + ctx->at = ctx->end; + return '\0'; + } + + switch (*at) { + case '\'': + case '"': + // advance the string reader context to the beginning quote of the next literal + stringCtx->stringOffset += stringCtx->index; + stringCtx->index = 0; + return StrRead_ConsumeCharOfString(ctx, stringCtx); + default: + // advance the main context to the end of the current string + ctx->at = at; + ConsumeWhite(ctx); + // we probably don't need to reset the string reader context here, + // any new string reading should call InitContext() again. + stringCtx->stringOffset = 0; + stringCtx->index = 0; + stringCtx->subIndex = 0; + return '\0'; + } } static char StrRead_ConsumeCharInString(parser_context_t* ctx, string_reader_context_t* stringCtx) From 804c011658b48494c0fd65603784d18ffa48d1a2 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 18:01:07 +0100 Subject: [PATCH 073/113] literal setLcdTxt strings --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 4de6c1e9e..15c9afb34 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -46,7 +46,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le } else if (ctx->at != ctx->end) { string_reader_context_t stringCtx; - StrRead_InitContext(ctx, &stringCtx, StrReadMode_Verbatim); + StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); for (uint8_t i = 0; true; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); From 3a1502c66b737d9ef0b4163be6167b1dee63a63b Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Sun, 1 Mar 2026 23:53:19 +0100 Subject: [PATCH 074/113] commands that have shortcut parameters (modded scancode) should now allow $ expressions --- right/src/macros/commands.c | 3 +- right/src/macros/scancode_commands.c | 30 +++++++++- right/src/macros/string_reader.c | 2 +- right/src/macros/vars.c | 83 ++++++++++++++++++++++------ 4 files changed, 97 insertions(+), 21 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 39adf60c3..efa202166 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1370,7 +1370,8 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat uint8_t Macros_TryConsumeKeyId(parser_context_t* ctx) { - // TODO: allow $macroArg.xxx for type keyId here as well + // TODO: allow $macroArg.xxx for type keyId here as well. + // - this should already work via Macros_ConsumeInt() -> consumeValue() chain. uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index 605804a6f..6213410df 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -480,8 +480,6 @@ macro_result_t Macros_ProcessTextAction(void) static macro_action_t decodeKeyAndConsume(parser_context_t* ctx, macro_sub_action_t defaultSubAction) { - // TODO: allow $macroArg.xxx for type scancode ("modded scancode") here as well - macro_action_t action; const char* end = TokEnd(ctx->at, ctx->end); MacroShortcutParser_Parse(ctx->at, end, defaultSubAction, &action, NULL); @@ -500,7 +498,33 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s reports = &Macros_PersistentReports; } - macro_action_t action = decodeKeyAndConsume(ctx, type); + // TODO: allow $macroArg.xxx for type scancode ("modded scancode") here as well. + // - check for $ + // - if found, call Macros_ConsumeString() to get a string segment (uses consumeValue()) + // - Macros_ConsumeString() is new (in vars.c) and should coalesceType to string + // - parse that string segment as a shortcut (with MacroShortcutParser_Parse) to get the scancode and modifiers + + if (*ctx->at == '$') { + string_segment_t segment = Macros_ConsumeString(ctx); + if (segment.start == NULL) { + Macros_ReportErrorTok("Expected shortcut string but found:"); + return MacroResult_Finished; + } + else { + parser_context_t stringCtx = (parser_context_t) { + .begin = segment.start, + .at = segment.start, + .end = segment.end, + .macroState = ctx->macroState, + .nestingLevel = ctx->nestingLevel, + .nestingBound = ctx->nestingBound, + }; + } + macro_action_t action = decodeKeyAndConsume(&stringCtx, type); + } + else { + macro_action_t action = decodeKeyAndConsume(ctx, type); + } if (Macros_DryRun) { return MacroResult_Finished; diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 931507248..11642ad28 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -288,7 +288,7 @@ static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_reader_c } -// existing code: +// existing code:a static char Macros_ConsumeCharInString(parser_context_t* ctx, string_type_t stringType, const char* at, uint16_t* index, uint16_t* subIndex); diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 01c9e6e45..61cbde45b 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -80,6 +80,11 @@ static macro_variable_t intVar(int32_t value) return (macro_variable_t) { .asInt = value, .type = MacroVariableType_Int }; } +static macro_variable_t floatVar(float value) +{ + return (macro_variable_t) { .asFloat = value, .type = MacroVariableType_Float }; +} + static macro_variable_t boolVar(bool value) { return (macro_variable_t) { .asBool = value, .type = MacroVariableType_Bool }; @@ -154,24 +159,43 @@ static macro_variable_t consumeNumericValue(parser_context_t* ctx) static macro_variable_t consumeBool(parser_context_t* ctx) { if (ConsumeToken(ctx, "false")) { - return (macro_variable_t){ .type = MacroVariableType_Bool, .asBool = false }; + return boolVar(false); } else if (ConsumeToken(ctx, "true")) { - return (macro_variable_t){ .type = MacroVariableType_Bool, .asBool = true }; + return boolVar(true); } Macros_ReportErrorTok(ctx, "Boolean value (true/false) expected but found:"); return noneVar(); } -static macro_variable_t consumeRawStringNonExpanded(parser_context_t* ctx) +static macro_variable_t consumeKeyIdValue(parser_context_t* ctx) { - // the remaining context is the string. No expansions. - uint16_t offset = ctx->at - (const char*)ValidatedUserConfigBuffer.buffer; - uint8_t len = ctx->end - ctx->at; - ctx->at = ctx->end; + uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); + if (keyId == 255) { + macro_variable_t res = consumeValue(ctx); + return coalesceType(ctx, res, MacroVariableType_Int); + } + return intVar(keyId); +} - return stringVar((string_ref_t){ .offset = offset, .len = len }); +static macro_variable_t consumeScancodeValue(parser_context_t* ctx) +// consume "scancode" = modded scancode = "shortcut", return as string variable. +{ + const char* atStart = ctx->at; + // should preparse the Mods+Scancode for validity. + // macro_action_t action = decodeKeyAndConsume(ctx, MacroSubAction_None); + // const char* atEnd = ctx->at; + const char* atEnd = TokEnd(ctx->at, ctx->end); + ConsumeWhiteAt(ctx, atEnd); + + return stringVar(createStringRef(atStart, atEnd)); +} + +static macro_variable_t consumeStringVerbatim(parser_context_t* ctx) +{ + // the remaining context is the string. No expansions. + return stringVar(createStringRef(ctx->at, ctx->end)); } static macro_variable_t consumeStringLiteral(parser_context_t* ctx) @@ -186,10 +210,7 @@ static macro_variable_t consumeStringLiteral(parser_context_t* ctx) return noneVar(); } - uint16_t offset = stringStart - (const char*)ValidatedUserConfigBuffer.buffer; - uint8_t len = ctx->at - stringStart; - - return stringVar((string_ref_t){ .offset = offset, .len = len }); + return stringVar(createStringRef(stringStart, ctx->at)); } macro_variable_t* Macros_ConsumeExistingWritableVariable(parser_context_t* ctx) @@ -414,7 +435,7 @@ static macro_variable_t consumeDollarExpression(parser_context_t* ctx) ConsumeUntilDot(ctx); uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); if (keyId == 255) { - Macros_ReportErrorTok(ctx, "KeyId abbreviation expected"); + Macros_ReportErrorTok(ctx, "KeyId abbreviation expected:"); return noneVar(); } return intVar(keyId); @@ -961,6 +982,16 @@ bool Macros_ConsumeBool(parser_context_t* ctx) return coalesceType(ctx, res, MacroVariableType_Bool).asBool; } +string_segment_t Macros_ConsumeString(parser_context_t* ctx) +{ + macro_variable_t res = consumeValue(ctx); + if (res.type != MacroVariableType_String) { + // Macros_ReportError("String value expected but found:", NULL, NULL); + return (string_segment_t){ .start = NULL, .end = NULL }; + } + return stringRefToSegment(res.asStringRef); +} + macro_variable_t Macros_ConsumeAnyValue(parser_context_t *ctx) { return consumeValue(ctx); @@ -977,7 +1008,23 @@ macro_result_t Macros_ProcessSetVarCommand(parser_context_t* ctx) if (dst != NULL) { dst->type = src.type; - dst->asInt = src.asInt; + switch (src.type) { + case MacroVariableType_Int: + dst->asInt = src.asInt; + break; + case MacroVariableType_Float: + dst->asFloat = src.asFloat; + break; + case MacroVariableType_Bool: + dst->asBool = src.asBool; + break; + case MacroVariableType_String: + dst->asStringRef = src.asStringRef; + break; + default: + Macros_ReportErrorNum("Unexpected variable type:", src.type, NULL); + break; + } } return MacroResult_Finished; @@ -1154,11 +1201,15 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { case MacroArgType_String: { // this used to be consumeStringLiteral, but that leads to $-expansions // within the string, even if not enclosed in double-quotes. - // Values configured for arguments should be interpreted as raw strings + // Values configured for arguments should be interpreted as verbatim strings // without expansions. // Use type 'any' if you want $-expansions in your arguments. - return consumeRawStringNonExpanded(&varCtx); + return consumeStringVerbatim(&varCtx); } + case MacroArgType_KeyId: + return consumeKeyIdValue(&varCtx); + case MacroArgType_ScanCode: + return consumeScancodeValue(&varCtx); default: Macros_ReportErrorNum("Unexpected argument type:", argType, NULL); return noneVar(); From 264730691b8d6364bd674524253908c824b31d8f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 00:02:54 +0100 Subject: [PATCH 075/113] syntax fixes --- right/src/macros/scancode_commands.c | 26 +++++++++++++------------- right/src/macros/vars.h | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index 6213410df..e9a4c8b6e 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -498,6 +498,8 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s reports = &Macros_PersistentReports; } + macro_action_t action; + // TODO: allow $macroArg.xxx for type scancode ("modded scancode") here as well. // - check for $ // - if found, call Macros_ConsumeString() to get a string segment (uses consumeValue()) @@ -507,23 +509,21 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s if (*ctx->at == '$') { string_segment_t segment = Macros_ConsumeString(ctx); if (segment.start == NULL) { - Macros_ReportErrorTok("Expected shortcut string but found:"); + Macros_ReportErrorTok(ctx, "Expected shortcut string but found:"); return MacroResult_Finished; } - else { - parser_context_t stringCtx = (parser_context_t) { - .begin = segment.start, - .at = segment.start, - .end = segment.end, - .macroState = ctx->macroState, - .nestingLevel = ctx->nestingLevel, - .nestingBound = ctx->nestingBound, - }; - } - macro_action_t action = decodeKeyAndConsume(&stringCtx, type); + parser_context_t stringCtx = (parser_context_t) { + .begin = segment.start, + .at = segment.start, + .end = segment.end, + .macroState = ctx->macroState, + .nestingLevel = ctx->nestingLevel, + .nestingBound = ctx->nestingBound, + }; + action = decodeKeyAndConsume(&stringCtx, type); } else { - macro_action_t action = decodeKeyAndConsume(ctx, type); + action = decodeKeyAndConsume(ctx, type); } if (Macros_DryRun) { diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index 9a388a4b2..b328087f0 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -84,6 +84,7 @@ int32_t Macros_ConsumeInt(parser_context_t* ctx); float Macros_ConsumeFloat(parser_context_t* ctx); bool Macros_ConsumeBool(parser_context_t* ctx); + string_segment_t Macros_ConsumeString(parser_context_t* ctx); macro_variable_t Macros_ConsumeAnyValue(parser_context_t* ctx); void MacroVariables_RunTests(void); void Macros_SerializeVar(char* buffer, uint8_t len, macro_variable_t var); From 8a1491293c4cabb604608fa969a47c4da19b8026 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 00:13:45 +0100 Subject: [PATCH 076/113] function prototypes fixed --- right/src/macros/vars.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 61cbde45b..09f112598 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -59,6 +59,10 @@ static macro_variable_t consumeValue(parser_context_t* ctx); static macro_variable_t negate(parser_context_t *ctx, macro_variable_t res); static macro_variable_t consumeMinMaxOperation(parser_context_t* ctx, operator_t op); static macro_variable_t negateBool(parser_context_t *ctx, macro_variable_t res); +static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t value, macro_variable_type_t dstType); + +static string_ref_t createStringRef(const char *start, const char *end); +static string_segment_t stringRefToSegment(string_ref_t ref); macro_result_t Macros_ProcessStatsVariablesCommand(void) { if (Macros_DryRun) { @@ -80,10 +84,10 @@ static macro_variable_t intVar(int32_t value) return (macro_variable_t) { .asInt = value, .type = MacroVariableType_Int }; } -static macro_variable_t floatVar(float value) -{ - return (macro_variable_t) { .asFloat = value, .type = MacroVariableType_Float }; -} +//static macro_variable_t floatVar(float value) +//{ +// return (macro_variable_t) { .asFloat = value, .type = MacroVariableType_Float }; +//} static macro_variable_t boolVar(bool value) { From 958fd7f42ac75514e90650ccd89da1b21373d23f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 00:54:41 +0100 Subject: [PATCH 077/113] workaround to remove quotes from regular string for modded scancodes --- right/src/macros/scancode_commands.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index e9a4c8b6e..afae8cfc7 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -520,7 +520,13 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s .nestingLevel = ctx->nestingLevel, .nestingBound = ctx->nestingBound, }; + if (stringCtx.at < stringCtx.end && (*stringCtx.at == '\'' || *stringCtx.at == '"')) { + stringCtx.at++; + } action = decodeKeyAndConsume(&stringCtx, type); + if (stringCtx.at < stringCtx.end && (*stringCtx.at == '\'' || *stringCtx.at == '"')) { + stringCtx.at++; + } } else { action = decodeKeyAndConsume(ctx, type); From 63472fd056af75ce612891256ce9ae0b7ef848db Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 01:50:43 +0100 Subject: [PATCH 078/113] dequoting of modded scancode strings --- right/src/macros/scancode_commands.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index afae8cfc7..3ffa454a2 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -489,6 +489,18 @@ static macro_action_t decodeKeyAndConsume(parser_context_t* ctx, macro_sub_actio return action; } +void dequoteContext(parser_context_t* ctx) +{ + if (ctx->at < ctx->end && (*ctx->at == '\'' || *ctx->at == '"')) { + char limiter = *ctx->at++; // remember starting quote and skip it + if (ctx->end > ctx->at && *(ctx->end - 1) == limiter) { // check if ending quote matches starting quote + ctx->end--; // if yes, skip the ending quote as well + } else { + ctx->at--; // if not, step back and keep quotes + } + } +} + macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_sub_action_t type, macro_usb_keyboard_reports_t* reports) { if (reports == NULL) { @@ -520,9 +532,7 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s .nestingLevel = ctx->nestingLevel, .nestingBound = ctx->nestingBound, }; - if (stringCtx.at < stringCtx.end && (*stringCtx.at == '\'' || *stringCtx.at == '"')) { - stringCtx.at++; - } + dequoteContext(&stringCtx); // remove enclosing quotes if they exist (hack to allow simple strings) action = decodeKeyAndConsume(&stringCtx, type); if (stringCtx.at < stringCtx.end && (*stringCtx.at == '\'' || *stringCtx.at == '"')) { stringCtx.at++; From 684318d97120233c98af414ca9657a3b7c9d5aed Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 02:24:22 +0100 Subject: [PATCH 079/113] default to verbatim strings when consuming values --- right/src/macros/vars.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 09f112598..2b9adfff5 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -532,12 +532,16 @@ static macro_variable_t consumeValue(parser_context_t* ctx) } failed: + return consumeStringLiteral(ctx); + +#if 0 if (IsIdentifierChar(*ctx->at)) { Macros_ReportErrorPrintf(ctx->at, "Parsing failed, did you mean '\"%s\"'?", OneWord(ctx)); } else { Macros_ReportErrorTok(ctx, "Could not parse"); } return noneVar(); +#endif } static macro_variable_t negate(parser_context_t *ctx, macro_variable_t res) From 912f8209f72caec939d8ff3a4e2ed31a54d145fd Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 11:03:46 +0100 Subject: [PATCH 080/113] test mit verbatim setLedTxt --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 15c9afb34..4de6c1e9e 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -46,7 +46,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le } else if (ctx->at != ctx->end) { string_reader_context_t stringCtx; - StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); + StrRead_InitContext(ctx, &stringCtx, StrReadMode_Verbatim); for (uint8_t i = 0; true; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); From 4758e83ca3791dc0b159a4684729dfd4fed369c0 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 11:59:00 +0100 Subject: [PATCH 081/113] back to literal processing --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 4de6c1e9e..15c9afb34 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -46,7 +46,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le } else if (ctx->at != ctx->end) { string_reader_context_t stringCtx; - StrRead_InitContext(ctx, &stringCtx, StrReadMode_Verbatim); + StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); for (uint8_t i = 0; true; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); From 523018f01f283c8bcff3a1d233a528defaf03cea Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 12:53:40 +0100 Subject: [PATCH 082/113] try to limit buffer --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 15c9afb34..555d5213b 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -48,7 +48,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); - for (uint8_t i = 0; true; i++) { + for (uint8_t i = 0; i<8 /* true */; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); if (c == '\0') { break; From 9da811d872c98d5feddd06b0daa65a27933efe43 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 13:10:34 +0100 Subject: [PATCH 083/113] try to limit buffer more --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 555d5213b..008a34327 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -48,7 +48,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); - for (uint8_t i = 0; i<8 /* true */; i++) { + for (uint8_t i = 0; i<2 /* true */; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); if (c == '\0') { break; From 374aa6a53d8bc6a825bb49a463b05784bdc649db Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 15:13:10 +0100 Subject: [PATCH 084/113] back to normal setLcdTxt string length --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 008a34327..15c9afb34 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -48,7 +48,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le StrRead_InitContext(ctx, &stringCtx, StrReadMode_Literal); - for (uint8_t i = 0; i<2 /* true */; i++) { + for (uint8_t i = 0; true; i++) { char c = StrRead_ConsumeCharOfString(ctx, &stringCtx); if (c == '\0') { break; From 150fea012e27b60cab29f82c22e83da5c3861e5e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 15:22:44 +0100 Subject: [PATCH 085/113] an attempt to fix uhk80 crashes --- right/src/macros/string_reader.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 11642ad28..a16ac239f 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -230,7 +230,11 @@ static char StrRead_consumeExpressionCharOfString(const macro_variable_t* variab if (*idx < len) { char c = str.start[*idx]; - (*idx)++; + if (c != '\0') { + (*idx)++; + } else { + *idx = 0; + } return c; } else { *idx = 0; From d5522f463a7eaced2dc8e0a081d91eba81fe198f Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 18:46:21 +0100 Subject: [PATCH 086/113] try without StrRead_consumeExpressionCharOfString --- right/src/macros/string_reader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index a16ac239f..6dd02bcea 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -273,7 +273,7 @@ static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_reader_c c = consumeExpressionCharOfBool(&res, &stringCtx->subIndex); break; case MacroVariableType_String: - c = StrRead_consumeExpressionCharOfString(&res, &stringCtx->subIndex); + c = /*StrRead_*/consumeExpressionCharOfString(&res, &stringCtx->subIndex); break; case MacroVariableType_None: c = '?'; From 81a3538ac469047b6c1982137558616875783bf3 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 2 Mar 2026 19:10:49 +0100 Subject: [PATCH 087/113] back to StrRead_consume... --- right/src/macros/string_reader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/string_reader.c b/right/src/macros/string_reader.c index 6dd02bcea..a16ac239f 100644 --- a/right/src/macros/string_reader.c +++ b/right/src/macros/string_reader.c @@ -273,7 +273,7 @@ static char StrRead_consumeExpressionChar(parser_context_t* ctx, string_reader_c c = consumeExpressionCharOfBool(&res, &stringCtx->subIndex); break; case MacroVariableType_String: - c = /*StrRead_*/consumeExpressionCharOfString(&res, &stringCtx->subIndex); + c = StrRead_consumeExpressionCharOfString(&res, &stringCtx->subIndex); break; case MacroVariableType_None: c = '?'; From 1d2962412baa84d8ac9a1b2343832c9400c0e747 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 10:08:54 +0100 Subject: [PATCH 088/113] simpler :any argument expansion --- right/src/macros/vars.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 2b9adfff5..94661556e 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1186,8 +1186,18 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // for type 'any', consume the value as a template expansion (i.e. like ¯oArg) // for compatibility with existing macros that don't declare their argument types. - PushParserContext(ctx, str.start, str.start, str.end); - return consumeValue(ctx); + // PushParserContext(ctx, str.start, str.start, str.end); + // return consumeValue(ctx); + parser_context_t varCtx = (parser_context_t) { + .at = str.start, + .begin = str.start, + .end = str.end, + .macroState = ctx->macroState, + .nestingLevel = ctx->nestingLevel, + .nestingBound = ctx->nestingBound, + }; + + return consumeValue(&varCtx); } else { // for declared types, consume the value according to type. parser_context_t varCtx = (parser_context_t) { From 0839f1deac8018d975730d26c0123564d3689d5d Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 13:55:11 +0100 Subject: [PATCH 089/113] back to pushing the context --- right/src/macros/vars.c | 6 ++++-- right/src/str_utils.c | 27 +++++++++++++-------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 94661556e..07a875875 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1186,8 +1186,9 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // for type 'any', consume the value as a template expansion (i.e. like ¯oArg) // for compatibility with existing macros that don't declare their argument types. - // PushParserContext(ctx, str.start, str.start, str.end); - // return consumeValue(ctx); + PushParserContext(ctx, str.start, str.start, str.end); + return consumeValue(ctx); +#if 0 parser_context_t varCtx = (parser_context_t) { .at = str.start, .begin = str.start, @@ -1198,6 +1199,7 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { }; return consumeValue(&varCtx); +#endif } else { // for declared types, consume the value according to type. parser_context_t varCtx = (parser_context_t) { diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 1c0a6444e..ce5451dab 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -124,7 +124,7 @@ static bool isEnd(parser_context_t* ctx) { return false; } while (ctx->nestingLevel > 0 && ctx->at >= ctx->end && PopParserContext(ctx)) { - /* everything was don in PopParserContext */ + /* everything was done in PopParserContext */ }; return ctx->at >= ctx->end; } @@ -439,7 +439,7 @@ bool TokenMatches2(const char *a, const char *aEnd, const char *b, const char *b uint8_t TokLen(const char *a, const char *aEnd) { uint8_t l = 0; - while(*a > 32 && a < aEnd) { + while(a < aEnd && *a > 32) { l++; a++; } @@ -448,7 +448,7 @@ uint8_t TokLen(const char *a, const char *aEnd) const char* TokEnd(const char* cmd, const char *cmdEnd) { - while(*cmd > 32 && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd > 32) { cmd++; } return cmd; @@ -457,10 +457,10 @@ const char* TokEnd(const char* cmd, const char *cmdEnd) // This doesn't handle expansions. Don't use it in actual macro context. const char* NextTok(const char* cmd, const char *cmdEnd) { - while(*cmd > 32 && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd > 32) { cmd++; } - while(*cmd <= 32 && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd <= 32) { cmd++; } if (cmd < cmdEnd - 1 && cmd[0] == '/' && cmd[1] == '/') { @@ -471,7 +471,7 @@ const char* NextTok(const char* cmd, const char *cmdEnd) void ConsumeAnyToken(parser_context_t* ctx) { - while (*ctx->at > 32 && ctx->at < ctx->end) { + while (ctx->at < ctx->end && *ctx->at > 32) { ctx->at++; } consumeWhite(ctx); @@ -482,12 +482,12 @@ struct command_entry* ConsumeGperfToken(parser_context_t* ctx) const char* start = ctx->at; // parse an identifier token - while (isIdentifierChar(*ctx->at) && ctx->at < ctx->end) { + while (ctx->at < ctx->end && isIdentifierChar(*ctx->at)) { ctx->at++; } // parse a single char operator if token wasn't matched. - if (ctx->at == start && !isIdentifierChar(*ctx->at) && *ctx->at > 32 && ctx->at < ctx->end) { + if (ctx->at < ctx->end && ctx->at == start && !isIdentifierChar(*ctx->at) && *ctx->at > 32) { ctx->at++; } @@ -498,11 +498,11 @@ struct command_entry* ConsumeGperfToken(parser_context_t* ctx) const char* NextCmd(const char* cmd, const char *cmdEnd) { - while(*cmd != '\n' && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd != '\n') { cmd++; } const char* lastNewline = cmd; - while(*cmd <= 32 && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd <= 32) { if (*cmd == '\n') { lastNewline = cmd; } @@ -518,7 +518,7 @@ const char* NextCmd(const char* cmd, const char *cmdEnd) const char* CmdEnd(const char* cmd, const char *cmdEnd) { - while(*cmd != '\n' && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd != '\n') { cmd++; } return cmd; @@ -526,7 +526,7 @@ const char* CmdEnd(const char* cmd, const char *cmdEnd) const char* SkipWhite(const char* cmd, const char *cmdEnd) { - while(*cmd <= 32 && cmd < cmdEnd) { + while(cmd < cmdEnd && *cmd <= 32) { cmd++; } return cmd; @@ -681,7 +681,7 @@ uint8_t CountCommands(const char* text, uint16_t textLen) uint8_t count = 1; const char* textEnd = text + textLen; - while ( *text <= 32 && text < textEnd) { + while (text < textEnd && *text <= 32) { text++; } @@ -769,4 +769,3 @@ const char* DeviceModelName(device_id_t device) { return "Unknown device"; } } - From 2d84902095c48496599dadcad39822971162bfed Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 16:14:56 +0100 Subject: [PATCH 090/113] safety checks for context overrun tightened in str_utils.c, reverted :any type parsing to non-template expansion (caused crashes) --- right/src/macros/vars.c | 5 +++- right/src/str_utils.c | 51 +++++++++++++++-------------------------- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 07a875875..a3b9b48be 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1186,9 +1186,12 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { // for type 'any', consume the value as a template expansion (i.e. like ¯oArg) // for compatibility with existing macros that don't declare their argument types. +#if 0 + // TODO: This doesn't work; it will cause firmware crashes. + // I don't understand why. PushParserContext(ctx, str.start, str.start, str.end); return consumeValue(ctx); -#if 0 +#else parser_context_t varCtx = (parser_context_t) { .at = str.start, .begin = str.start, diff --git a/right/src/str_utils.c b/right/src/str_utils.c index ce5451dab..b6e1534a6 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -162,11 +162,11 @@ bool IsWhite(parser_context_t* ctx) { static void consumeWhite(parser_context_t* ctx) { while (!isEnd(ctx)) { - while (*ctx->at <= 32 && !isEnd(ctx)) { + while (!isEnd(ctx) && ctx->at <= 32) { ctx->at++; } - if (isCommentLeader(ctx) && consumeCommentsAsWhite) { - while (*ctx->at != '\n' && !isEnd(ctx)) { + if (consumeCommentsAsWhite && isCommentLeader(ctx)) { + while (!isEnd(ctx) && *ctx->at != '\n') { ctx->at++; } } @@ -236,8 +236,8 @@ bool ConsumeToken(parser_context_t* ctx, const char *b) { const char* at = ctx->at; while(at < ctx->end && *b) { - if (*at <= 32 || at == ctx->end || *b <= 32) { - bool res = (*at <= 32 || at == ctx->end) && *b <= 32; + if (at >= ctx->end || *at <= 32 || *b <= 32) { + bool res = (at >= ctx->end || *at <= 32) && *b <= 32; if (res) { ctx->at = at; consumeWhite(ctx); @@ -248,7 +248,7 @@ bool ConsumeToken(parser_context_t* ctx, const char *b) return false; } } - bool res = (*at <= 32 || at == ctx->end || !isIdentifierChar(*at) || !isIdentifierChar(*(at-1))) && *b <= 32; + bool res = (at >= ctx->end || *at <= 32 || !isIdentifierChar(*at) || !isIdentifierChar(*(at-1))) && *b <= 32; if (res) { ctx->at = at; consumeWhite(ctx); @@ -269,8 +269,8 @@ bool ConsumeTokenByRef(parser_context_t* ctx, string_ref_t ref) const char* b = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset); const char* bEnd = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); while(at < ctx->end && b < bEnd) { - if (*at <= 32 || at == ctx->end || *b <= 32 || b == bEnd) { - bool res = (*at <= 32 || at == ctx->end) && *b <= 32; + if (at >= ctx->end || *at <= 32 || b >= bEnd || *b <= 32) { + bool res = (at >= ctx->end || *at <= 32) && *b <= 32; if (res) { ctx->at = at; consumeWhite(ctx); @@ -281,7 +281,7 @@ bool ConsumeTokenByRef(parser_context_t* ctx, string_ref_t ref) return false; } } - bool res = (*at <= 32 || at == ctx->end || *at == '.') && (*b <= 32 || b == bEnd); + bool res = (at >= ctx->end || *at <= 32 || *at == '.') && (b >= bEnd || *b <= 32); if (res) { ctx->at = at; consumeWhite(ctx); @@ -313,8 +313,8 @@ bool ConsumeIdentifierByRef(parser_context_t* ctx, string_ref_t ref) const char* b = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset); const char* bEnd = (const char*)(ValidatedUserConfigBuffer.buffer + ref.offset + ref.len); while(at < ctx->end && b < bEnd) { - if (!isIdentifierChar(*at) || at == ctx->end || !isIdentifierChar(*b) || b == bEnd) { - bool res = (!isIdentifierChar(*at) || at == ctx->end) && (!isIdentifierChar(*b) || b == bEnd); + if (at >= ctx->end || !isIdentifierChar(*at) || b >= bEnd || !isIdentifierChar(*b)) { + bool res = (at >= ctx->end || !isIdentifierChar(*at)) && (b >= bEnd || !isIdentifierChar(*b)); if (res) { ctx->at = at; consumeWhite(ctx); @@ -326,7 +326,7 @@ bool ConsumeIdentifierByRef(parser_context_t* ctx, string_ref_t ref) } } - bool res = (!isIdentifierChar(*at) || at == ctx->end) && (!isIdentifierChar(*b) || b == bEnd); + bool res = (at >= ctx->end || !isIdentifierChar(*at)) && (b >= bEnd || !isIdentifierChar(*b)); if (res) { ctx->at = at; consumeWhite(ctx); @@ -337,7 +337,7 @@ bool ConsumeIdentifierByRef(parser_context_t* ctx, string_ref_t ref) const char* IdentifierEnd(parser_context_t* ctx) { const char* at = ctx->at; - while (isIdentifierChar(*at) && at < ctx->end) { + while (at < ctx->end && isIdentifierChar(*at)) { at++; } return at; @@ -349,21 +349,6 @@ void ConsumeAnyIdentifier(parser_context_t* ctx) consumeWhite(ctx); } -#if 0 -/* old ConsumeUntilDot (replaced below) */ - -void ConsumeUntilDot(parser_context_t* ctx) -{ - while(*ctx->at > 32 && *ctx->at != '.' && !isEnd(ctx)) { - ctx->at++; - } - if (*ctx->at != '.') { - Macros_ReportError("'.' expected", ctx->at, ctx->at); - } - ctx->at++; -} -#endif - // Consume characters until a specific character is found or whitespace is hit. // If end of context is reached, report an error. // If the character is found, consume it. @@ -413,8 +398,8 @@ bool ConsumeOneDot(parser_context_t* ctx) bool TokenMatches(const char *a, const char *aEnd, const char *b) { while(a < aEnd && *b) { - if (*a <= 32 || a == aEnd || *b <= 32) { - return (*a <= 32 || a == aEnd) && *b <= 32; + if (a >= aEnd || *a <= 32 || *b <= 32) { + return (a >= aEnd || *a <= 32) && *b <= 32; } if (*a++ != *b++) { return false; @@ -426,14 +411,14 @@ bool TokenMatches(const char *a, const char *aEnd, const char *b) bool TokenMatches2(const char *a, const char *aEnd, const char *b, const char *bEnd) { while(a < aEnd && b < bEnd) { - if (*a <= 32 || a == aEnd || *b <= 32 || b == bEnd) { - return (*a <= 32 || a == aEnd) && *b <= 32; + if (a >= aEnd || *a <= 32 || b >= bEnd || *b <= 32) { + return (a >= aEnd || *a <= 32) && *b <= 32; } if (*a++ != *b++) { return false; } } - return (*a <= 32 || a == aEnd || *a == '.') && (*b <= 32 || b == bEnd); + return (a >= aEnd || *a <= 32 || *a == '.') && (b >= bEnd || *b <= 32); } uint8_t TokLen(const char *a, const char *aEnd) From b68906ad2d3a20570f23b1bea59e2b0dcb208711 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 18:24:20 +0100 Subject: [PATCH 091/113] fix refactoring typo in consumeWhite() --- right/src/macros/commands.c | 2 +- right/src/str_utils.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index efa202166..f3a266271 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -2486,7 +2486,7 @@ static macro_result_t processCommand(parser_context_t* ctx) if (cmdTokEnd > ctx->at && cmdTokEnd[-1] == ':') { //skip labels ConsumeAnyToken(ctx); - if (ctx->at == ctx->end && IsEnd(ctx)) { + if (/* ctx->at >= ctx->end && */ IsEnd(ctx)) { return MacroResult_Finished; } } diff --git a/right/src/str_utils.c b/right/src/str_utils.c index b6e1534a6..943950766 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -162,7 +162,7 @@ bool IsWhite(parser_context_t* ctx) { static void consumeWhite(parser_context_t* ctx) { while (!isEnd(ctx)) { - while (!isEnd(ctx) && ctx->at <= 32) { + while (!isEnd(ctx) && *ctx->at <= 32) { ctx->at++; } if (consumeCommentsAsWhite && isCommentLeader(ctx)) { From c9e4aca61777c29fc8bfcda9c71119edfffe20a2 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 22:00:24 +0100 Subject: [PATCH 092/113] ConsumeUntilDot() => ConsumeOneDot(), for more accurate parsing of $something.this.that --- right/src/macros/vars.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index a3b9b48be..abdb4f13a 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -259,7 +259,7 @@ static macro_variable_t consumeVariable(parser_context_t* ctx) } ConsumeAnyIdentifier(ctx); - return (macro_variable_t){}; + return (macro_variable_t){}; // TODO: shouldn't this be noneVar()? } // Expects @@ -424,7 +424,7 @@ static macro_variable_t consumeDollarExpression(parser_context_t* ctx) return intVar(Timer_GetCurrentTime() & 0x7FFFFFFF); } else if (ConsumeToken(ctx, "queuedKeyId")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); int8_t queueIdx = Macros_ConsumeInt(ctx); if (queueIdx >= PostponerQuery_PendingKeypressCount()) { if (!Macros_DryRun) { @@ -436,7 +436,7 @@ static macro_variable_t consumeDollarExpression(parser_context_t* ctx) return intVar(PostponerExtended_PendingId(queueIdx)); } else if (ConsumeToken(ctx, "keyId")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); if (keyId == 255) { Macros_ReportErrorTok(ctx, "KeyId abbreviation expected:"); @@ -445,7 +445,7 @@ static macro_variable_t consumeDollarExpression(parser_context_t* ctx) return intVar(keyId); } else if (ConsumeToken(ctx, "uhk")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); if (ConsumeToken(ctx, "name")) { return stringVar(Cfg.DeviceName); } else { @@ -1264,7 +1264,7 @@ bool TryExpandMacroTemplateOnce(parser_context_t* ctx) { Trace_Printc("e1"); if (ConsumeToken(ctx, "macroArg")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); uint8_t argId = Macros_ConsumeInt(ctx); expandArgumentInplace(ctx, argId); return true; From e0fca861cd068621a89bb31a5d5563b46dc39fbe Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 22:07:23 +0100 Subject: [PATCH 093/113] more of OneDot --- right/src/macros/set_command.c | 40 +++++++++++++++++----------------- right/src/str_utils.c | 3 +++ right/src/str_utils.h | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/right/src/macros/set_command.c b/right/src/macros/set_command.c index ffd6ad358..d78aeff27 100644 --- a/right/src/macros/set_command.c +++ b/right/src/macros/set_command.c @@ -266,14 +266,14 @@ static macro_variable_t module(parser_context_t* ctx, set_command_action_t actio module_id_t moduleId = ConsumeModuleId(ctx); module_configuration_t* module = GetModuleConfiguration(moduleId); - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); if (Macros_ParserError) { return noneVar(); } if (ConsumeToken(ctx, "navigationMode")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return moduleNavigationMode(ctx, action, module); } else if (ConsumeToken(ctx, "holdContinuationTimeout") && moduleId == ModuleId_TouchpadRight) { @@ -348,7 +348,7 @@ static macro_variable_t secondaryRoles(parser_context_t* ctx, set_command_action ASSIGN_CUSTOM(int32_t, intVar, Cfg.SecondaryRoles_Strategy, ConsumeSecondaryRoleStrategy(ctx)); } else if (ConsumeToken(ctx, "advanced")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return secondaryRoleAdvanced(ctx, action); } else { @@ -414,7 +414,7 @@ static macro_variable_t mouseKeys(parser_context_t* ctx, set_command_action_t ac return noneVar(); } - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); if (ConsumeToken(ctx, "initialSpeed")) { DEFINE_INT_LIMITS(0, 255); @@ -540,7 +540,7 @@ static macro_variable_t keyRgb(parser_context_t* ctx, set_command_action_t actio { layer_id_t layerId = Macros_ConsumeLayerId(ctx); - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); uint16_t keyId = Macros_TryConsumeKeyId(ctx); @@ -703,11 +703,11 @@ static macro_variable_t backlight(parser_context_t* ctx, set_command_action_t ac return backlightStrategy(ctx, action); } else if (ConsumeToken(ctx, "constantRgb")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return constantRgb(ctx, action); } else if (ConsumeToken(ctx, "keyRgb")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return keyRgb(ctx, action); } else { @@ -756,7 +756,7 @@ static macro_variable_t navigationModeAction(parser_context_t* ctx, set_command_ navigationMode = ConsumeNavigationModeId(ctx); - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); if (action == SetCommandAction_Read) { Macros_ReportErrorPos(ctx, "Reading actions is not supported!"); @@ -810,7 +810,7 @@ static macro_variable_t keymapAction(parser_context_t* ctx, set_command_action_t uint8_t layerId = Macros_ConsumeLayerId(ctx); CTX_COPY(keyIdPos, *ctx); - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); uint16_t keyId = Macros_TryConsumeKeyId(ctx); @@ -960,47 +960,47 @@ static macro_variable_t setMaxVoltage(parser_context_t* ctx, set_command_action_ static macro_variable_t root(parser_context_t* ctx, set_command_action_t action) { if (ConsumeToken(ctx, "module")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return module(ctx, action); } else if (ConsumeToken(ctx, "secondaryRole")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return secondaryRoles(ctx, action); } else if (ConsumeToken(ctx, "bluetooth")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return bluetooth(ctx, action); } else if (ConsumeToken(ctx, "mouseKeys")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return mouseKeys(ctx, action); } else if (ConsumeToken(ctx, "keymapAction")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return keymapAction(ctx, action); } else if (ConsumeToken(ctx, "navigationModeAction")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return navigationModeAction(ctx, action); } else if (ConsumeToken(ctx, "macroEngine")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return macroEngine(ctx, action); } else if (ConsumeToken(ctx, "backlight")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return backlight(ctx, action); } else if (ConsumeToken(ctx, "battery")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return battery(ctx, action); } else if (ConsumeToken(ctx, "leds")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return leds(ctx, action); } else if (ConsumeToken(ctx, "modifierLayerTriggers")) { - ConsumeUntilDot(ctx); + ConsumeOneDot(ctx); return modLayerTriggers(ctx, action); } else if (ConsumeToken(ctx, "maxVoltage")) { diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 943950766..0f5aff56e 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -349,6 +349,7 @@ void ConsumeAnyIdentifier(parser_context_t* ctx) consumeWhite(ctx); } +#if 0 // Consume characters until a specific character is found or whitespace is hit. // If end of context is reached, report an error. // If the character is found, consume it. @@ -376,7 +377,9 @@ void ConsumeUntilDot(parser_context_t* ctx) { ConsumeUntilCharOrWhite(ctx, '.', true); } +#endif +// will consume exactly one character. // will not consume whitespace after the character. // returns true if the character was found, false otherwise. bool ConsumeOneChar(parser_context_t* ctx, char c) diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 7da321c42..05197315d 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -85,7 +85,7 @@ const char* CmdEnd(const char* cmd, const char *cmdEnd); bool ConsumeOneChar(parser_context_t* ctx, char c); bool ConsumeOneDot(parser_context_t* ctx); - void ConsumeUntilDot(parser_context_t* ctx); + void ConsumeOneDot(parser_context_t* ctx); void ConsumeWhiteAt(parser_context_t* ctx, const char* at); const char* SkipWhite(const char* cmd, const char *cmdEnd); uint8_t CountCommands(const char* text, uint16_t textLen); From e5bce18e0016537c5aad2fff1d6a99f1669761a6 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 22:17:40 +0100 Subject: [PATCH 094/113] fix ConsumeOneDot header --- right/src/str_utils.c | 4 ++-- right/src/str_utils.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/right/src/str_utils.c b/right/src/str_utils.c index 0f5aff56e..09c0022c2 100644 --- a/right/src/str_utils.c +++ b/right/src/str_utils.c @@ -354,7 +354,7 @@ void ConsumeAnyIdentifier(parser_context_t* ctx) // If end of context is reached, report an error. // If the character is found, consume it. // If whitespace is found, and failOnWhite is true, report an error. -void ConsumeUntilCharOrWhite(parser_context_t* ctx, char c, bool failOnWhite) +void consumeUntilCharOrWhite(parser_context_t* ctx, char c, bool failOnWhite) { while(!isEnd(ctx) && *ctx->at > 32 && *ctx->at != c) { ctx->at++; @@ -375,7 +375,7 @@ void ConsumeUntilCharOrWhite(parser_context_t* ctx, char c, bool failOnWhite) void ConsumeUntilDot(parser_context_t* ctx) { - ConsumeUntilCharOrWhite(ctx, '.', true); + consumeUntilCharOrWhite(ctx, '.', true); } #endif diff --git a/right/src/str_utils.h b/right/src/str_utils.h index 05197315d..7da321c42 100644 --- a/right/src/str_utils.h +++ b/right/src/str_utils.h @@ -85,7 +85,7 @@ const char* CmdEnd(const char* cmd, const char *cmdEnd); bool ConsumeOneChar(parser_context_t* ctx, char c); bool ConsumeOneDot(parser_context_t* ctx); - void ConsumeOneDot(parser_context_t* ctx); + void ConsumeUntilDot(parser_context_t* ctx); void ConsumeWhiteAt(parser_context_t* ctx, const char* at); const char* SkipWhite(const char* cmd, const char *cmdEnd); uint8_t CountCommands(const char* text, uint16_t textLen); From 41e54bb93e8b35c614d675d1ee547f89e0f3757e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Tue, 3 Mar 2026 23:49:59 +0100 Subject: [PATCH 095/113] setLedTxt for string variable type --- right/src/macros/display.c | 1 + right/src/segment_display.c | 16 ++++++++++++++++ right/src/segment_display.h | 1 + 3 files changed, 18 insertions(+) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 15c9afb34..bc7ab20ca 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -214,6 +214,7 @@ void processList(parser_context_t* ctx, bool show, uint16_t time) { macro_result_t Macros_ProcessSetLedTxtCommand(parser_context_t* ctx) { + // TODO: I guess ATTR_UNUSED is not correct here? ATTR_UNUSED int16_t time = Macros_ConsumeInt(ctx); macro_result_t res = MacroResult_Finished; diff --git a/right/src/segment_display.c b/right/src/segment_display.c index 0cf13e2a6..5d3e66aa9 100644 --- a/right/src/segment_display.c +++ b/right/src/segment_display.c @@ -124,12 +124,28 @@ void SegmentDisplay_SerializeVar(char* buffer, macro_variable_t var) case MacroVariableType_Bool: SegmentDisplay_SerializeInt(buffer, var.asBool); break; + case MacroVariableType_String: + SegmentDisplay_SerializeString(buffer, var.asStringRef) + break; default: Macros_ReportErrorNum("Unexpected variable type:", var.type, NULL); break; } } +void SegmentDisplay_SerializeString(char* buffer, string_ref_t strRef) +{ + RETURN_IF_SEGMENT_NOT_PRESENT; + // copy the first 3 chars of the string, pad with spaces if shorter. + for (uint8_t i = 0; i < 3; i++) { + if (i < strRef.len) { + buffer[i] = ((char*)ValidatedUserConfigBuffer.buffer)[strRef.offset + i]; + } else { + buffer[i] = ' '; + } + } +} + void SegmentDisplay_SerializeFloat(char* buffer, float num) { RETURN_IF_SEGMENT_NOT_PRESENT; diff --git a/right/src/segment_display.h b/right/src/segment_display.h index 54e128502..0a7fdf8af 100644 --- a/right/src/segment_display.h +++ b/right/src/segment_display.h @@ -34,6 +34,7 @@ void SegmentDisplay_SetText(uint8_t len, const char* text, segment_display_slot_t slot); void SegmentDisplay_SetInt(int32_t a, segment_display_slot_t slot); void SegmentDisplay_SetFloat(float a, segment_display_slot_t slot); + void SegmentDisplay_SerializeString(char* buffer, string_ref_t strRef); void SegmentDisplay_SerializeInt(char* buffer, int32_t a); void SegmentDisplay_SerializeFloat(char* buffer, float f); void SegmentDisplay_SerializeVar(char* buffer, macro_variable_t var); From f9de55072a10d30c2468d59a27f8acf279807005 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 00:12:41 +0100 Subject: [PATCH 096/113] StringRefToSegment() to get access to the actual string --- right/src/macros/vars.h | 1 + right/src/segment_display.c | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index b328087f0..b46f7856d 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -90,6 +90,7 @@ void Macros_SerializeVar(char* buffer, uint8_t len, macro_variable_t var); bool TryExpandMacroTemplateOnce(parser_context_t* ctx); + string_segment_t StringRefToSegment(string_ref_t ref); macro_argument_alloc_result_t Macros_AllocateMacroArgument(uint8_t owner, const char *idStart, const char *idEnd, macro_argument_type_t type, uint8_t argNumber); void Macros_DeallocateMacroArgumentsByOwner(uint8_t owner); uint8_t Macros_CountMacroArgumentsByOwner(uint8_t owner); diff --git a/right/src/segment_display.c b/right/src/segment_display.c index 5d3e66aa9..b92bc60ba 100644 --- a/right/src/segment_display.c +++ b/right/src/segment_display.c @@ -125,7 +125,7 @@ void SegmentDisplay_SerializeVar(char* buffer, macro_variable_t var) SegmentDisplay_SerializeInt(buffer, var.asBool); break; case MacroVariableType_String: - SegmentDisplay_SerializeString(buffer, var.asStringRef) + SegmentDisplay_SerializeString(buffer, var.asStringRef); break; default: Macros_ReportErrorNum("Unexpected variable type:", var.type, NULL); @@ -136,15 +136,17 @@ void SegmentDisplay_SerializeVar(char* buffer, macro_variable_t var) void SegmentDisplay_SerializeString(char* buffer, string_ref_t strRef) { RETURN_IF_SEGMENT_NOT_PRESENT; + string_segment_t strSeg = StringRefToSegment(strRef); + // copy the first 3 chars of the string, pad with spaces if shorter. for (uint8_t i = 0; i < 3; i++) { if (i < strRef.len) { - buffer[i] = ((char*)ValidatedUserConfigBuffer.buffer)[strRef.offset + i]; + buffer[i] = strSeg.start[i]; } else { buffer[i] = ' '; } } -} +} void SegmentDisplay_SerializeFloat(char* buffer, float num) { From de0978d8f795502e5968fc61d3d1b7229baaa79a Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 00:48:29 +0100 Subject: [PATCH 097/113] better errors when template expansion fails --- right/src/macros/vars.c | 16 +++++++++++----- right/src/macros/vars.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index abdb4f13a..17021c7fd 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -1259,18 +1259,24 @@ static bool expandArgumentInplace(parser_context_t* ctx, uint8_t argNumber) { bool TryExpandMacroTemplateOnce(parser_context_t* ctx) { ASSERT(*ctx->at == '&'); + // save context position to restore if the "try" fails + const char *savedAt = ctx->at; + ctx->at++; Trace_Printc("e1"); if (ConsumeToken(ctx, "macroArg")) { - ConsumeOneDot(ctx); - uint8_t argId = Macros_ConsumeInt(ctx); - expandArgumentInplace(ctx, argId); - return true; + if(ConsumeOneDot(ctx)) { + uint8_t argId = Macros_ConsumeInt(ctx); + expandArgumentInplace(ctx, argId); + return true; + } } - ctx->at--; + // restore parser context if no expansion was performed + ctx->at = savedAt; + return false; } diff --git a/right/src/macros/vars.h b/right/src/macros/vars.h index b46f7856d..f6fd8127a 100644 --- a/right/src/macros/vars.h +++ b/right/src/macros/vars.h @@ -59,12 +59,12 @@ typedef struct { uint8_t owner; // MACRO_STATE_SLOT() of the macro that owns this argument - macro_argument_type_t type; uint8_t idx; // index of the argument in the macro's argument list (1-based) // (we could always calculate idx by looping through the pool, // but returning argument+index separately everywhere becomes // a nightmare...) string_ref_t name; // macro argument name (identifier) + macro_argument_type_t type; } macro_argument_t; typedef enum { From e1733b1cc56d68ca1980fb7111009ae8cdf89e55 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 01:31:38 +0100 Subject: [PATCH 098/113] keyid and scancode types now also supported --- right/src/macros/scancode_commands.c | 1 + right/src/macros/vars.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index 3ffa454a2..711cb7197 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -534,6 +534,7 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s }; dequoteContext(&stringCtx); // remove enclosing quotes if they exist (hack to allow simple strings) action = decodeKeyAndConsume(&stringCtx, type); + // the next part should not be necessary, because dequoteContext should have already removed the quotes. if (stringCtx.at < stringCtx.end && (*stringCtx.at == '\'' || *stringCtx.at == '"')) { stringCtx.at++; } diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 17021c7fd..08ef8098a 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -177,8 +177,7 @@ static macro_variable_t consumeKeyIdValue(parser_context_t* ctx) { uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); if (keyId == 255) { - macro_variable_t res = consumeValue(ctx); - return coalesceType(ctx, res, MacroVariableType_Int); + return consumeIntValue(ctx); } return intVar(keyId); } From 371241570684827c7b7fc4b4eccc7ba614bdc774 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 01:59:48 +0100 Subject: [PATCH 099/113] comment updated --- right/src/macros/scancode_commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/scancode_commands.c b/right/src/macros/scancode_commands.c index 711cb7197..4ff777489 100644 --- a/right/src/macros/scancode_commands.c +++ b/right/src/macros/scancode_commands.c @@ -512,7 +512,7 @@ macro_result_t Macros_ProcessKeyCommandAndConsume(parser_context_t* ctx, macro_s macro_action_t action; - // TODO: allow $macroArg.xxx for type scancode ("modded scancode") here as well. + // Allow $macroArg.xxx for type scancode ("modded scancode") here as well. // - check for $ // - if found, call Macros_ConsumeString() to get a string segment (uses consumeValue()) // - Macros_ConsumeString() is new (in vars.c) and should coalesceType to string From 40081fb0ddb3e5bc3aa2c3d93c472efb93ec68ab Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 10:45:38 +0100 Subject: [PATCH 100/113] small refactor in arg bounds checking, no semantic change --- right/src/macros/status_buffer.c | 12 ++++++------ right/src/macros/vars.c | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/right/src/macros/status_buffer.c b/right/src/macros/status_buffer.c index 326d9f6b4..65029757d 100644 --- a/right/src/macros/status_buffer.c +++ b/right/src/macros/status_buffer.c @@ -224,7 +224,7 @@ static uint16_t findPosition(const char* arg) const char* startOfLine = S->ms.currentMacroAction.cmd.text + S->ls->ms.commandBegin; const char* endOfLine = S->ms.currentMacroAction.cmd.text + S->ls->ms.commandEnd; - if (arg < startOfLine || endOfLine < arg) { + if (arg < startOfLine || arg > endOfLine) { return 1; } return arg - startOfLine + 1; @@ -240,7 +240,7 @@ static uint16_t findPositionCtx(const parser_context_t* ctx) ctx = ViewContext(0); } - if (ctx->at < ctx->begin || ctx->end < ctx->at) { + if (ctx->at < ctx->begin || ctx->at > ctx->end) { return 1; } @@ -293,9 +293,9 @@ static void reportCommandLocation(uint16_t line, uint16_t pos, const char* begin } static void reportLocationStackLevel(const parser_context_t* ctx, uint16_t line, uint8_t indent) { - uint16_t pos = ctx->at - ctx->begin; - bool positionIsValid = ctx->begin <= ctx->at && ctx->at <= ctx->end; + bool positionIsValid = ctx->at >= ctx->begin && ctx->at <= ctx->end; if (positionIsValid) { + uint16_t pos = ctx->at - ctx->begin; reportCommandLocation(line, pos, ctx->begin, ctx->end, positionIsValid, indent); } else { Macros_SetStatusString("> Position not available here.\n", NULL); @@ -320,7 +320,6 @@ static void reportError( Macros_SetStatusString(err, NULL); if (S != NULL) { - bool argIsCommand = ValidatedUserConfigBuffer.buffer <= (uint8_t*)arg && (uint8_t*)arg < ValidatedUserConfigBuffer.buffer + USER_CONFIG_SIZE; if (arg != NULL && arg != argEnd) { Macros_SetStatusString(" ", NULL); Macros_SetStatusString(arg, TokEnd(arg, argEnd)); @@ -329,7 +328,8 @@ static void reportError( const char* startOfLine = S->ms.currentMacroAction.cmd.text + S->ls->ms.commandBegin; const char* endOfLine = S->ms.currentMacroAction.cmd.text + S->ls->ms.commandEnd; uint16_t line = findCurrentCommandLine(); - if (startOfLine <= arg && arg <= endOfLine) { + if (arg != NULL && argEnd != NULL && argEnd >= startOfLine && arg <= endOfLine) { + bool argIsCommand = (uint8_t*)arg >= ValidatedUserConfigBuffer.buffer && (uint8_t*)arg < ValidatedUserConfigBuffer.buffer + USER_CONFIG_SIZE; reportCommandLocation(line, arg - startOfLine, startOfLine, endOfLine, argIsCommand, 0); } else if (ctx != NULL) { reportLocationStack(ctx, line); diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 08ef8098a..263da813c 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -185,10 +185,10 @@ static macro_variable_t consumeKeyIdValue(parser_context_t* ctx) static macro_variable_t consumeScancodeValue(parser_context_t* ctx) // consume "scancode" = modded scancode = "shortcut", return as string variable. { + // this function could preparse the Mods+Scancode for validity, e.g. + // macro_action_t action = decodeKeyAndConsume(ctx, MacroSubAction_None); + // but for now we return the raw string. const char* atStart = ctx->at; - // should preparse the Mods+Scancode for validity. - // macro_action_t action = decodeKeyAndConsume(ctx, MacroSubAction_None); - // const char* atEnd = ctx->at; const char* atEnd = TokEnd(ctx->at, ctx->end); ConsumeWhiteAt(ctx, atEnd); From 38275b2f29bc5789e91d862b0c13f9119df5a965 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 11:49:58 +0100 Subject: [PATCH 101/113] better error reporting when setLedTxt cannot parse a value given --- right/src/macros/display.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index bc7ab20ca..72bb6555c 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -37,6 +37,10 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le if (Macros_IsNUM(ctx)) { #ifndef __ZEPHYR__ macro_variable_t value = Macros_ConsumeAnyValue(ctx); + if (value.type == MacroVariableType_None) { + Macros_ReportErrorTok(ctx, "Value expected but found:"); + return 0; + } SegmentDisplay_SerializeVar(str, value); textLen = 3; #else From 4b4524913a406100711830d72d8f5f2c2693f775 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 12:02:50 +0100 Subject: [PATCH 102/113] error message wording --- right/src/macros/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/display.c b/right/src/macros/display.c index 72bb6555c..c8d16ded4 100644 --- a/right/src/macros/display.c +++ b/right/src/macros/display.c @@ -38,7 +38,7 @@ static uint8_t consumeDisplayString(parser_context_t* ctx, char* str, uint8_t le #ifndef __ZEPHYR__ macro_variable_t value = Macros_ConsumeAnyValue(ctx); if (value.type == MacroVariableType_None) { - Macros_ReportErrorTok(ctx, "Value expected but found:"); + Macros_ReportErrorTok(ctx, "Could not resolve:"); return 0; } SegmentDisplay_SerializeVar(str, value); From c8ed56a3bd5b6150b47efd1f712712805582d63d Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Wed, 4 Mar 2026 17:27:06 +0100 Subject: [PATCH 103/113] mostly comments refinements, and removal of code that was commented out already. Also, moving additional bit into existing bitfield in macro_state_t to avoid additional alignment bytes to next 4-aligned boundary. --- right/src/macros/commands.c | 9 ++--- right/src/macros/core.h | 4 +-- right/src/macros/vars.c | 67 ++++++++++++++++++------------------- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index f3a266271..6fae69be8 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1370,13 +1370,12 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat uint8_t Macros_TryConsumeKeyId(parser_context_t* ctx) { - // TODO: allow $macroArg.xxx for type keyId here as well. - // - this should already work via Macros_ConsumeInt() -> consumeValue() chain. - uint8_t keyId = MacroKeyIdParser_TryConsumeKeyId(ctx); if (keyId == 255 && isNUM(ctx)) { - uint8_t num = Macros_ConsumeInt(ctx); + // through the consumeValue() chain, this will parse plain integers, + // numeric variables, and $macroArg of type :keyId (in both text or numeric form). + uint8_t num = Macros_ConsumeInt(ctx); if (Macros_ParserError) { return 255; } else { @@ -2486,6 +2485,8 @@ static macro_result_t processCommand(parser_context_t* ctx) if (cmdTokEnd > ctx->at && cmdTokEnd[-1] == ':') { //skip labels ConsumeAnyToken(ctx); + // TODO: why not just IsEnd() in this condition? + // The ctx->at check is done inside IsEnd() anyway. if (/* ctx->at >= ctx->end && */ IsEnd(ctx)) { return MacroResult_Finished; } diff --git a/right/src/macros/core.h b/right/src/macros/core.h index 0d0e34c72..877bb2306 100644 --- a/right/src/macros/core.h +++ b/right/src/macros/core.h @@ -173,11 +173,9 @@ macro_autorepeat_state_t autoRepeatPhase: 1; bool isDoubletap: 1; secondary_role_state_t secondaryRoleState: 2; + bool macroHeadersProcessed : 1; // ---- 4-aligned ---- macro_usb_keyboard_reports_t reports; - - //uint8_t argumentCount : 4; // TODO: we don't need this; we can calculate it using Macros_CountMacroArgumentsByOwner() - bool macroHeadersProcessed : 1; } ms; // action scope data diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index a3b9b48be..a13f9f518 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -259,7 +259,7 @@ static macro_variable_t consumeVariable(parser_context_t* ctx) } ConsumeAnyIdentifier(ctx); - return (macro_variable_t){}; + return (macro_variable_t){}; // TODO: shouldn't this be noneVar()? } // Expects @@ -532,7 +532,7 @@ static macro_variable_t consumeValue(parser_context_t* ctx) } failed: - return consumeStringLiteral(ctx); + return consumeStringLiteral(ctx); // try reading as a raw string literal #if 0 if (IsIdentifierChar(*ctx->at)) { @@ -1204,38 +1204,37 @@ static macro_variable_t consumeArgumentAsValue(parser_context_t* ctx) { return consumeValue(&varCtx); #endif } else { - // for declared types, consume the value according to type. - parser_context_t varCtx = (parser_context_t) { - .at = str.start, - .begin = str.start, - .end = str.end, - .macroState = ctx->macroState, - .nestingLevel = ctx->nestingLevel, - .nestingBound = ctx->nestingBound, - }; - - switch (argType) { - case MacroArgType_Int: - return consumeIntValue(&varCtx); - case MacroArgType_Float: - return consumeFloatValue(&varCtx); - case MacroArgType_Bool: - return consumeBool(&varCtx); - case MacroArgType_String: { - // this used to be consumeStringLiteral, but that leads to $-expansions - // within the string, even if not enclosed in double-quotes. - // Values configured for arguments should be interpreted as verbatim strings - // without expansions. - // Use type 'any' if you want $-expansions in your arguments. - return consumeStringVerbatim(&varCtx); - } - case MacroArgType_KeyId: - return consumeKeyIdValue(&varCtx); - case MacroArgType_ScanCode: - return consumeScancodeValue(&varCtx); - default: - Macros_ReportErrorNum("Unexpected argument type:", argType, NULL); - return noneVar(); + // for declared types, consume the value according to type. + parser_context_t varCtx = (parser_context_t) { + .at = str.start, + .begin = str.start, + .end = str.end, + .macroState = ctx->macroState, + .nestingLevel = ctx->nestingLevel, + .nestingBound = ctx->nestingBound, + }; + + switch (argType) { + case MacroArgType_Int: + return consumeIntValue(&varCtx); + case MacroArgType_Float: + return consumeFloatValue(&varCtx); + case MacroArgType_Bool: + return consumeBool(&varCtx); + case MacroArgType_String: + // this used to be consumeStringLiteral, but that leads to $-expansions + // within the string, even if not enclosed in double-quotes. + // Values configured for arguments of type string should be interpreted + // as verbatim strings without expansions. + // Use type 'any' if you want $-expansions in your arguments. + return consumeStringVerbatim(&varCtx); + case MacroArgType_KeyId: + return consumeKeyIdValue(&varCtx); + case MacroArgType_ScanCode: + return consumeScancodeValue(&varCtx); + default: + Macros_ReportErrorNum("Unexpected argument type:", argType, NULL); + return noneVar(); } } } From 77023f32ff9905361b6eef95795efc2f472a2bd6 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Thu, 5 Mar 2026 17:24:43 +0100 Subject: [PATCH 104/113] update to reference manual with description and grammar for macroArg command. --- doc-dev/reference-manual.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc-dev/reference-manual.md b/doc-dev/reference-manual.md index f6ae84b22..af70db928 100644 --- a/doc-dev/reference-manual.md +++ b/doc-dev/reference-manual.md @@ -41,7 +41,7 @@ $onJoin $onSplit ``` -i.e., if you want to customize the acceleration driver for your trackball module on keymap QWR, create a macro named `$onKeymapChange QWR`, with content e.g.: +For example, if you want to customize the acceleration driver for your trackball module on keymap QWR, create a macro named `$onKeymapChange QWR` containing: ``` set module.trackball.baseSpeed 0.5 @@ -195,6 +195,8 @@ COMMAND = set bluetooth.enabled BOOL COMMAND = set bluetooth.alwaysAdvertiseHid BOOL COMMAND = set modifierLayerTriggers.{shift|alt|super|ctrl} {left|right|both} COMMAND = ¯oArg. +COMMAND = macroArg [: MACROARG_TYPE] +MACROARG_TYPE = { int | float | bool | string | keyid | scancode | any } CONDITION = CONDITION = if (EXPRESSION) CONDITION = else @@ -233,7 +235,7 @@ KEYMAPID = |last|current MACROID = last | | OPERATOR = + | - | * | / | % | < | > | <= | >= | == | != | && | || VARIABLE_EXPANSION = $ | $ -VARIABLE_EXPANSION = $currentAddress | $currentTime | $thisKeyId | $queuedKeyId. | $keyId.KEYID_ABBREV | $uhk.name | $macroArg. +VARIABLE_EXPANSION = $currentAddress | $currentTime | $thisKeyId | $queuedKeyId. | $keyId.KEYID_ABBREV | $uhk.name | $macroArg. | $macroArg. EXPRESSION = | (EXPRESSION) | INT | BOOL | FLOAT | VARIABLE_EXPANSION | EXPRESSION OPERATOR EXPRESSION | !EXPRESSION | min(EXPRESSION [, EXPRESSION]+) | max(EXPRESSION [, EXPRESSION]+) EXPRESSION = STRING == STRING | STRING != STRING PARENTHESSED_EXPRESSION = (EXPRESSION) @@ -609,6 +611,12 @@ Key actions can be parametrized with macro arguments. These arguments can be exp - the argument bounds must correspond to token bounds in the fully expanded string - the argument cannot span multiple lines +### Named Arguments + +Macro arguments can also be named and typed by declaring them using the `macroArg` command at the beginning of the macro. + +Such named arguments can also be accessed using `$macroArg.`, and the value provided by Agent will be parsed according to the argument type. + ### Configuration options - `set stickyModifiers {never|smart|always}` globally turns on or off sticky modifiers. This affects only standard scancode actions. Macro actions (both gui and command ones) are always nonsticky, unless `sticky` flag is included in `tapKey|holdKey|pressKey` commands. Default value is `smart`, which is the official behaviour - i.e., ` + ` are sticky. From 507594ec3ec3bf03e527d2cc718a72a48c86570b Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Mar 2026 11:02:56 +0100 Subject: [PATCH 105/113] user manual update --- doc-dev/user-guide.md | 13 +++++++++++-- right/src/macros/vars.c | 13 +++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/doc-dev/user-guide.md b/doc-dev/user-guide.md index 482142f1e..347eed7fe 100644 --- a/doc-dev/user-guide.md +++ b/doc-dev/user-guide.md @@ -55,9 +55,9 @@ ifDoubletap tapKey capsLock - Provided value bounds are informational only - they denote values that seem to make sense. Sometimes default values are marked. - If you are still not sure about some feature or syntax, do not hesitate to ask. -3) If `ERR` appears on the display, you can retrieve the description by using `printStatus` over a focused text editor. Or, using the above point, just search the [reference manual](reference-manual.md) for `ERR`. +3) If `ERR` appears on the display (UHK60), or a triangle warning icon `⚠️` is shown on the display (UHK80), you can retrieve the error description by using `printStatus` over a focused text editor. In addition, a yellow error pane will automatically appear in Agent, showing the error description. You can also search the [reference manual](reference-manual.md) for `ERR`. -4) If you encounter a bug, let me know. There are lots of features and quite few users around this codebase - if you do not report problems you find, chances are that no one else will (since most likely no one else has noticed). +4) If you encounter a bug, let us know (ideally, by reporting a GitHub issue). There are lots of features and quite few users around this codebase - if you do not report problems you find, chances are that no one else will (since most likely no one else has noticed). ## Known software limitations and oddities @@ -114,6 +114,15 @@ replaceLayer mod QTY mod replaceLayer mouse QTY mouse ``` +Alternatively, you can also replace all of the keymap first, and the just load the base layer of your keymap again. For this approach, your macro would be: + +``` +replaceKeymap QTY +overlayKeymap current +``` + +This second method also allows you to only define some keys on the COL base layer that need to change from QTY. Any key mapped to "None" inherits its action from QTY, as `overlayKeymap` will not overload it. + ## Examples Every nonempty line is considered as one command. Empty line, or commented line too. Empty lines are skipped. Exception is an empty command action, which counts for one command. Even `{` and `}` are treated as commands, and have to be on separate lines. diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index ef35c8e60..9861f5d2a 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -334,8 +334,7 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - Macros_ReportErrorNum("Unexpected variable type:", value.type, NULL); - break; + goto unexpected_variable_type; } break; case MacroVariableType_Float: @@ -355,8 +354,7 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - Macros_ReportErrorNum("Unexpected variable type:", value.type, NULL); - break; + goto unexpected_variable_type; } break; case MacroVariableType_Bool: @@ -376,8 +374,7 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - Macros_ReportErrorNum("Unexpected variable type:", value.type, NULL); - break; + goto unexpected_variable_type; } break; case MacroVariableType_String: @@ -393,13 +390,13 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - Macros_ReportErrorNum("Unexpected variable type:", value.type, NULL); - break; + goto unexpected_variable_type; } break; case MacroVariableType_None: break; default: +unexpected_variable_type: Macros_ReportErrorNum("Unexpected variable type:", dstType, NULL); break; } From 2d0a00339ee96b413eb2d537b3848c2e2c505e1e Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Mar 2026 12:54:18 +0100 Subject: [PATCH 106/113] refactor goto --- right/src/macros/vars.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index 9861f5d2a..bbd99f5cb 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -310,6 +310,14 @@ static macro_variable_t* consumeVarAndAllocate(parser_context_t* ctx) return res; } +static macro_variable_t reportUnexpectedVariableType(parser_context_t* ctx, macro_variable_type_t type) +{ + // TODO: would there be any way to trace the current position down the + // context stack and report it here? + Macros_ReportErrorNum("Unexpected variable type:", type, NULL); + return noneVar(); +} + static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t value, macro_variable_type_t dstType) { if (value.type == dstType) { @@ -334,7 +342,7 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - goto unexpected_variable_type; + return reportUnexpectedVariableType(ctx, value.type); } break; case MacroVariableType_Float: @@ -354,7 +362,7 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - goto unexpected_variable_type; + return reportUnexpectedVariableType(ctx, value.type); } break; case MacroVariableType_Bool: @@ -374,7 +382,7 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - goto unexpected_variable_type; + return reportUnexpectedVariableType(ctx, value.type); } break; case MacroVariableType_String: @@ -390,15 +398,13 @@ static macro_variable_t coalesceType(parser_context_t* ctx, macro_variable_t val case MacroVariableType_None: break; default: - goto unexpected_variable_type; + return reportUnexpectedVariableType(ctx, value.type); } break; case MacroVariableType_None: break; default: -unexpected_variable_type: - Macros_ReportErrorNum("Unexpected variable type:", dstType, NULL); - break; + return reportUnexpectedVariableType(ctx, value.type);; } value.type = dstType; return value; From 3171b3f97e9e95a055e0d94d2662caaf2e89fe6c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Mar 2026 14:00:29 +0100 Subject: [PATCH 107/113] comment update for more clarity --- right/src/macros/vars.c | 1 + 1 file changed, 1 insertion(+) diff --git a/right/src/macros/vars.c b/right/src/macros/vars.c index bbd99f5cb..11176e75e 100644 --- a/right/src/macros/vars.c +++ b/right/src/macros/vars.c @@ -996,6 +996,7 @@ string_segment_t Macros_ConsumeString(parser_context_t* ctx) { macro_variable_t res = consumeValue(ctx); if (res.type != MacroVariableType_String) { + // Do not report error directly here, just return a "null" string. // Macros_ReportError("String value expected but found:", NULL, NULL); return (string_segment_t){ .start = NULL, .end = NULL }; } From b42fef38de8f6e205128ebfd3ef9118e5e22961b Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Mar 2026 17:42:03 +0100 Subject: [PATCH 108/113] added comment --- right/src/macros/keyid_parser.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/right/src/macros/keyid_parser.c b/right/src/macros/keyid_parser.c index 8514e8add..516ffc0c6 100644 --- a/right/src/macros/keyid_parser.c +++ b/right/src/macros/keyid_parser.c @@ -166,6 +166,8 @@ static const lookup_record_t* lookup(uint8_t begin, uint8_t end, const char* str } } +// this attempts to consume a KEYID_ABBREV (textual key id)). +// returns the key id, or 255 if not found (invalid key id). uint8_t MacroKeyIdParser_TryConsumeKeyId(parser_context_t* ctx) { // this gets identifier till the next dot only From 3358bd47262103449a59b58b13a545a16ce66d2c Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Fri, 6 Mar 2026 17:51:47 +0100 Subject: [PATCH 109/113] reorder of boundary safety checks --- right/src/macros/commands.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 0acbaadca..9e4220013 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1272,7 +1272,7 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat CTX_COPY(ctx2, *ctx); uint8_t totalArgs = 0; uint8_t argKeyId = 255; - while((argKeyId = Macros_TryConsumeKeyId(&ctx2)) != 255 && ctx2.at < ctx2.end) { + while(ctx2.at < ctx2.end && (argKeyId = Macros_TryConsumeKeyId(&ctx2)) != 255) { totalArgs++; } if (totalArgs > PostponerQuery_PendingKeypressCount()) { @@ -1285,7 +1285,7 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat uint8_t numArgs = 0; bool someoneNotReleased = false; uint8_t argKeyId = 255; - while((argKeyId = Macros_TryConsumeKeyId(ctx)) != 255 && ctx->at < ctx->end) { + while(ctx->at < ctx->end && (argKeyId = Macros_TryConsumeKeyId(ctx)) != 255) { numArgs++; if (pendingCount < numArgs || insufficientNumberForAnyOrder) { uint32_t referenceTime = transitive && pendingCount > 0 ? PostponerExtended_LastPressTime() : S->ms.currentMacroStartTime; @@ -1318,14 +1318,14 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat } } else if (orGate) { - // go through all canidates all at once + // go through all candidates all at once while (true) { // first keyid had already been processed. if (PostponerQuery_ContainsKeyId(argKeyId)) { numArgs = 1; goto matched; } - if ((argKeyId = Macros_TryConsumeKeyId(ctx)) == 255 || ctx->at == ctx->end) { + if (ctx->at == ctx->end || (argKeyId = Macros_TryConsumeKeyId(ctx)) == 255) { break; } } From feb362c217cef7162b0d7d8addf041d399cae9d3 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 9 Mar 2026 14:08:48 +0100 Subject: [PATCH 110/113] improved safety of context boundary check --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 9e4220013..7950c7ff7 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -1325,7 +1325,7 @@ static macro_result_t processIfShortcutCommand(parser_context_t* ctx, bool negat numArgs = 1; goto matched; } - if (ctx->at == ctx->end || (argKeyId = Macros_TryConsumeKeyId(ctx)) == 255) { + if (ctx->at >= ctx->end || (argKeyId = Macros_TryConsumeKeyId(ctx)) == 255) { break; } } From 7c17135632bcfb74f1d3c70a17616434b07e1797 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 9 Mar 2026 14:11:31 +0100 Subject: [PATCH 111/113] strange test --- right/src/macros/commands.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 7950c7ff7..739a744c1 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -962,6 +962,9 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) macro_argument_type_t argType; if (ConsumeToken(ctx, ":")) { + // strange test: + *ctx->at = 'i'; + // end of test if (ConsumeToken(ctx, "int")) { argType = MacroArgType_Int; } From 4e8e6fe60c02d28286c3a3b695bd14ce2d4c1cb9 Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 9 Mar 2026 14:20:22 +0100 Subject: [PATCH 112/113] force config overwrite for strange test --- right/src/macros/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 739a744c1..637afce9c 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -963,7 +963,7 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) if (ConsumeToken(ctx, ":")) { // strange test: - *ctx->at = 'i'; + *((char *)ctx->at) = 'i'; // end of test if (ConsumeToken(ctx, "int")) { argType = MacroArgType_Int; From 6c4fc5b308419bddf639c5bdf624f2c86c3ce7cb Mon Sep 17 00:00:00 2001 From: Maximilian Hantsch Date: Mon, 9 Mar 2026 15:42:03 +0100 Subject: [PATCH 113/113] back to non-strange things --- right/src/macros/commands.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/right/src/macros/commands.c b/right/src/macros/commands.c index 637afce9c..7950c7ff7 100644 --- a/right/src/macros/commands.c +++ b/right/src/macros/commands.c @@ -962,9 +962,6 @@ static macro_result_t processMacroArgCommand(parser_context_t* ctx) macro_argument_type_t argType; if (ConsumeToken(ctx, ":")) { - // strange test: - *((char *)ctx->at) = 'i'; - // end of test if (ConsumeToken(ctx, "int")) { argType = MacroArgType_Int; }