Calculator

Source: src/calculator.c · 527 lines · 34 functions.

Screen captures

Views of this part running on the Mini Arcade.

Calculator screen on the Mini Arcade
Calculator showing its keypad and value display.

Module entry points

These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.

How to read these diagrams

Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.

These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.

Functions in this file

isDigitKey() · source line 62
static bool isDigitKey(char key)
  1. Exit this functionreturn (key >= '0' && key <= '9') || (key >= 'A' && key <= 'F');
Read original function source
static bool isDigitKey(char key)
{
    return (key >= '0' && key <= '9') || (key >= 'A' && key <= 'F');
}
digitValue() · source line 67
static uint8_t digitValue(char key)
  1. Exit this functionreturn key >= 'A' ? (uint8_t)(key - 'A' + 10) : (uint8_t)(key - '0');
Read original function source
static uint8_t digitValue(char key)
{
    return key >= 'A' ? (uint8_t)(key - 'A' + 10) : (uint8_t)(key - '0');
}
inputBase() · source line 72
static uint8_t inputBase(void)
  1. Exit this functionreturn ascii_mode ? 16U : base;
Read original function source
static uint8_t inputBase(void)
{
    return ascii_mode ? 16U : base;
}
keyIsAvailable() · source line 77
static bool keyIsAvailable(const calculator_key_t *key)

Calls into this documentation:

  1. Decisionkey->type == KEY_NONE
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Exit this functionreturn key->type != KEY_DIGIT || digitValue(key->value) < inputBase();
Read original function source
static bool keyIsAvailable(const calculator_key_t *key)
{
    if (key->type == KEY_NONE) return false;
    return key->type != KEY_DIGIT || digitValue(key->value) < inputBase();
}
logicLabel() · source line 83
static const char *logicLabel(void)
  1. Decisionlogic_operator == '&'
    True
    1. Exit this functionreturn "AND";
    False

    Continue after this decision.

  2. Exit this functionreturn logic_operator == '|' ? "OR" : "XOR";
Read original function source
static const char *logicLabel(void)
{
    if (logic_operator == '&') return "AND";
    return logic_operator == '|' ? "OR" : "XOR";
}
operatorLabel() · source line 89
static const char *operatorLabel(char operation)
  1. Select a caseoperation

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase '+':
      2. Exit this functionreturn "ADD";
      3. Case / labelcase '-':
      4. Exit this functionreturn "SUB";
      5. Case / labelcase '*':
      6. Exit this functionreturn "MUL";
      7. Case / labelcase '/':
      8. Exit this functionreturn "DIV";
      9. Case / labelcase '%':
      10. Exit this functionreturn "MOD";
      11. Case / labelcase 'P':
      12. Exit this functionreturn "POWER";
      13. Case / labelcase '&':
      14. Exit this functionreturn "AND";
      15. Case / labelcase '|':
      16. Exit this functionreturn "OR";
      17. Case / labelcase '^':
      18. Exit this functionreturn "XOR";
      19. Case / labeldefault:
      20. Exit this functionreturn "";
Read original function source
static const char *operatorLabel(char operation)
{
    switch (operation) {
    case '+': return "ADD";
    case '-': return "SUB";
    case '*': return "MUL";
    case '/': return "DIV";
    case '%': return "MOD";
    case 'P': return "POWER";
    case '&': return "AND";
    case '|': return "OR";
    case '^': return "XOR";
    default: return "";
    }
}
clearCalculator() · source line 105
static void clearCalculator(void)
  1. Executeentry_active = false;
  2. Executehas_accumulator = false;
  3. Executeentry_value = 0U;
  4. Executeaccumulator = 0U;
  5. Executepending_operator = '\0';
  6. Executeerror_text = NULL;
Read original function source
static void clearCalculator(void)
{
    entry_active = false;
    has_accumulator = false;
    entry_value = 0U;
    accumulator = 0U;
    pending_operator = '\0';
    error_text = NULL;
}
displayedValue() · source line 115
static uint64_t displayedValue(void)
  1. Exit this functionreturn entry_active ? entry_value : (has_accumulator ? accumulator : 0U);
Read original function source
static uint64_t displayedValue(void)
{
    return entry_active ? entry_value : (has_accumulator ? accumulator : 0U);
}
formatValue() · source line 120
static void formatValue(uint64_t value, char *text, size_t text_size)

Calls into this documentation:

  1. Executestatic const char digits[] = "0123456789ABCDEF";
  2. Executechar reversed[65];
  3. Executesize_t length = 0U;
  4. DO / WHILE loopvalue != 0U && length < sizeof(reversed)

    Run body, then test condition; repeat while true.

      1. Executereversed[length++] = digits[value % inputBase()];
      2. Executevalue /= inputBase();
  5. Decisiontext_size <= length
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  6. FOR loopsize_t index = 0U; index < length; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Executetext[index] = reversed[length - index - 1U];
  7. Executetext[length] = '\0';
Read original function source
static void formatValue(uint64_t value, char *text, size_t text_size)
{
    static const char digits[] = "0123456789ABCDEF";
    char reversed[65];
    size_t length = 0U;

    do {
        reversed[length++] = digits[value % inputBase()];
        value /= inputBase();
    } while (value != 0U && length < sizeof(reversed));

    if (text_size <= length) return;
    for (size_t index = 0U; index < length; ++index) {
        text[index] = reversed[length - index - 1U];
    }
    text[length] = '\0';
}
multiplyChecked() · source line 138
static bool multiplyChecked(uint64_t left, uint64_t right, uint64_t *result)
  1. Decisionright != 0U && left > UINT64_MAX / right
    True
      1. Executeerror_text = "64 BIT OVERFLOW";
      2. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Execute*result = left * right;
  3. Exit this functionreturn true;
Read original function source
static bool multiplyChecked(uint64_t left, uint64_t right, uint64_t *result)
{
    if (right != 0U && left > UINT64_MAX / right) {
        error_text = "64 BIT OVERFLOW";
        return false;
    }
    *result = left * right;
    return true;
}
powerChecked() · source line 148
static bool powerChecked(uint64_t value, uint64_t exponent, uint64_t *result)

Calls into this documentation:

  1. Executeuint64_t power = 1U;
  2. Executeuint64_t factor = value;
  3. WHILE loopexponent != 0U

    Repeat body while the loop condition permits; then continue below.

      1. Decision(exponent & 1U) != 0U && !multiplyChecked(power, factor, &power)
        True
        1. Exit this functionreturn false;
        False

        Continue after this decision.

      2. Executeexponent >>= 1U;
      3. Decisionexponent != 0U && !multiplyChecked(factor, factor, &factor)
        True
        1. Exit this functionreturn false;
        False

        Continue after this decision.

  4. Execute*result = power;
  5. Exit this functionreturn true;
Read original function source
static bool powerChecked(uint64_t value, uint64_t exponent, uint64_t *result)
{
    uint64_t power = 1U;
    uint64_t factor = value;
    while (exponent != 0U) {
        if ((exponent & 1U) != 0U && !multiplyChecked(power, factor, &power)) return false;
        exponent >>= 1U;
        if (exponent != 0U && !multiplyChecked(factor, factor, &factor)) return false;
    }
    *result = power;
    return true;
}
integerSquareRoot() · source line 161
static uint64_t integerSquareRoot(uint64_t value)
  1. Executeuint64_t result = 0U;
  2. Executeuint64_t bit = UINT64_C(1) << 62U;
  3. WHILE loopbit > value

    Repeat body while the loop condition permits; then continue below.

    1. Executebit >>= 2U;
  4. WHILE loopbit != 0U

    Repeat body while the loop condition permits; then continue below.

      1. Decisionvalue >= result + bit
        True
          1. Executevalue -= result + bit;
          2. Executeresult = (result >> 1U) + bit;
        False
          1. Executeresult >>= 1U;
      2. Executebit >>= 2U;
  5. Exit this functionreturn result;
Read original function source
static uint64_t integerSquareRoot(uint64_t value)
{
    uint64_t result = 0U;
    uint64_t bit = UINT64_C(1) << 62U;
    while (bit > value) bit >>= 2U;
    while (bit != 0U) {
        if (value >= result + bit) {
            value -= result + bit;
            result = (result >> 1U) + bit;
        } else {
            result >>= 1U;
        }
        bit >>= 2U;
    }
    return result;
}
applyOperator() · source line 178
static bool applyOperator(char operation, uint64_t left, uint64_t right, uint64_t *result)

Calls into this documentation:

  1. Select a caseoperation

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase '+':
      2. Decisionleft > UINT64_MAX - right
        True
        1. Executeerror_text = "64 BIT OVERFLOW";
        False
        1. Execute*result = left + right;
      3. Exit enclosing loop or switchbreak;
      4. Case / labelcase '-':
      5. Decisionleft < right
        True
        1. Executeerror_text = "NEGATIVE RESULT";
        False
        1. Execute*result = left - right;
      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase '*':
      8. Exit this functionreturn multiplyChecked(left, right, result);
      9. Case / labelcase '/':
      10. Decisionright == 0U
        True
        1. Executeerror_text = "DIVIDE BY ZERO";
        False
        1. Execute*result = left / right;
      11. Exit enclosing loop or switchbreak;
      12. Case / labelcase '%':
      13. Decisionright == 0U
        True
        1. Executeerror_text = "DIVIDE BY ZERO";
        False
        1. Execute*result = left % right;
      14. Exit enclosing loop or switchbreak;
      15. Case / labelcase 'P':
      16. Exit this functionreturn powerChecked(left, right, result);
      17. Case / labelcase '&':
      18. Execute*result = left & right;
      19. Exit enclosing loop or switchbreak;
      20. Case / labelcase '|':
      21. Execute*result = left | right;
      22. Exit enclosing loop or switchbreak;
      23. Case / labelcase '^':
      24. Execute*result = left ^ right;
      25. Exit enclosing loop or switchbreak;
      26. Case / labeldefault:
      27. Exit this functionreturn false;
  2. Exit this functionreturn error_text == NULL;
Read original function source
static bool applyOperator(char operation, uint64_t left, uint64_t right, uint64_t *result)
{
    switch (operation) {
    case '+':
        if (left > UINT64_MAX - right) error_text = "64 BIT OVERFLOW";
        else *result = left + right;
        break;
    case '-':
        if (left < right) error_text = "NEGATIVE RESULT";
        else *result = left - right;
        break;
    case '*': return multiplyChecked(left, right, result);
    case '/':
        if (right == 0U) error_text = "DIVIDE BY ZERO";
        else *result = left / right;
        break;
    case '%':
        if (right == 0U) error_text = "DIVIDE BY ZERO";
        else *result = left % right;
        break;
    case 'P': return powerChecked(left, right, result);
    case '&': *result = left & right; break;
    case '|': *result = left | right; break;
    case '^': *result = left ^ right; break;
    default: return false;
    }
    return error_text == NULL;
}
appendDigit() · source line 207
static void appendDigit(uint8_t digit)

Calls into this documentation:

  1. Decisionerror_text != NULL
    True
    1. ExecuteclearCalculator();
    False

    Continue after this decision.

  2. Decision!entry_active
    True
      1. Executeentry_value = 0U;
      2. Executeentry_active = true;
    False

    Continue after this decision.

  3. Decisionentry_value > (UINT64_MAX - digit) / inputBase()
    True
      1. Executeerror_text = "64 BIT OVERFLOW";
      2. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executeentry_value = entry_value * inputBase() + digit;
Read original function source
static void appendDigit(uint8_t digit)
{
    if (error_text != NULL) clearCalculator();
    if (!entry_active) {
        entry_value = 0U;
        entry_active = true;
    }
    if (entry_value > (UINT64_MAX - digit) / inputBase()) {
        error_text = "64 BIT OVERFLOW";
        return;
    }
    entry_value = entry_value * inputBase() + digit;
}
selectOperator() · source line 221
static void selectOperator(char operation)

Calls into this documentation:

  1. Decisionerror_text != NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decision!has_accumulator
    True
      1. Executeaccumulator = entry_active ? entry_value : 0U;
      2. Executehas_accumulator = true;
    False
    1. Decisionpending_operator != '\0' && entry_active
      True
        1. Executeuint64_t result = 0U;
        2. Decision!applyOperator(pending_operator, accumulator, entry_value, &result)
          True
          1. Exit this functionreturn;
          False

          Continue after this decision.

        3. Executeaccumulator = result;
      False

      Continue after this decision.

  3. Executepending_operator = operation;
  4. Executeentry_active = false;
Read original function source
static void selectOperator(char operation)
{
    if (error_text != NULL) return;
    if (!has_accumulator) {
        accumulator = entry_active ? entry_value : 0U;
        has_accumulator = true;
    } else if (pending_operator != '\0' && entry_active) {
        uint64_t result = 0U;
        if (!applyOperator(pending_operator, accumulator, entry_value, &result)) return;
        accumulator = result;
    }
    pending_operator = operation;
    entry_active = false;
}
calculateResult() · source line 236
static void calculateResult(void)

Calls into this documentation:

  1. Decisionerror_text != NULL || pending_operator == '\0' || !entry_active
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeuint64_t result = 0U;
  3. Decision!applyOperator(pending_operator, accumulator, entry_value, &result)
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executeentry_value = result;
  5. Executeaccumulator = result;
  6. Executeentry_active = true;
  7. Executehas_accumulator = false;
  8. Executepending_operator = '\0';
Read original function source
static void calculateResult(void)
{
    if (error_text != NULL || pending_operator == '\0' || !entry_active) return;
    uint64_t result = 0U;
    if (!applyOperator(pending_operator, accumulator, entry_value, &result)) return;
    entry_value = result;
    accumulator = result;
    entry_active = true;
    has_accumulator = false;
    pending_operator = '\0';
}
calculateRoot() · source line 248
static void calculateRoot(void)

Calls into this documentation:

  1. Decisionerror_text != NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeentry_value = integerSquareRoot(displayedValue());
  3. Executeentry_active = true;
Read original function source
static void calculateRoot(void)
{
    if (error_text != NULL) return;
    entry_value = integerSquareRoot(displayedValue());
    entry_active = true;
}
moveCursor() · source line 255
static void moveCursor(int row_delta, int column_delta)

Calls into this documentation:

  1. FOR loopunsigned tries = 0U; tries < CALCULATOR_ROWS * CALCULATOR_COLUMNS; ++tries

    Repeat body while the loop condition permits; then continue below.

      1. Executeselected_row = (uint8_t)((selected_row + row_delta + CALCULATOR_ROWS) % CALCULATOR_ROWS);
      2. Executeselected_column = (uint8_t)((selected_column + column_delta + CALCULATOR_COLUMNS) % CALCULATOR_COLUMNS);
      3. DecisionkeyIsAvailable(&keypad[selected_row][selected_column])
        True
        1. Exit this functionreturn;
        False

        Continue after this decision.

Read original function source
static void moveCursor(int row_delta, int column_delta)
{
    for (unsigned tries = 0U; tries < CALCULATOR_ROWS * CALCULATOR_COLUMNS; ++tries) {
        selected_row = (uint8_t)((selected_row + row_delta + CALCULATOR_ROWS) %
                                 CALCULATOR_ROWS);
        selected_column = (uint8_t)((selected_column + column_delta +
                                     CALCULATOR_COLUMNS) % CALCULATOR_COLUMNS);
        if (keyIsAvailable(&keypad[selected_row][selected_column])) return;
    }
}
changeBase() · source line 266
static void changeBase(bool increase)

Calls into this documentation:

  1. Decisionascii_mode
    True
      1. Executeascii_mode = false;
      2. Executebase = increase ? CALCULATOR_MIN_BASE : CALCULATOR_MAX_BASE;
    False
    1. Decisionincrease && base == CALCULATOR_MAX_BASE
      True
        1. Executeascii_mode = true;
      False
      1. Decision!increase && base == CALCULATOR_MIN_BASE
        True
          1. Executeascii_mode = true;
        False
          1. Executebase = (uint8_t)(base + (increase ? 1 : -1));
  2. WHILE loop!keyIsAvailable(&keypad[selected_row][selected_column])

    Repeat body while the loop condition permits; then continue below.

    1. ExecutemoveCursor(0, 1);
Read original function source
static void changeBase(bool increase)
{
    if (ascii_mode) {
        ascii_mode = false;
        base = increase ? CALCULATOR_MIN_BASE : CALCULATOR_MAX_BASE;
    } else if (increase && base == CALCULATOR_MAX_BASE) {
        ascii_mode = true;
    } else if (!increase && base == CALCULATOR_MIN_BASE) {
        ascii_mode = true;
    } else {
        base = (uint8_t)(base + (increase ? 1 : -1));
    }
    while (!keyIsAvailable(&keypad[selected_row][selected_column])) moveCursor(0, 1);
}
pressSelectedKey() · source line 281
static void pressSelectedKey(void)

Calls into this documentation:

  1. Executeconst calculator_key_t *key = &keypad[selected_row][selected_column];
  2. Decision!keyIsAvailable(key)
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionkey->type == KEY_DIGIT
    True
    1. ExecuteappendDigit(digitValue(key->value));
    False
    1. Decisionkey->type == KEY_OPERATOR
      True
      1. ExecuteselectOperator(key->value);
      False
      1. Decisionkey->type == KEY_EQUALS
        True
        1. ExecutecalculateResult();
        False
        1. Decisionkey->type == KEY_ROOT
          True
          1. ExecutecalculateRoot();
          False
          1. Decisionkey->type == KEY_LOGIC
            True
            1. ExecuteselectOperator(logic_operator);
            False
            1. Decisionkey->type == KEY_BASE_DOWN
              True
              1. ExecutechangeBase(false);
              False
              1. Decisionkey->type == KEY_BASE_UP
                True
                1. ExecutechangeBase(true);
                False

                Continue after this decision.

Read original function source
static void pressSelectedKey(void)
{
    const calculator_key_t *key = &keypad[selected_row][selected_column];
    if (!keyIsAvailable(key)) return;
    if (key->type == KEY_DIGIT) appendDigit(digitValue(key->value));
    else if (key->type == KEY_OPERATOR) selectOperator(key->value);
    else if (key->type == KEY_EQUALS) calculateResult();
    else if (key->type == KEY_ROOT) calculateRoot();
    else if (key->type == KEY_LOGIC) selectOperator(logic_operator);
    else if (key->type == KEY_BASE_DOWN) changeBase(false);
    else if (key->type == KEY_BASE_UP) changeBase(true);
}
startCalculator() · source line 294
void startCalculator(uint32_t now_ms)

Calls into this documentation:

  1. Executerunning = true;
  2. Executebase = 10U;
  3. Executeascii_mode = false;
  4. Executelogic_operator = '&';
  5. Executeselected_row = 3U;
  6. Executeselected_column = 0U;
  7. Executelast_cursor_move = now_ms - CURSOR_REPEAT_MS;
  8. ExecuteclearCalculator();
Read original function source
void startCalculator(uint32_t now_ms)
{
    running = true;
    base = 10U;
    ascii_mode = false;
    logic_operator = '&';
    selected_row = 3U;
    selected_column = 0U;
    last_cursor_move = now_ms - CURSOR_REPEAT_MS;
    clearCalculator();
}
stopCalculator() · source line 306
void stopCalculator(void)
  1. Executerunning = false;
Read original function source
void stopCalculator(void)
{
    running = false;
}
calculatorAction() · source line 311
void calculatorAction(bool select, bool backspace, bool clear, bool cycle_logic, uint32_t now_ms)

Calls into this documentation:

  1. Execute(void)now_ms;
  2. Decision!running
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. Decisionclear
    True
      1. ExecuteclearCalculator();
    False
    1. Decisionbackspace
      True
        1. Decisionerror_text != NULL
          True
          1. ExecuteclearCalculator();
          False
          1. Decisionentry_active
            True
            1. Executeentry_value /= inputBase();
            False

            Continue after this decision.

      False
      1. Decisioncycle_logic && keypad[selected_row][selected_column].type == KEY_LOGIC
        True
          1. Executelogic_operator = logic_operator == '&' ? '|' : (logic_operator == '|' ? '^' : '&');
        False
        1. Decisionselect
          True
            1. ExecutepressSelectedKey();
          False

          Continue after this decision.

Read original function source
void calculatorAction(bool select, bool backspace, bool clear, bool cycle_logic, uint32_t now_ms)
{
    (void)now_ms;
    if (!running) return;
    if (clear) {
        clearCalculator();
    } else if (backspace) {
        if (error_text != NULL) clearCalculator();
        else if (entry_active) entry_value /= inputBase();
    } else if (cycle_logic && keypad[selected_row][selected_column].type == KEY_LOGIC) {
        logic_operator = logic_operator == '&' ? '|' : (logic_operator == '|' ? '^' : '&');
    } else if (select) {
        pressSelectedKey();
    }
}
updateCalculator() · source line 327
void updateCalculator(int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)

Calls into this documentation:

  1. Decision!running || !joystick_valid || now_ms - last_cursor_move < CURSOR_REPEAT_MS
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionx_offset >= JOYSTICK_ENTER
    True
    1. ExecutemoveCursor(0, 1);
    False
    1. Decisionx_offset <= -JOYSTICK_ENTER
      True
      1. ExecutemoveCursor(0, -1);
      False
      1. Decisiony_offset >= JOYSTICK_ENTER
        True
        1. ExecutemoveCursor(-1, 0);
        False
        1. Decisiony_offset <= -JOYSTICK_ENTER
          True
          1. ExecutemoveCursor(1, 0);
          False
          1. Exit this functionreturn;
  3. Executelast_cursor_move = now_ms;
Read original function source
void updateCalculator(int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
{
    if (!running || !joystick_valid || now_ms - last_cursor_move < CURSOR_REPEAT_MS) return;
    if (x_offset >= JOYSTICK_ENTER) moveCursor(0, 1);
    else if (x_offset <= -JOYSTICK_ENTER) moveCursor(0, -1);
    else if (y_offset >= JOYSTICK_ENTER) moveCursor(-1, 0);
    else if (y_offset <= -JOYSTICK_ENTER) moveCursor(1, 0);
    else return;
    last_cursor_move = now_ms;
}
asciiGlyphSupported() · source line 338
static bool asciiGlyphSupported(uint8_t character)
  1. Exit this functionreturn (character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z') || character == ':' || character == '/' || character == '-' || character == '+' || character == '.' || character == '\'' || character == '?' || character == '!' || character == ' ';
Read original function source
static bool asciiGlyphSupported(uint8_t character)
{
    return (character >= '0' && character <= '9') ||
           (character >= 'A' && character <= 'Z') ||
           (character >= 'a' && character <= 'z') || character == ':' ||
           character == '/' || character == '-' || character == '+' ||
           character == '.' || character == '\'' || character == '?' ||
           character == '!' || character == ' ';
}
drawAsciiValue() · source line 348
static void drawAsciiValue(uint16_t *frame)

Calls into this documentation:

  1. Executeuint8_t bytes[8];
  2. Executeuint64_t value = displayedValue();
  3. Executesize_t count = 0U;
  4. DO / WHILE loopvalue != 0U && count < sizeof(bytes)

    Run body, then test condition; repeat while true.

      1. Executebytes[count++] = (uint8_t)value;
      2. Executevalue >>= 8U;
  5. Executeconst int character_width = UI_FONT_ADVANCE * 2;
  6. Executeint x = (LCD_WIDTH - (int)count * character_width + 2) / 2;
  7. FOR loopsize_t index = count; index > 0U; --index

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst uint8_t character = bytes[index - 1U];
      2. Decisioncharacter < 32U || character > 126U
        True
          1. Executefill_rect(frame, x, 17, 12, 16, COLOR_WHITE);
        False
          1. Executechar text[2] = {(char)(asciiGlyphSupported(character) ? character : '?'), '\0'};
          2. Executedraw_text(frame, x, 17, text, 2, COLOR_WHITE);
      3. Executex += character_width;
Read original function source
static void drawAsciiValue(uint16_t *frame)
{
    uint8_t bytes[8];
    uint64_t value = displayedValue();
    size_t count = 0U;
    do {
        bytes[count++] = (uint8_t)value;
        value >>= 8U;
    } while (value != 0U && count < sizeof(bytes));

    const int character_width = UI_FONT_ADVANCE * 2;
    int x = (LCD_WIDTH - (int)count * character_width + 2) / 2;
    for (size_t index = count; index > 0U; --index) {
        const uint8_t character = bytes[index - 1U];
        if (character < 32U || character > 126U) {
            fill_rect(frame, x, 17, 12, 16, COLOR_WHITE);
        } else {
            char text[2] = {(char)(asciiGlyphSupported(character) ? character : '?'), '\0'};
            draw_text(frame, x, 17, text, 2, COLOR_WHITE);
        }
        x += character_width;
    }
}
displayCapacity() · source line 372
static size_t displayCapacity(uint8_t radix)
  1. Executestatic const uint8_t capacities[] = { 0U, 0U, 64U, 41U, 32U, 28U, 25U, 23U, 22U, 21U, 20U, 19U, 18U, 18U, 17U, 17U, 16U, };
  2. Exit this functionreturn capacities[radix];
Read original function source
static size_t displayCapacity(uint8_t radix)
{
    static const uint8_t capacities[] = {
        0U, 0U, 64U, 41U, 32U, 28U, 25U, 23U, 22U, 21U, 20U, 19U, 18U,
        18U, 17U, 17U, 16U,
    };
    return capacities[radix];
}
drawValue() · source line 381
static void drawValue(uint16_t *frame)

Calls into this documentation:

  1. Executechar value[65];
  2. ExecuteformatValue(displayedValue(), value, sizeof(value));
  3. Decisionascii_mode
    True
      1. ExecutedrawAsciiValue(frame);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executeconst size_t length = strlen(value);
  5. Executeconst size_t capacity = displayCapacity(inputBase());
  6. Executeconst size_t digits_per_line = capacity > 42U ? 32U : capacity;
  7. Executeconst int scale = capacity <= 20U ? 2 : 1;
  8. Executeconst int advance = UI_FONT_ADVANCE * scale;
  9. Executeconst size_t ghost_count = capacity - length;
  10. FOR loopsize_t position = 0U; position < capacity; ++position

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst size_t line = position / digits_per_line;
      2. Executeconst size_t line_start = line * digits_per_line;
      3. Executeconst size_t line_length = capacity - line_start < digits_per_line ? capacity - line_start : digits_per_line;
      4. Executeconst int x = LCD_WIDTH - (int)line_length * advance + scale + (int)(position - line_start) * advance;
      5. Executeconst int y = capacity > 42U ? 14 + (int)line * 11 : (scale == 2 ? 17 : 22);
      6. Executeconst bool ghost = position < ghost_count;
      7. Executechar digit[2] = {ghost ? '0' : value[position - ghost_count], '\0'};
      8. Executedraw_text(frame, x, y, digit, scale, ghost ? COLOR_DIMGREY : COLOR_WHITE);
Read original function source
static void drawValue(uint16_t *frame)
{
    char value[65];
    formatValue(displayedValue(), value, sizeof(value));
    if (ascii_mode) {
        drawAsciiValue(frame);
        return;
    }

    const size_t length = strlen(value);
    const size_t capacity = displayCapacity(inputBase());
    const size_t digits_per_line = capacity > 42U ? 32U : capacity;
    const int scale = capacity <= 20U ? 2 : 1;
    const int advance = UI_FONT_ADVANCE * scale;
    const size_t ghost_count = capacity - length;

    for (size_t position = 0U; position < capacity; ++position) {
        const size_t line = position / digits_per_line;
        const size_t line_start = line * digits_per_line;
        const size_t line_length = capacity - line_start < digits_per_line ?
            capacity - line_start : digits_per_line;
        const int x = LCD_WIDTH - (int)line_length * advance + scale +
            (int)(position - line_start) * advance;
        const int y = capacity > 42U ? 14 + (int)line * 11 : (scale == 2 ? 17 : 22);
        const bool ghost = position < ghost_count;
        char digit[2] = {ghost ? '0' : value[position - ghost_count], '\0'};
        draw_text(frame, x, y, digit, scale, ghost ? COLOR_DIMGREY : COLOR_WHITE);
    }
}
keyX() · source line 411
static int keyX(uint8_t row, uint8_t column)
  1. Decisionrow == 0U
    True
    1. Exit this functionreturn 56 + (int)column * 34;
    False

    Continue after this decision.

  2. Decisioncolumn < 3U
    True
    1. Exit this functionreturn 57 + (int)column * 45;
    False

    Continue after this decision.

  3. Decisioncolumn == 3U
    True
    1. Exit this functionreturn 195;
    False

    Continue after this decision.

  4. Decisioncolumn == 4U
    True
    1. Exit this functionreturn 250;
    False

    Continue after this decision.

  5. Exit this functionreturn 6;
Read original function source
static int keyX(uint8_t row, uint8_t column)
{
    if (row == 0U) return 56 + (int)column * 34;
    if (column < 3U) return 57 + (int)column * 45;
    if (column == 3U) return 195;
    if (column == 4U) return 250;
    return 6;
}
keyY() · source line 420
static int keyY(uint8_t row)
  1. Exit this functionreturn row == 0U ? 35 : 58 + (int)(row - 1U) * 18;
Read original function source
static int keyY(uint8_t row)
{
    return row == 0U ? 35 : 58 + (int)(row - 1U) * 18;
}
keyWidth() · source line 425
static int keyWidth(uint8_t row, uint8_t column)
  1. Decisionrow == 0U
    True
    1. Exit this functionreturn 29;
    False

    Continue after this decision.

  2. Decisioncolumn < 3U
    True
    1. Exit this functionreturn 40;
    False

    Continue after this decision.

  3. Decisioncolumn == 3U
    True
    1. Exit this functionreturn 48;
    False

    Continue after this decision.

  4. Decisioncolumn == 4U
    True
    1. Exit this functionreturn 61;
    False

    Continue after this decision.

  5. Exit this functionreturn 43;
Read original function source
static int keyWidth(uint8_t row, uint8_t column)
{
    if (row == 0U) return 29;
    if (column < 3U) return 40;
    if (column == 3U) return 48;
    if (column == 4U) return 61;
    return 43;
}
drawBasicOperator() · source line 434
static void drawBasicOperator(uint16_t *frame, int x, int y, int width,
                              char operation, uint16_t colour)

Calls into this documentation:

  1. Executeconst int centre_x = x + width / 2;
  2. Decisionoperation == '+'
    True
      1. Executefill_rect(frame, centre_x - 1, y + 2, 3, 12, colour);
      2. Executefill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
    False
    1. Decisionoperation == '-'
      True
        1. Executefill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
      False
      1. Decisionoperation == '*'
        True
          1. FOR loopint thickness = -1; thickness <= 1; ++thickness

            Repeat body while the loop condition permits; then continue below.

              1. Executedraw_line(frame, centre_x - 6, y + 2 + thickness, centre_x + 6, y + 14 + thickness, colour);
              2. Executedraw_line(frame, centre_x - 6, y + 14 + thickness, centre_x + 6, y + 2 + thickness, colour);
        False
        1. Decisionoperation == '/'
          True
            1. Executefill_rect(frame, centre_x - 1, y + 1, 3, 3, colour);
            2. Executefill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
            3. Executefill_rect(frame, centre_x - 1, y + 12, 3, 3, colour);
          False
            1. Executefill_rect(frame, centre_x - 8, y + 4, 17, 3, colour);
            2. Executefill_rect(frame, centre_x - 8, y + 10, 17, 3, colour);
Read original function source
static void drawBasicOperator(uint16_t *frame, int x, int y, int width,
                              char operation, uint16_t colour)
{
    const int centre_x = x + width / 2;
    if (operation == '+') {
        fill_rect(frame, centre_x - 1, y + 2, 3, 12, colour);
        fill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
    } else if (operation == '-') {
        fill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
    } else if (operation == '*') {
        for (int thickness = -1; thickness <= 1; ++thickness) {
            draw_line(frame, centre_x - 6, y + 2 + thickness, centre_x + 6, y + 14 + thickness,
                      colour);
            draw_line(frame, centre_x - 6, y + 14 + thickness, centre_x + 6, y + 2 + thickness,
                      colour);
        }
    } else if (operation == '/') {
        fill_rect(frame, centre_x - 1, y + 1, 3, 3, colour);
        fill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
        fill_rect(frame, centre_x - 1, y + 12, 3, 3, colour);
    } else {
        fill_rect(frame, centre_x - 8, y + 4, 17, 3, colour);
        fill_rect(frame, centre_x - 8, y + 10, 17, 3, colour);
    }
}
drawKey() · source line 460
static void drawKey(uint16_t *frame, uint8_t row, uint8_t column)

Calls into this documentation:

  1. Executeconst calculator_key_t *key = &keypad[row][column];
  2. Decisionkey->type == KEY_NONE
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  3. Executeconst int x = keyX(row, column);
  4. Executeconst int y = keyY(row);
  5. Executeconst int width = keyWidth(row, column);
  6. Executeconst bool selected = row == selected_row && column == selected_column;
  7. Executeconst bool digit = key->type == KEY_DIGIT;
  8. Executeconst uint16_t colour = keyIsAvailable(key) ? COLOR_LIGHTGREY : COLOR_DARKGREY;
  9. Decisionselected
    True
    1. Executedraw_rect(frame, x, y, width, 16, COLOR_ORANGE);
    False

    Continue after this decision.

  10. Decisionkey->type == KEY_LOGIC
    True
      1. Executedraw_text_centred(frame, x + width / 2, y, "LOGIC", 1, selected ? COLOR_ORANGE : colour);
      2. Executedraw_text_centred(frame, x + width / 2, y + 8, logicLabel(), 1, selected ? COLOR_ORANGE : colour);
      3. Exit this functionreturn;
    False

    Continue after this decision.

  11. Decision(key->type == KEY_OPERATOR && (key->value == '+' || key->value == '-' || key->value == '*' || key->value == '/')) || key->type == KEY_EQUALS
    True
      1. ExecutedrawBasicOperator(frame, x, y, width, key->type == KEY_EQUALS ? '=' : key->value, selected ? COLOR_ORANGE : colour);
      2. Exit this functionreturn;
    False

    Continue after this decision.

  12. Executechar digit_text[2] = {key->value, '\0'};
  13. Executeconst char *label = digit ? digit_text : (key->type == KEY_OPERATOR ? operatorLabel(key->value) : (key->type == KEY_EQUALS ? "EQ" : (key->type == KEY_ROOT ? "ROOT" : (key->type == KEY_BASE_DOWN ? "BASE-" : "BASE+"))));
  14. Executedraw_text_centred(frame, x + width / 2, y + (digit ? (row == 0U ? 0 : 1) : 4), label, digit ? 2 : 1, selected ? COLOR_ORANGE : colour);
Read original function source
static void drawKey(uint16_t *frame, uint8_t row, uint8_t column)
{
    const calculator_key_t *key = &keypad[row][column];
    if (key->type == KEY_NONE) return;
    const int x = keyX(row, column);
    const int y = keyY(row);
    const int width = keyWidth(row, column);
    const bool selected = row == selected_row && column == selected_column;
    const bool digit = key->type == KEY_DIGIT;
    const uint16_t colour = keyIsAvailable(key) ? COLOR_LIGHTGREY : COLOR_DARKGREY;
    if (selected) draw_rect(frame, x, y, width, 16, COLOR_ORANGE);

    if (key->type == KEY_LOGIC) {
        draw_text_centred(frame, x + width / 2, y, "LOGIC", 1, selected ? COLOR_ORANGE : colour);
        draw_text_centred(frame, x + width / 2, y + 8, logicLabel(), 1,
                          selected ? COLOR_ORANGE : colour);
        return;
    }

    if ((key->type == KEY_OPERATOR && (key->value == '+' || key->value == '-' ||
                                       key->value == '*' || key->value == '/')) ||
        key->type == KEY_EQUALS) {
        drawBasicOperator(frame, x, y, width, key->type == KEY_EQUALS ? '=' : key->value,
                          selected ? COLOR_ORANGE : colour);
        return;
    }

    char digit_text[2] = {key->value, '\0'};
    const char *label = digit ? digit_text :
        (key->type == KEY_OPERATOR ? operatorLabel(key->value) :
         (key->type == KEY_EQUALS ? "EQ" :
          (key->type == KEY_ROOT ? "ROOT" :
           (key->type == KEY_BASE_DOWN ? "BASE-" : "BASE+"))));
    draw_text_centred(frame, x + width / 2, y + (digit ? (row == 0U ? 0 : 1) : 4), label,
                      digit ? 2 : 1, selected ? COLOR_ORANGE : colour);
}
drawCalculator() · source line 497
void drawCalculator(uint16_t *frame)

Calls into this documentation:

  1. Decision!running || frame == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeclear(frame, COLOR_BLACK);
  3. ExecutedrawValue(frame);
  4. FOR loopuint8_t row = 0U; row < CALCULATOR_ROWS; ++row

    Repeat body while the loop condition permits; then continue below.

      1. FOR loopuint8_t column = 0U; column < CALCULATOR_COLUMNS; ++column

        Repeat body while the loop condition permits; then continue below.

          1. ExecutedrawKey(frame, row, column);
  5. Executedraw_text_centred(frame, 27, 78, "BASE", 1, COLOR_CYAN);
  6. Decisionascii_mode
    True
      1. Executedraw_text_centred(frame, 27, 87, "ASCII", 1, COLOR_WHITE);
    False
      1. Executechar base_text[4];
      2. Executesnprintf(base_text, sizeof(base_text), "%u", (unsigned)base);
      3. Executedraw_text_centred(frame, 27, 87, base_text, 1, COLOR_WHITE);
  7. Decisionerror_text != NULL
    True
    1. Executedraw_text_centred(frame, LCD_WIDTH / 2, 148, error_text, 1, COLOR_RED);
    False
    1. Decisionpending_operator != '\0'
      True
        1. Executechar operation[20];
        2. Executesnprintf(operation, sizeof(operation), "OPERATOR %s", operatorLabel(pending_operator));
        3. Executedraw_text_centred(frame, 85, 148, operation, 1, COLOR_YELLOW);
      False

      Continue after this decision.

  8. Executedraw_text_centred(frame, LCD_WIDTH / 2, 160, "A/K KEY B LOGIC C BACK D CLEAR", 1, COLOR_LIGHTGREY);
Read original function source
void drawCalculator(uint16_t *frame)
{
    if (!running || frame == NULL) return;
    clear(frame, COLOR_BLACK);

    drawValue(frame);
    for (uint8_t row = 0U; row < CALCULATOR_ROWS; ++row) {
        for (uint8_t column = 0U; column < CALCULATOR_COLUMNS; ++column) {
            drawKey(frame, row, column);
        }
    }

    draw_text_centred(frame, 27, 78, "BASE", 1, COLOR_CYAN);
    if (ascii_mode) {
        draw_text_centred(frame, 27, 87, "ASCII", 1, COLOR_WHITE);
    } else {
        char base_text[4];
        snprintf(base_text, sizeof(base_text), "%u", (unsigned)base);
        draw_text_centred(frame, 27, 87, base_text, 1, COLOR_WHITE);
    }
    if (error_text != NULL) draw_text_centred(frame, LCD_WIDTH / 2, 148, error_text, 1, COLOR_RED);
    else if (pending_operator != '\0') {
        char operation[20];
        snprintf(operation, sizeof(operation), "OPERATOR %s", operatorLabel(pending_operator));
        draw_text_centred(frame, 85, 148, operation, 1, COLOR_YELLOW);
    }
    draw_text_centred(frame, LCD_WIDTH / 2, 160, "A/K KEY  B LOGIC  C BACK  D CLEAR", 1,
                      COLOR_LIGHTGREY);
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "calculator.h"

#include "game_graphics.h"
#include "PET2001_font.h"

#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>

#define CALCULATOR_ROWS 6U
#define CALCULATOR_COLUMNS 6U
#define CALCULATOR_MIN_BASE 2U
#define CALCULATOR_MAX_BASE 16U
#define JOYSTICK_ENTER 600
#define CURSOR_REPEAT_MS 160U

typedef enum {
    KEY_NONE,
    KEY_DIGIT,
    KEY_OPERATOR,
    KEY_EQUALS,
    KEY_ROOT,
    KEY_LOGIC,
    KEY_BASE_DOWN,
    KEY_BASE_UP,
} calculator_key_type_t;

typedef struct {
    calculator_key_type_t type;
    char value;
} calculator_key_t;

static const calculator_key_t keypad[CALCULATOR_ROWS][CALCULATOR_COLUMNS] = {
    {{KEY_DIGIT, 'A'}, {KEY_DIGIT, 'B'}, {KEY_DIGIT, 'C'},
     {KEY_DIGIT, 'D'}, {KEY_DIGIT, 'E'}, {KEY_DIGIT, 'F'}},
    {{KEY_DIGIT, '7'}, {KEY_DIGIT, '8'}, {KEY_DIGIT, '9'},
     {KEY_OPERATOR, '+'}, {KEY_OPERATOR, '%'}, {KEY_BASE_DOWN, 0}},
    {{KEY_DIGIT, '4'}, {KEY_DIGIT, '5'}, {KEY_DIGIT, '6'},
     {KEY_OPERATOR, '-'}, {KEY_OPERATOR, 'P'}, {KEY_NONE, 0}},
    {{KEY_DIGIT, '1'}, {KEY_DIGIT, '2'}, {KEY_DIGIT, '3'},
     {KEY_OPERATOR, '*'}, {KEY_ROOT, 0}, {KEY_BASE_UP, 0}},
    {{KEY_NONE, 0}, {KEY_DIGIT, '0'}, {KEY_NONE, 0},
     {KEY_OPERATOR, '/'}, {KEY_LOGIC, 0}, {KEY_NONE, 0}},
    {{KEY_NONE, 0}, {KEY_NONE, 0}, {KEY_NONE, 0},
     {KEY_EQUALS, 0}, {KEY_NONE, 0}, {KEY_NONE, 0}},
};

static bool running;
static bool entry_active;
static bool has_accumulator;
static uint64_t entry_value;
static uint64_t accumulator;
static char pending_operator;
static char logic_operator;
static const char *error_text;
static uint8_t base;
static bool ascii_mode;
static uint8_t selected_row;
static uint8_t selected_column;
static uint32_t last_cursor_move;

static bool isDigitKey(char key)
{
    return (key >= '0' && key <= '9') || (key >= 'A' && key <= 'F');
}

static uint8_t digitValue(char key)
{
    return key >= 'A' ? (uint8_t)(key - 'A' + 10) : (uint8_t)(key - '0');
}

static uint8_t inputBase(void)
{
    return ascii_mode ? 16U : base;
}

static bool keyIsAvailable(const calculator_key_t *key)
{
    if (key->type == KEY_NONE) return false;
    return key->type != KEY_DIGIT || digitValue(key->value) < inputBase();
}

static const char *logicLabel(void)
{
    if (logic_operator == '&') return "AND";
    return logic_operator == '|' ? "OR" : "XOR";
}

static const char *operatorLabel(char operation)
{
    switch (operation) {
    case '+': return "ADD";
    case '-': return "SUB";
    case '*': return "MUL";
    case '/': return "DIV";
    case '%': return "MOD";
    case 'P': return "POWER";
    case '&': return "AND";
    case '|': return "OR";
    case '^': return "XOR";
    default: return "";
    }
}

static void clearCalculator(void)
{
    entry_active = false;
    has_accumulator = false;
    entry_value = 0U;
    accumulator = 0U;
    pending_operator = '\0';
    error_text = NULL;
}

static uint64_t displayedValue(void)
{
    return entry_active ? entry_value : (has_accumulator ? accumulator : 0U);
}

static void formatValue(uint64_t value, char *text, size_t text_size)
{
    static const char digits[] = "0123456789ABCDEF";
    char reversed[65];
    size_t length = 0U;

    do {
        reversed[length++] = digits[value % inputBase()];
        value /= inputBase();
    } while (value != 0U && length < sizeof(reversed));

    if (text_size <= length) return;
    for (size_t index = 0U; index < length; ++index) {
        text[index] = reversed[length - index - 1U];
    }
    text[length] = '\0';
}

static bool multiplyChecked(uint64_t left, uint64_t right, uint64_t *result)
{
    if (right != 0U && left > UINT64_MAX / right) {
        error_text = "64 BIT OVERFLOW";
        return false;
    }
    *result = left * right;
    return true;
}

static bool powerChecked(uint64_t value, uint64_t exponent, uint64_t *result)
{
    uint64_t power = 1U;
    uint64_t factor = value;
    while (exponent != 0U) {
        if ((exponent & 1U) != 0U && !multiplyChecked(power, factor, &power)) return false;
        exponent >>= 1U;
        if (exponent != 0U && !multiplyChecked(factor, factor, &factor)) return false;
    }
    *result = power;
    return true;
}

static uint64_t integerSquareRoot(uint64_t value)
{
    uint64_t result = 0U;
    uint64_t bit = UINT64_C(1) << 62U;
    while (bit > value) bit >>= 2U;
    while (bit != 0U) {
        if (value >= result + bit) {
            value -= result + bit;
            result = (result >> 1U) + bit;
        } else {
            result >>= 1U;
        }
        bit >>= 2U;
    }
    return result;
}

static bool applyOperator(char operation, uint64_t left, uint64_t right, uint64_t *result)
{
    switch (operation) {
    case '+':
        if (left > UINT64_MAX - right) error_text = "64 BIT OVERFLOW";
        else *result = left + right;
        break;
    case '-':
        if (left < right) error_text = "NEGATIVE RESULT";
        else *result = left - right;
        break;
    case '*': return multiplyChecked(left, right, result);
    case '/':
        if (right == 0U) error_text = "DIVIDE BY ZERO";
        else *result = left / right;
        break;
    case '%':
        if (right == 0U) error_text = "DIVIDE BY ZERO";
        else *result = left % right;
        break;
    case 'P': return powerChecked(left, right, result);
    case '&': *result = left & right; break;
    case '|': *result = left | right; break;
    case '^': *result = left ^ right; break;
    default: return false;
    }
    return error_text == NULL;
}

static void appendDigit(uint8_t digit)
{
    if (error_text != NULL) clearCalculator();
    if (!entry_active) {
        entry_value = 0U;
        entry_active = true;
    }
    if (entry_value > (UINT64_MAX - digit) / inputBase()) {
        error_text = "64 BIT OVERFLOW";
        return;
    }
    entry_value = entry_value * inputBase() + digit;
}

static void selectOperator(char operation)
{
    if (error_text != NULL) return;
    if (!has_accumulator) {
        accumulator = entry_active ? entry_value : 0U;
        has_accumulator = true;
    } else if (pending_operator != '\0' && entry_active) {
        uint64_t result = 0U;
        if (!applyOperator(pending_operator, accumulator, entry_value, &result)) return;
        accumulator = result;
    }
    pending_operator = operation;
    entry_active = false;
}

static void calculateResult(void)
{
    if (error_text != NULL || pending_operator == '\0' || !entry_active) return;
    uint64_t result = 0U;
    if (!applyOperator(pending_operator, accumulator, entry_value, &result)) return;
    entry_value = result;
    accumulator = result;
    entry_active = true;
    has_accumulator = false;
    pending_operator = '\0';
}

static void calculateRoot(void)
{
    if (error_text != NULL) return;
    entry_value = integerSquareRoot(displayedValue());
    entry_active = true;
}

static void moveCursor(int row_delta, int column_delta)
{
    for (unsigned tries = 0U; tries < CALCULATOR_ROWS * CALCULATOR_COLUMNS; ++tries) {
        selected_row = (uint8_t)((selected_row + row_delta + CALCULATOR_ROWS) %
                                 CALCULATOR_ROWS);
        selected_column = (uint8_t)((selected_column + column_delta +
                                     CALCULATOR_COLUMNS) % CALCULATOR_COLUMNS);
        if (keyIsAvailable(&keypad[selected_row][selected_column])) return;
    }
}

static void changeBase(bool increase)
{
    if (ascii_mode) {
        ascii_mode = false;
        base = increase ? CALCULATOR_MIN_BASE : CALCULATOR_MAX_BASE;
    } else if (increase && base == CALCULATOR_MAX_BASE) {
        ascii_mode = true;
    } else if (!increase && base == CALCULATOR_MIN_BASE) {
        ascii_mode = true;
    } else {
        base = (uint8_t)(base + (increase ? 1 : -1));
    }
    while (!keyIsAvailable(&keypad[selected_row][selected_column])) moveCursor(0, 1);
}

static void pressSelectedKey(void)
{
    const calculator_key_t *key = &keypad[selected_row][selected_column];
    if (!keyIsAvailable(key)) return;
    if (key->type == KEY_DIGIT) appendDigit(digitValue(key->value));
    else if (key->type == KEY_OPERATOR) selectOperator(key->value);
    else if (key->type == KEY_EQUALS) calculateResult();
    else if (key->type == KEY_ROOT) calculateRoot();
    else if (key->type == KEY_LOGIC) selectOperator(logic_operator);
    else if (key->type == KEY_BASE_DOWN) changeBase(false);
    else if (key->type == KEY_BASE_UP) changeBase(true);
}

void startCalculator(uint32_t now_ms)
{
    running = true;
    base = 10U;
    ascii_mode = false;
    logic_operator = '&';
    selected_row = 3U;
    selected_column = 0U;
    last_cursor_move = now_ms - CURSOR_REPEAT_MS;
    clearCalculator();
}

void stopCalculator(void)
{
    running = false;
}

void calculatorAction(bool select, bool backspace, bool clear, bool cycle_logic, uint32_t now_ms)
{
    (void)now_ms;
    if (!running) return;
    if (clear) {
        clearCalculator();
    } else if (backspace) {
        if (error_text != NULL) clearCalculator();
        else if (entry_active) entry_value /= inputBase();
    } else if (cycle_logic && keypad[selected_row][selected_column].type == KEY_LOGIC) {
        logic_operator = logic_operator == '&' ? '|' : (logic_operator == '|' ? '^' : '&');
    } else if (select) {
        pressSelectedKey();
    }
}

void updateCalculator(int x_offset, int y_offset, bool joystick_valid, uint32_t now_ms)
{
    if (!running || !joystick_valid || now_ms - last_cursor_move < CURSOR_REPEAT_MS) return;
    if (x_offset >= JOYSTICK_ENTER) moveCursor(0, 1);
    else if (x_offset <= -JOYSTICK_ENTER) moveCursor(0, -1);
    else if (y_offset >= JOYSTICK_ENTER) moveCursor(-1, 0);
    else if (y_offset <= -JOYSTICK_ENTER) moveCursor(1, 0);
    else return;
    last_cursor_move = now_ms;
}

static bool asciiGlyphSupported(uint8_t character)
{
    return (character >= '0' && character <= '9') ||
           (character >= 'A' && character <= 'Z') ||
           (character >= 'a' && character <= 'z') || character == ':' ||
           character == '/' || character == '-' || character == '+' ||
           character == '.' || character == '\'' || character == '?' ||
           character == '!' || character == ' ';
}

static void drawAsciiValue(uint16_t *frame)
{
    uint8_t bytes[8];
    uint64_t value = displayedValue();
    size_t count = 0U;
    do {
        bytes[count++] = (uint8_t)value;
        value >>= 8U;
    } while (value != 0U && count < sizeof(bytes));

    const int character_width = PET2001_FONT_GLYPH_WIDTH * 2;
    int x = (LCD_WIDTH - (int)count * character_width) / 2;
    for (size_t index = count; index > 0U; --index) {
        const uint8_t character = bytes[index - 1U];
        if (character < 32U || character > 126U) {
            fill_rect(frame, x, 17, character_width, 16, COLOR_WHITE);
        } else {
            char text[2] = {(char)(asciiGlyphSupported(character) ? character : '?'), '\0'};
            drawPet2001TextTransparent(frame, x, 17, text, COLOR_WHITE, 2);
        }
        x += character_width;
    }
}

static size_t displayCapacity(uint8_t radix)
{
    static const uint8_t capacities[] = {
        0U, 0U, 64U, 41U, 32U, 28U, 25U, 23U, 22U, 21U, 20U, 19U, 18U,
        18U, 17U, 17U, 16U,
    };
    return capacities[radix];
}

static void drawValue(uint16_t *frame)
{
    char value[65];
    formatValue(displayedValue(), value, sizeof(value));
    if (ascii_mode) {
        drawAsciiValue(frame);
        return;
    }

    const size_t length = strlen(value);
    const size_t capacity = displayCapacity(inputBase());
    const bool wraps_to_two_lines = capacity > 40U;
    const size_t digits_per_line = wraps_to_two_lines ? 32U : capacity;
    const int scale = capacity <= 20U ? 2 : 1;
    const int advance = PET2001_FONT_GLYPH_WIDTH * scale;
    const size_t ghost_count = capacity - length;

    for (size_t position = 0U; position < capacity; ++position) {
        const size_t line = position / digits_per_line;
        const size_t line_start = line * digits_per_line;
        const size_t line_length = capacity - line_start < digits_per_line ?
            capacity - line_start : digits_per_line;
        const int x = LCD_WIDTH - (int)line_length * advance +
            (int)(position - line_start) * advance;
        const int y = wraps_to_two_lines ? 14 + (int)line * 11 : (scale == 2 ? 17 : 22);
        const bool ghost = position < ghost_count;
        char digit[2] = {ghost ? '0' : value[position - ghost_count], '\0'};
        drawPet2001TextTransparent(frame, x, y, digit, ghost ? COLOR_DIMGREY : COLOR_WHITE, scale);
    }
}

static int keyX(uint8_t row, uint8_t column)
{
    if (row == 0U) return 56 + (int)column * 34;
    if (column < 3U) return 57 + (int)column * 45;
    if (column == 3U) return 195;
    if (column == 4U) return 250;
    return 6;
}

static int keyY(uint8_t row)
{
    return row == 0U ? 35 : 58 + (int)(row - 1U) * 18;
}

static int keyWidth(uint8_t row, uint8_t column)
{
    if (row == 0U) return 29;
    if (column < 3U) return 40;
    if (column == 3U) return 48;
    if (column == 4U) return 61;
    return 43;
}

static void drawBasicOperator(uint16_t *frame, int x, int y, int width,
                              char operation, uint16_t colour)
{
    const int centre_x = x + width / 2;
    if (operation == '+') {
        fill_rect(frame, centre_x - 1, y + 2, 3, 12, colour);
        fill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
    } else if (operation == '-') {
        fill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
    } else if (operation == '*') {
        for (int thickness = -1; thickness <= 1; ++thickness) {
            draw_line(frame, centre_x - 6, y + 2 + thickness, centre_x + 6, y + 14 + thickness,
                      colour);
            draw_line(frame, centre_x - 6, y + 14 + thickness, centre_x + 6, y + 2 + thickness,
                      colour);
        }
    } else if (operation == '/') {
        fill_rect(frame, centre_x - 1, y + 1, 3, 3, colour);
        fill_rect(frame, centre_x - 8, y + 7, 17, 3, colour);
        fill_rect(frame, centre_x - 1, y + 12, 3, 3, colour);
    } else {
        fill_rect(frame, centre_x - 8, y + 4, 17, 3, colour);
        fill_rect(frame, centre_x - 8, y + 10, 17, 3, colour);
    }
}

static void drawKey(uint16_t *frame, uint8_t row, uint8_t column)
{
    const calculator_key_t *key = &keypad[row][column];
    if (key->type == KEY_NONE) return;
    const int x = keyX(row, column);
    const int y = keyY(row);
    const int width = keyWidth(row, column);
    const bool selected = row == selected_row && column == selected_column;
    const bool digit = key->type == KEY_DIGIT;
    const uint16_t colour = keyIsAvailable(key) ? COLOR_LIGHTGREY : COLOR_DARKGREY;
    if (selected) draw_rect(frame, x, y, width, 16, COLOR_ORANGE);

    if (key->type == KEY_LOGIC) {
        drawPet2001TextCentredTransparent(frame, x + width / 2, y, "LOGIC", selected ? COLOR_ORANGE : colour, 1);
        drawPet2001TextCentredTransparent(frame, x + width / 2, y + 8, logicLabel(),
                                           selected ? COLOR_ORANGE : colour, 1);
        return;
    }

    if ((key->type == KEY_OPERATOR && (key->value == '+' || key->value == '-' ||
                                       key->value == '*' || key->value == '/')) ||
        key->type == KEY_EQUALS) {
        drawBasicOperator(frame, x, y, width, key->type == KEY_EQUALS ? '=' : key->value,
                          selected ? COLOR_ORANGE : colour);
        return;
    }

    char digit_text[2] = {key->value, '\0'};
    const char *label = digit ? digit_text :
        (key->type == KEY_OPERATOR ? operatorLabel(key->value) :
         (key->type == KEY_EQUALS ? "EQ" :
          (key->type == KEY_ROOT ? "ROOT" :
           (key->type == KEY_BASE_DOWN ? "BASE-" : "BASE+"))));
    drawPet2001TextCentredTransparent(frame, x + width / 2, y + (digit ? (row == 0U ? 0 : 1) : 4),
                                       label, selected ? COLOR_ORANGE : colour, digit ? 2 : 1);
}

void drawCalculator(uint16_t *frame)
{
    if (!running || frame == NULL) return;
    clear(frame, COLOR_BLACK);

    drawValue(frame);
    for (uint8_t row = 0U; row < CALCULATOR_ROWS; ++row) {
        for (uint8_t column = 0U; column < CALCULATOR_COLUMNS; ++column) {
            drawKey(frame, row, column);
        }
    }

    drawPet2001TextCentredTransparent(frame, 27, 78, "BASE", COLOR_CYAN, 1);
    if (ascii_mode) {
        drawPet2001TextCentredTransparent(frame, 27, 87, "ASCII", COLOR_WHITE, 1);
    } else {
        char base_text[4];
        snprintf(base_text, sizeof(base_text), "%u", (unsigned)base);
        drawPet2001TextCentredTransparent(frame, 27, 87, base_text, COLOR_WHITE, 1);
    }
    if (error_text != NULL) drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 148, error_text, COLOR_RED, 1);
    else if (pending_operator != '\0') {
        char operation[20];
        snprintf(operation, sizeof(operation), "OPERATOR %s", operatorLabel(pending_operator));
        drawPet2001TextCentredTransparent(frame, 85, 148, operation, COLOR_YELLOW, 1);
    }
    drawPet2001TextCentredTransparent(frame, LCD_WIDTH / 2, 160,
                                       "A/K KEY  B LOGIC  C BACK  D CLEAR", COLOR_LIGHTGREY, 1);
}