#include "../core_runtime/core_runtime_private.h"
#include "../filesystem/fs_private.h"
#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_int32_bits.h"
#include "coduo_native_x87.h"

#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define PC_SOURCE_DIAGNOSTIC_BUFFER_SIZE 1024
#define PC_SOURCE_ERROR_FATAL 0
#define PC_INDENT_TYPE_NONE 0
#define PC_INDENT_TYPE_IF 1
#define PC_INDENT_TYPE_ELSE 2
#define PC_INDENT_TYPE_ELIF 4
#define PC_INDENT_TYPE_IFDEF 8
#define PC_INDENT_TYPE_IFNDEF 16
#define PC_TOKEN_MAX_CHARS CODUO_PC_TOKEN_STRING_SIZE
#define PC_TOKEN_TYPE_STRING 1
#define PC_TOKEN_TYPE_LITERAL 2
#define PC_TOKEN_TYPE_NUMBER 3
#define PC_TOKEN_TYPE_NAME 4
#define PC_TOKEN_TYPE_PUNCTUATION 5
#define PC_TOKEN_SUBTYPE_NONE 0
#define PC_TOKEN_SUBTYPE_DECIMAL 0x00000008
#define PC_TOKEN_SUBTYPE_HEX 0x00000100
#define PC_TOKEN_SUBTYPE_OCTAL 0x00000200
#define PC_TOKEN_SUBTYPE_BINARY 0x00000400
#define PC_TOKEN_SUBTYPE_FLOAT 0x00000800
#define PC_TOKEN_SUBTYPE_INTEGER 0x00001000
#define PC_TOKEN_SUBTYPE_LONG 0x00002000
#define PC_TOKEN_SUBTYPE_UNSIGNED 0x00004000
#define PC_TOKEN_SUBTYPE_DECIMAL_INTEGER \
    (PC_TOKEN_SUBTYPE_DECIMAL | PC_TOKEN_SUBTYPE_INTEGER)
#define PC_TOKEN_SUBTYPE_DECIMAL_FLOAT_LONG \
    (PC_TOKEN_SUBTYPE_DECIMAL | PC_TOKEN_SUBTYPE_FLOAT | PC_TOKEN_SUBTYPE_LONG)
#define PC_TOKEN_SUBTYPE_DECIMAL_INTEGER_LONG \
    (PC_TOKEN_SUBTYPE_DECIMAL | PC_TOKEN_SUBTYPE_INTEGER | PC_TOKEN_SUBTYPE_LONG)
#define PC_TOKEN_WHITESPACE_NONE NULL
#define PC_PUNCTUATION_SUBTYPE_MINUS 30
#define PC_SCRIPT_FLAG_NO_ERRORS 0x00000001
#define PC_SCRIPT_FLAG_NO_WARNINGS 0x00000002
#define PC_SCRIPT_FLAG_NO_STRING_CONCAT 0x00000004
#define PC_SCRIPT_FLAG_NO_STRING_ESCAPE_CHARS 0x00000008
#define PC_SCRIPT_FLAG_PRIMITIVE 0x00000010
#define PC_DEFINE_MAX_PARMS 0x80
#define PC_DEFINE_HASH_INDEX_BIAS 119
#define PC_DEFINE_HASH_SHIFT_A 10
#define PC_DEFINE_HASH_SHIFT_B 20
#define PC_DEFINE_HASH_BUCKET_MASK 0x3ffU
#define PC_DEFINE_PARAMETER_NOT_FOUND -1
#define PC_DEFINE_FLAG_BUILTIN 0x00000001
#define PC_BUILTIN_LINE 1
#define PC_BUILTIN_FILE 2
#define PC_BUILTIN_DATE 3
#define PC_BUILTIN_TIME 4
#define PC_X87_EXTENDED_EXPONENT_BIAS 16383
#define PC_X87_EXTENDED_SIGN_MASK 0x8000u
#define PC_X87_EXTENDED_SIGNIFICAND_BYTES 8
#define PC_X87_EXTENDED_EXPONENT_OFFSET 8
#define PC_X87_EXTENDED_TBYTE_SIZE 10
#if EMULATE_X87
/* NOT_FROM_ORIGINAL_SOURCE: a .pc number token stores the parsed value as a raw
 * x87 80-bit TBYTE in its
 * 12-byte floatValue slot (see PC_SetSyntheticTokenFloatFromInt32, which builds
 * the same bytes by hand). On a native x87 build the token currency is
 * `long double` and (de)serializes by a plain memcpy of its bytes; off x87 a
 * `long double` is only 64-bit, so we carry the value in a SoftFloat
 * extFloat80_t (whose little-endian {significand:8, signExp:2} layout is the
 * x87 TBYTE byte-for-byte) and copy exactly 10 bytes. This keeps the stored
 * token bytes — and every float/double narrowing read back out of them —
 * bit-identical to the stock egcs binary. */
static inline void coduo_engine_pc_store_token_float80(uint8_t *dst,
                                                        x87f value)
{
    memcpy(dst, &value, PC_X87_EXTENDED_TBYTE_SIZE);
}
/* NOT_FROM_ORIGINAL_SOURCE: native-host inverse of the TBYTE adapter above. */
static inline x87f coduo_engine_pc_load_token_float80(const uint8_t *src)
{
    x87f value;
    memset(&value, 0, sizeof(value));
    memcpy(&value, src, PC_X87_EXTENDED_TBYTE_SIZE);
    return value;
}
#endif
#define PC_CTIME_DATE_MONTH_DAY_OFFSET 4
#define PC_CTIME_DATE_MONTH_DAY_LENGTH 7
#define PC_CTIME_DATE_YEAR_OFFSET 20
#define PC_CTIME_DATE_YEAR_LENGTH 4
#define PC_CTIME_TIME_OFFSET 11
#define PC_CTIME_TIME_LENGTH 8
#define PC_NUMBER_SUFFIX_CHECK_COUNT 2
#define PC_NUMBER_FLOAT_BASE 10.0L
#define PC_SCRIPT_SIGN_POSITIVE 1
#define PC_SCRIPT_SIGN_NEGATIVE -1
#define PC_SCRIPT_HANDLE_INVALID 0
#define PC_SCRIPT_INITIAL_LINE 1
#define PC_SCRIPT_NOT_AVAILABLE 0
#define PC_OPERATOR_PRIORITY_NONE 0
#define PC_OPERATOR_PRIORITY_TERNARY 5
#define PC_OPERATOR_PRIORITY_LOGICAL_OR 6
#define PC_OPERATOR_PRIORITY_LOGICAL_AND 7
#define PC_OPERATOR_PRIORITY_BITWISE_OR 8
#define PC_OPERATOR_PRIORITY_BITWISE_XOR 9
#define PC_OPERATOR_PRIORITY_BITWISE_AND 10
#define PC_OPERATOR_PRIORITY_EQUALITY 11
#define PC_OPERATOR_PRIORITY_RELATIONAL 12
#define PC_OPERATOR_PRIORITY_SHIFT 13
#define PC_OPERATOR_PRIORITY_ADDITIVE 14
#define PC_OPERATOR_PRIORITY_MULTIPLICATIVE 15
#define PC_OPERATOR_PRIORITY_UNARY 16
#define PC_EVAL_MAX_VALUES 0x40
#define PC_EVAL_MAX_OPERATORS 0x40
#define PC_PUNCTUATION_SUBTYPE_AND_AND 5
#define PC_PUNCTUATION_SUBTYPE_OR_OR 6
#define PC_PUNCTUATION_SUBTYPE_GREATER_EQUAL 7
#define PC_PUNCTUATION_SUBTYPE_LESS_EQUAL 8
#define PC_PUNCTUATION_SUBTYPE_EQUAL 9
#define PC_PUNCTUATION_SUBTYPE_NOT_EQUAL 10
#define PC_PUNCTUATION_SUBTYPE_INCREMENT 16
#define PC_PUNCTUATION_SUBTYPE_DECREMENT 17
#define PC_PUNCTUATION_SUBTYPE_SHIFT_RIGHT 21
#define PC_PUNCTUATION_SUBTYPE_SHIFT_LEFT 22
#define PC_PUNCTUATION_SUBTYPE_MULTIPLY 26
#define PC_PUNCTUATION_SUBTYPE_DIVIDE 27
#define PC_PUNCTUATION_SUBTYPE_MODULO 28
#define PC_PUNCTUATION_SUBTYPE_PLUS 29
#define PC_PUNCTUATION_SUBTYPE_AMPERSAND 32
#define PC_PUNCTUATION_SUBTYPE_OR 33
#define PC_PUNCTUATION_SUBTYPE_XOR 34
#define PC_PUNCTUATION_SUBTYPE_COMPLEMENT 35
#define PC_PUNCTUATION_SUBTYPE_NOT 36
#define PC_PUNCTUATION_SUBTYPE_GREATER 37
#define PC_PUNCTUATION_SUBTYPE_LESS 38
#define PC_PUNCTUATION_SUBTYPE_COLON 42
#define PC_PUNCTUATION_SUBTYPE_QUESTION 43
#define PC_PUNCTUATION_SUBTYPE_OPEN_PAREN 44
#define PC_PUNCTUATION_SUBTYPE_CLOSE_PAREN 45

int32_t pc_tokenNodeCount;
define_t *pc_globalDefines;
source_t *pc_sourceFiles[CODUO_PC_SOURCE_HANDLE_COUNT];
coduo_pc_base_folder_t pc_baseFolder;

typedef struct coduo_pc_handle_token_s {
    int32_t type;
    int32_t subtype;
    int32_t intValue;
    float floatValue;
    char string[CODUO_PC_TOKEN_STRING_SIZE];
} coduo_pc_handle_token_t;

static coduo_pc_default_punctuation_table_t pc_defaultPunctuations = {
    {">>=", 1, NULL}, {"<<=", 2, NULL}, {"...", 3, NULL},
    {"##", 4, NULL},  {"&&", 5, NULL},  {"||", 6, NULL},
    {">=", 7, NULL},  {"<=", 8, NULL},  {"==", 9, NULL},
    {"!=", 10, NULL}, {"*=", 11, NULL}, {"/=", 12, NULL},
    {"%=", 13, NULL}, {"+=", 14, NULL}, {"-=", 15, NULL},
    {"++", 16, NULL}, {"--", 17, NULL}, {"&=", 18, NULL},
    {"|=", 19, NULL}, {"^=", 20, NULL}, {">>", 21, NULL},
    {"<<", 22, NULL}, {"->", 23, NULL}, {"::", 24, NULL},
    {".*", 25, NULL}, {"*", 26, NULL},  {"/", 27, NULL},
    {"%", 28, NULL},  {"+", 29, NULL},  {"-", 30, NULL},
    {"=", 31, NULL},  {"&", 32, NULL},  {"|", 33, NULL},
    {"^", 34, NULL},  {"~", 35, NULL},  {"!", 36, NULL},
    {">", 37, NULL},  {"<", 38, NULL},  {".", 39, NULL},
    {",", 40, NULL},  {";", 41, NULL},  {":", 42, NULL},
    {"?", 43, NULL},  {"(", 44, NULL},  {")", 45, NULL},
    {"{", 46, NULL},  {"}", 47, NULL},  {"[", 48, NULL},
    {"]", 49, NULL},  {"\\", 50, NULL}, {"#", 51, NULL},
    {"$", 52, NULL},  {NULL, 0, NULL},
};

typedef struct value_s {
    int32_t intValue;
    double floatValue;
    int32_t parentheses;
    struct value_s *prev;
    struct value_s *next;
} value_t;

typedef struct operator_s {
    int32_t operator;
    int32_t priority;
    int32_t parentheses;
    struct operator_s *prev;
    struct operator_s *next;
} operator_t;

qboolean PC_CheckTokenString(source_t *source, const char *string);

void SetScriptPunctuations(script_t *script,
                  punctuation_t *punctuations);
const char *PS_PunctuationStringForSubtype(script_t *script,
                         int32_t subtype);
void ScriptError(script_t *script, const char *format, ...);
void ScriptWarning(script_t *script, const char *format, ...);
void PS_CreatePunctuationTable(script_t *script,
                  punctuation_t *punctuations);
qboolean PS_ReadWhiteSpace(script_t *script);
qboolean PS_ReadEscapeCharacter(script_t *script, char *out);
qboolean PS_ReadString(script_t *script, token_t *token,
                      int32_t quote);
qboolean PS_ReadName(script_t *script, token_t *token);
void NumberValue(char *string, int32_t subtype,
                 int32_t *intValue,
                 uint8_t floatValue[CODUO_PC_TOKEN_FLOAT_VALUE_SIZE]);
qboolean PS_ReadNumber(script_t *script, token_t *token);
qboolean PS_ReadLiteral(script_t *script, token_t *token);
qboolean PS_ReadPunctuation(script_t *script, token_t *token);
qboolean PS_ReadPrimitive(script_t *script, token_t *token);
qboolean PS_ReadToken(script_t *script, token_t *token);
qboolean PS_ExpectTokenString(script_t *script, const char *string);
qboolean PS_ExpectTokenType(script_t *script,
                      int32_t type,
                      int32_t subtype,
                      token_t *token);
qboolean PS_ReadTokenOrError(script_t *script, token_t *token);
qboolean PS_CheckTokenString(script_t *script, const char *string);
qboolean PS_CheckTokenType(script_t *script,
                      int32_t type,
                      int32_t subtype,
                      token_t *token);
qboolean PS_SkipUntilString(script_t *script, const char *string);
void PS_UnreadLastToken(script_t *script);
void PS_UnreadToken(script_t *script, const token_t *token);
int32_t PS_ReadWhitespaceChar(script_t *script);
void StripDoubleQuotes(char *string);
void StripSingleQuotes(char *string);
long double PS_ReadFloat(script_t *script);
int32_t PS_ReadInteger(script_t *script);
void SetScriptFlags(script_t *script, int32_t flags);
int32_t GetScriptFlags(const script_t *script);
void ResetScript(script_t *script);
qboolean EndOfScript(script_t *script);
int32_t PS_LinesCrossed(const script_t *script);
qboolean PS_FindStringInScript(script_t *script, const char *string);
script_t *LoadScriptFile(const char *filename);
script_t *LoadScriptMemory(const char *ptr, size_t length,
                                    const char *name);
void FreeScript(script_t *script);
void PS_SetBaseFolder(const char *path);

qboolean PC_ExpandDefine(source_t *source,
                         const token_t *token,
                         const define_t *define,
                         token_t **firstToken,
                         token_t **lastToken);
qboolean PC_ExpandDefineIntoSource(source_t *source,
                                   const token_t *token,
                                   const define_t *define);
void PC_ConvertPath(char *path);
qboolean PC_Directive_include(source_t *source);
qboolean PC_ReadLine(source_t *source, token_t *token);
qboolean PC_WhiteSpaceBeforeToken(const token_t *token);
void PC_ClearTokenWhiteSpace(token_t *token);
qboolean PC_Directive_undef(source_t *source);
qboolean PC_Directive_define(source_t *source);
qboolean PC_Evaluate(source_t *source, int32_t *intValue,
                      double *floatValue, qboolean integerEval);
qboolean PC_DollarEvaluate(source_t *source, int32_t *intValue,
                      double *floatValue, qboolean integerEval);
qboolean PC_Directive_if(source_t *source);
qboolean PC_Directive_elif(source_t *source);
qboolean PC_Directive_line(source_t *source);
qboolean PC_Directive_error(source_t *source);
qboolean PC_Directive_pragma(source_t *source);
void UnreadSignToken(source_t *source);
qboolean PC_Directive_eval(source_t *source);
qboolean PC_Directive_evalfloat(source_t *source);
qboolean PC_DollarDirective_evalint(source_t *source);
qboolean PC_DollarDirective_evalfloat(source_t *source);
qboolean PC_ReadDirective(source_t *source);
qboolean PC_ReadDollarDirective(source_t *source);
qboolean PC_ReadToken(source_t *source, token_t *token);
qboolean PC_ExpectTokenString(source_t *source, const char *string);
qboolean PC_ExpectTokenType(source_t *source,
                            int32_t type,
                            int32_t subtype,
                            token_t *token);
qboolean PC_ExpectAnyToken(source_t *source, token_t *token);
qboolean PC_CheckTokenType(source_t *source,
                           int32_t type,
                           int32_t subtype,
                           token_t *token);
qboolean PC_SkipUntilString(source_t *source, const char *string);
void PC_UnreadToken(source_t *source);
void PC_UnreadTokenValue(source_t *source,
                         const token_t *token);
void PC_SetIncludePath(source_t *source, const char *path);
void PC_SetPunctuations(source_t *source,
                  punctuation_t *punctuations);
define_t *PC_DefineFromString(const char *string);
qboolean PC_AddDefineToSource(source_t *source,
                              const char *string);
qboolean PC_AddGlobalDefine(const char *string);
qboolean PC_RemoveGlobalDefine(const char *name);
void PC_RemoveAllGlobalDefines(void);
define_t *PC_CopyDefine(source_t *source,
                                 const define_t *define);
void PC_AddGlobalDefinesToSource(source_t *source);
qboolean PC_Directive_if_def(source_t *source, int32_t type);
qboolean PC_Directive_ifdef(source_t *source);
qboolean PC_Directive_ifndef(source_t *source);
qboolean PC_Directive_else(source_t *source);
qboolean PC_Directive_endif(source_t *source);
int32_t PC_OperatorPriority(int32_t operatorSubtype);
source_t *LoadSourceFile(const char *filename);
source_t *LoadSourceMemory(const char *ptr, size_t length,
                                    const char *name);
void FreeSource(source_t *source);
int32_t PC_LoadSourceHandle(const char *filename);
qboolean PC_FreeSourceHandle(int32_t handle);
qboolean PC_ReadTokenHandle(int32_t handle, coduo_pc_handle_token_t *token);
qboolean PC_SourceFileAndLine(int32_t handle, char *filename,
                              int32_t *line);
void PC_CheckOpenSourceHandles(void);
void PC_SetBaseFolder(const char *path);

void PC_NoOp_08078077(void)
{
}

void PC_NoOp_0807807c(void)
{
}

void PC_SourceError(source_t *source, const char *format, ...)
{
    char message[PC_SOURCE_DIAGNOSTIC_BUFFER_SIZE];
    va_list ap;

    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this 1,024-byte
     * stack array.  See
     * original-bugs/engine-pc-source-diagnostic-stack-overflow.md. */
    va_start(ap, format);
    vsprintf(message, format, ap);
    va_end(ap);

    Com_Printf("^1Error: file %s, line %d: %s\n",
               source->scriptStack->filename, source->scriptStack->line,
               message);
}

void PC_SourceWarning(source_t *source, const char *format, ...)
{
    char message[PC_SOURCE_DIAGNOSTIC_BUFFER_SIZE];
    va_list ap;

    /* ORIGINAL_BINARY_BUG: the warning path has the same unbounded stack
     * formatting as PC_SourceError.  See
     * original-bugs/engine-pc-source-diagnostic-stack-overflow.md. */
    va_start(ap, format);
    vsprintf(message, format, ap);
    va_end(ap);

    Com_Printf("file %s, line %d: %s\n", source->scriptStack->filename,
               source->scriptStack->line, message);
}

void SetScriptPunctuations(script_t *script,
                  punctuation_t *punctuations)
{
    if (script->punctuationtable == NULL) {
        script->punctuationtable = Com_ZoneDebugAlloc(
            sizeof(coduo_pc_punctuation_bucket_table_t));
    }

    memset(script->punctuationtable, 0,
           sizeof(coduo_pc_punctuation_bucket_table_t));

    for (punctuation_t *punctuation = punctuations;
         punctuation->p != NULL; ++punctuation) {
        /* ORIGINAL_BINARY_BUG: stock sign-extends the first punctuation byte
         * before indexing the 256-entry pointer table, so a high-bit byte
         * selects storage before the table.  See
         * original-bugs/coduomp-precompiler-tokenizer-boundary-accesses.md. */
        int8_t bucket = (int8_t)punctuation->p[0];
        punctuation_t *previous = NULL;
        punctuation_t *scan = script->punctuationtable[bucket];

        while (scan != NULL) {
            if (strlen(scan->p) < strlen(punctuation->p)) {
                break;
            }

            previous = scan;
            scan = scan->next;
        }

        punctuation->next = scan;
        if (previous == NULL) {
            script->punctuationtable[bucket] = punctuation;
        } else {
            previous->next = punctuation;
        }
    }
}

const char *PS_PunctuationStringForSubtype(script_t *script,
                         int32_t subtype)
{
    for (punctuation_t *punctuation = script->punctuations;
         punctuation->p != NULL; ++punctuation) {
        if (punctuation->n == subtype) {
            return punctuation->p;
        }
    }

    return "unkown punctuation";
}

void ScriptError(script_t *script, const char *format, ...)
{
    char message[PC_SOURCE_DIAGNOSTIC_BUFFER_SIZE];
    va_list ap;

    if ((script->flags & PC_SCRIPT_FLAG_NO_ERRORS) != 0) {
        return;
    }

    /* ORIGINAL_BINARY_BUG: stock formats without a bound into this 1,024-byte
     * stack array.  See
     * original-bugs/coduomp-precompiler-diagnostic-stack-overflow.md. */
    va_start(ap, format);
    vsprintf(message, format, ap);
    va_end(ap);

    Com_Printf("^1Error: file %s, line %d: %s\n", script->filename,
               script->line, message);
}

void ScriptWarning(script_t *script, const char *format, ...)
{
    char message[PC_SOURCE_DIAGNOSTIC_BUFFER_SIZE];
    va_list ap;

    if ((script->flags & PC_SCRIPT_FLAG_NO_WARNINGS) != 0) {
        return;
    }

    /* ORIGINAL_BINARY_BUG: the warning path repeats ScriptError's unbounded
     * 1,024-byte stack formatting.  See
     * original-bugs/coduomp-precompiler-diagnostic-stack-overflow.md. */
    va_start(ap, format);
    vsprintf(message, format, ap);
    va_end(ap);

    Com_Printf("file %s, line %d: %s\n", script->filename, script->line,
               message);
}

void PS_CreatePunctuationTable(script_t *script,
                  punctuation_t *punctuations)
{
    if (punctuations == NULL) {
        SetScriptPunctuations(script, pc_defaultPunctuations);
        script->punctuations = pc_defaultPunctuations;
        return;
    }

    SetScriptPunctuations(script, punctuations);
    script->punctuations = punctuations;
}

qboolean PS_ReadWhiteSpace(script_t *script)
{
    while (*script->script_p != '\0') {
        while (*script->script_p < '!') {
            if (*script->script_p == '\0') {
                return qfalse;
            }
            if (*script->script_p == '\n') {
                script->line++;
            }
            script->script_p++;
        }

        if (*script->script_p != '/') {
            return qtrue;
        }

        if (script->script_p[1] == '/') {
            script->script_p++;
            do {
                script->script_p++;
                if (*script->script_p == '\0') {
                    return qfalse;
                }
            } while (*script->script_p != '\n');

            script->line++;
            script->script_p++;
            continue;
        }

        if (script->script_p[1] != '*') {
            return qtrue;
        }

        script->script_p++;
        do {
            script->script_p++;
            if (*script->script_p == '\0') {
                return qfalse;
            }
            if (*script->script_p == '\n') {
                script->line++;
            }
        } while (*script->script_p != '*' || script->script_p[1] != '/');

        script->script_p++;
        if (*script->script_p == '\0') {
            return qfalse;
        }
        script->script_p++;
        if (*script->script_p == '\0') {
            return qfalse;
        }
    }

    return qfalse;
}

qboolean PS_ReadEscapeCharacter(script_t *script, char *out)
{
    int32_t value = 0;
    int32_t digits = 0;
    int32_t digitValue;

    script->script_p++;

    switch (*script->script_p) {
    case '"':
        value = '"';
        break;
    case '\'':
        value = '\'';
        break;
    case '?':
        value = '?';
        break;
    case '\\':
        value = '\\';
        break;
    case 'a':
        value = '\a';
        break;
    case 'b':
        value = '\b';
        break;
    case 'f':
        value = '\f';
        break;
    case 'n':
        value = '\n';
        break;
    case 'r':
        value = '\r';
        break;
    case 't':
        value = '\t';
        break;
    case 'v':
        value = '\v';
        break;
    case 'x':
        script->script_p++;
        /* ORIGINAL_BINARY_BUG: stock treats every ASCII letter as a valid
         * hexadecimal digit, rather than stopping after A-F/a-f.  See
         * original-bugs/coduomp-precompiler-number-tokenization.md. */
        while (qtrue) {
            int32_t c = (unsigned char)*script->script_p;
            if (c >= '0' && c <= '9') {
                digitValue = c - '0';
            } else if (c >= 'A' && c <= 'Z') {
                digitValue = c - 'A' + 10;
            } else if (c >= 'a' && c <= 'z') {
                digitValue = c - 'a' + 10;
            } else {
                break;
            }

            value = value * 16 + digitValue;
            digits++;
            script->script_p++;
        }
        script->script_p--;
        break;
    default:
        if (*script->script_p < '0' || *script->script_p > '9') {
            ScriptError(script, "unknown escape char");
        }

        while (*script->script_p >= '0' && *script->script_p <= '9') {
            value = value * 10 + *script->script_p - '0';
            digits++;
            script->script_p++;
        }
        script->script_p--;
        break;
    }

    (void)digits;
    if (value > 0xff) {
        ScriptWarning(script, "too large value in escape character");
        value = 0xff;
    }

    script->script_p++;
    *out = (char)value;
    return qtrue;
}

qboolean PS_ReadString(script_t *script, token_t *token,
                      int32_t quote)
{
    token->type =
        quote == '"' ? PC_TOKEN_TYPE_STRING : PC_TOKEN_TYPE_LITERAL;
    token->string[0] = *script->script_p;
    script->script_p++;

    int32_t length = 1;
    while (qtrue) {
        if (length > PC_TOKEN_MAX_CHARS - 3) {
            ScriptError(script, "string longer than MAX_TOKEN = %d",
                         PC_TOKEN_MAX_CHARS);
            return qfalse;
        }

        if (*script->script_p == '\\' &&
            (script->flags & PC_SCRIPT_FLAG_NO_STRING_ESCAPE_CHARS) == 0) {
            if (PS_ReadEscapeCharacter(script, &token->string[length]) == qfalse) {
                token->string[length] = '\0';
                return qfalse;
            }
            length++;
            continue;
        }

        if (*script->script_p == quote) {
            script->script_p++;
            if ((script->flags & PC_SCRIPT_FLAG_NO_STRING_CONCAT) != 0) {
                break;
            }

            char *savedScript = script->script_p;
            int32_t savedLine = script->line;
            if (PS_ReadWhiteSpace(script) == qfalse ||
                *script->script_p != quote) {
                script->script_p = savedScript;
                script->line = savedLine;
                break;
            }

            script->script_p++;
            continue;
        }

        if (*script->script_p == '\0') {
            token->string[length] = '\0';
            ScriptError(script, "missing trailing quote");
            return qfalse;
        }

        if (*script->script_p == '\n') {
            token->string[length] = '\0';
            ScriptError(script, "newline inside string %s",
                         token->string);
            return qfalse;
        }

        token->string[length] = *script->script_p;
        script->script_p++;
        length++;
    }

    token->string[length] = (char)quote;
    token->string[length + 1] = '\0';
    token->subtype = length + 1;
    return qtrue;
}

qboolean PS_ReadName(script_t *script, token_t *token)
{
    int32_t length = 0;

    token->type = PC_TOKEN_TYPE_NAME;
    do {
        token->string[length] = *script->script_p;
        script->script_p++;
        length++;

        /* ORIGINAL_BINARY_BUG: the failure path returns before adding a NUL.
         * PC_ReadTokenHandle nevertheless publishes the partial token with
         * strcpy.  See original-bugs/
         * coduomp-precompiler-tokenizer-boundary-accesses.md. */
        if (length > PC_TOKEN_MAX_CHARS - 1) {
            ScriptError(script, "name longer than MAX_TOKEN = %d",
                         PC_TOKEN_MAX_CHARS);
            return qfalse;
        }
    } while ((*script->script_p >= 'a' && *script->script_p <= 'z') ||
             (*script->script_p >= 'A' && *script->script_p <= 'Z') ||
             (*script->script_p >= '0' && *script->script_p <= '9') ||
             *script->script_p == '_');

    token->string[length] = '\0';
    token->subtype = length;
    return qtrue;
}

void NumberValue(char *string, int32_t subtype,
                 int32_t *intValue,
                 uint8_t floatValue[CODUO_PC_TOKEN_FLOAT_VALUE_SIZE])
{
#if EMULATE_X87
    x87f numberValue = x87f_load_f32(0.0f);
#else
    long double numberValue = 0.0L;
    size_t numberValueCopySize = sizeof(numberValue);
    if (numberValueCopySize > PC_X87_EXTENDED_TBYTE_SIZE) {
        numberValueCopySize = PC_X87_EXTENDED_TBYTE_SIZE;
    }
#endif

    *intValue = 0;
    memset(floatValue, 0, CODUO_PC_TOKEN_FLOAT_VALUE_SIZE);

    if ((subtype & PC_TOKEN_SUBTYPE_FLOAT) != 0) {
        uint32_t divisor = 0;

        for (; *string != '\0'; ++string) {
            if (*string == '.') {
                if (divisor != 0) {
#if EMULATE_X87
                    coduo_engine_pc_store_token_float80(floatValue,
                                                         numberValue);
#else
                    /* Stock clears the 12-byte token slot, then fstp writes
                     * only the 10-byte x87 TBYTE payload.  Relaxed non-x87
                     * hosts copy only their valid native long-double bytes. */
                    memcpy(floatValue, &numberValue,
                           numberValueCopySize);
#endif
                    return;
                }
                /* ORIGINAL_BINARY_BUG: the increment here and the for-loop
                 * increment together skip a byte; for a trailing point stock
                 * first evaluates the NUL as a digit and then scans adjacent
                 * token storage.  See original-bugs/
                 * coduomp-precompiler-number-tokenization.md. */
                divisor = 10;
                string++;
            }

            if (divisor == 0) {
#if EMULATE_X87
                /* fild(digit) + fld(10.0L)*numberValue */
                numberValue = x87f_add(
                    x87f_load_i32(*string - '0'),
                    x87f_mul(x87f_load_f64((double)PC_NUMBER_FLOAT_BASE),
                             numberValue));
#else
                numberValue =
                    (long double)(*string - '0') +
                    PC_NUMBER_FLOAT_BASE * numberValue;
#endif
            } else {
#if EMULATE_X87
                /* numberValue += fild(digit) / (uint32->80)divisor */
                numberValue = x87f_add(
                    numberValue,
                    x87f_div(x87f_load_i32(*string - '0'),
                             x87f_load_f64((double)divisor)));
#else
                numberValue += (long double)(*string - '0') /
                               (long double)divisor;
#endif
                divisor *= 10;
            }
        }

        /* 0x807df16: fistp QWORD + low dword = unsigned-int conversion,
         * not the fistp DWORD a direct (int32_t) cast would emit. */
#if EMULATE_X87
        *intValue = (int32_t)(uint32_t)extF80_to_i64(
            numberValue, softfloat_round_minMag, false);
        coduo_engine_pc_store_token_float80(floatValue, numberValue);
#else
        *intValue = (int32_t)(uint32_t)numberValue;
        memcpy(floatValue, &numberValue, numberValueCopySize);
#endif
        return;
    }

    if ((subtype & PC_TOKEN_SUBTYPE_DECIMAL) != 0) {
        for (; *string != '\0'; ++string) {
            uint32_t value = (uint32_t)*intValue;
            value = value * 10 + (uint32_t)(*string - '0');
            *intValue = (int32_t)value;
        }
#if EMULATE_X87
        numberValue = x87f_load_f64((double)(uint32_t)*intValue);
        coduo_engine_pc_store_token_float80(floatValue, numberValue);
#else
        numberValue = (long double)(uint32_t)*intValue;
        memcpy(floatValue, &numberValue, numberValueCopySize);
#endif
        return;
    }

    if ((subtype & PC_TOKEN_SUBTYPE_HEX) != 0) {
        for (string += 2; *string != '\0'; ++string) {
            uint32_t value = (uint32_t)*intValue << 4;
            if (*string >= 'a' && *string <= 'f') {
                value += (uint32_t)(*string - 'a' + 10);
            } else if (*string >= 'A' && *string <= 'F') {
                value += (uint32_t)(*string - 'A' + 10);
            } else {
                value += (uint32_t)(*string - '0');
            }
            *intValue = (int32_t)value;
        }
#if EMULATE_X87
        numberValue = x87f_load_f64((double)(uint32_t)*intValue);
        coduo_engine_pc_store_token_float80(floatValue, numberValue);
#else
        numberValue = (long double)(uint32_t)*intValue;
        memcpy(floatValue, &numberValue, numberValueCopySize);
#endif
        return;
    }

    if ((subtype & PC_TOKEN_SUBTYPE_OCTAL) != 0) {
        while (*++string != '\0') {
            uint32_t value = (uint32_t)*intValue;
            value = value * 8 + (uint32_t)(*string - '0');
            *intValue = (int32_t)value;
        }
#if EMULATE_X87
        numberValue = x87f_load_f64((double)(uint32_t)*intValue);
        coduo_engine_pc_store_token_float80(floatValue, numberValue);
#else
        numberValue = (long double)(uint32_t)*intValue;
        memcpy(floatValue, &numberValue, numberValueCopySize);
#endif
        return;
    }

    if ((subtype & PC_TOKEN_SUBTYPE_BINARY) != 0) {
        for (string += 2; *string != '\0'; ++string) {
            uint32_t value = (uint32_t)*intValue;
            value = value * 2 + (uint32_t)(*string - '0');
            *intValue = (int32_t)value;
        }
#if EMULATE_X87
        numberValue = x87f_load_f64((double)(uint32_t)*intValue);
        coduo_engine_pc_store_token_float80(floatValue, numberValue);
#else
        numberValue = (long double)(uint32_t)*intValue;
        memcpy(floatValue, &numberValue, numberValueCopySize);
#endif
    }
}

qboolean PS_ReadNumber(script_t *script, token_t *token)
{
    int32_t length = 0;

    token->type = PC_TOKEN_TYPE_NUMBER;
    token->subtype = PC_TOKEN_SUBTYPE_NONE;

    if (script->script_p[0] == '0' &&
        (script->script_p[1] == 'x' || script->script_p[1] == 'X')) {
        token->string[length++] = *script->script_p++;
        token->string[length++] = *script->script_p++;

        /* ORIGINAL_BINARY_BUG: the final uppercase test is exactly 'A', so
         * uppercase B-F terminate an otherwise valid hexadecimal token.  See
         * original-bugs/coduomp-precompiler-number-tokenization.md. */
        while ((*script->script_p >= '0' && *script->script_p <= '9') ||
               (*script->script_p >= 'a' && *script->script_p <= 'f') ||
               *script->script_p == 'A') {
            token->string[length++] = *script->script_p++;
            /* ORIGINAL_BINARY_BUG: this failed fixed-capacity number path
             * returns without terminating the token; PC_ReadTokenHandle still
             * publishes it.  See original-bugs/
             * coduomp-precompiler-tokenizer-boundary-accesses.md. */
            if (length > PC_TOKEN_MAX_CHARS - 1) {
                ScriptError(script,
                             "hexadecimal number longer than MAX_TOKEN = %d",
                             PC_TOKEN_MAX_CHARS);
                return qfalse;
            }
        }

        token->subtype |= PC_TOKEN_SUBTYPE_HEX;
    } else if (script->script_p[0] == '0' &&
               (script->script_p[1] == 'b' || script->script_p[1] == 'B')) {
        token->string[length++] = *script->script_p++;
        token->string[length++] = *script->script_p++;

        while (*script->script_p == '0' || *script->script_p == '1') {
            token->string[length++] = *script->script_p++;
            /* ORIGINAL_BINARY_BUG: like the hexadecimal path, this return
             * leaves no NUL for PC_ReadTokenHandle's unconditional strcpy.
             * See original-bugs/
             * coduomp-precompiler-tokenizer-boundary-accesses.md. */
            if (length > PC_TOKEN_MAX_CHARS - 1) {
                ScriptError(script,
                             "binary number longer than MAX_TOKEN = %d",
                             PC_TOKEN_MAX_CHARS);
                return qfalse;
            }
        }

        token->subtype |= PC_TOKEN_SUBTYPE_BINARY;
    } else {
        qboolean octal = script->script_p[0] == '0' ? qtrue : qfalse;
        qboolean hasDot = qfalse;

        while (qtrue) {
            char c = *script->script_p;
            if (c == '.') {
                hasDot = qtrue;
            } else if (c == '8' || c == '9') {
                octal = qfalse;
            } else if (c < '0' || c > '9') {
                token->subtype |=
                    octal != qfalse ? PC_TOKEN_SUBTYPE_OCTAL
                                    : PC_TOKEN_SUBTYPE_DECIMAL;
                if (hasDot != qfalse) {
                    token->subtype |= PC_TOKEN_SUBTYPE_FLOAT;
                }
                break;
            }

            token->string[length++] = *script->script_p++;
            if (length >= PC_TOKEN_MAX_CHARS - 1) {
                ScriptError(script, "number longer than MAX_TOKEN = %d",
                             PC_TOKEN_MAX_CHARS);
                return qfalse;
            }
        }
    }

    for (int32_t suffix = 0; suffix < PC_NUMBER_SUFFIX_CHECK_COUNT;
         ++suffix) {
        char c = *script->script_p;
        if ((c == 'l' || c == 'L') &&
            (token->subtype & PC_TOKEN_SUBTYPE_LONG) == 0) {
            script->script_p++;
            token->subtype |= PC_TOKEN_SUBTYPE_LONG;
        } else if ((c == 'u' || c == 'U') &&
                   (token->subtype & (PC_TOKEN_SUBTYPE_UNSIGNED |
                                      PC_TOKEN_SUBTYPE_FLOAT)) == 0) {
            script->script_p++;
            token->subtype |= PC_TOKEN_SUBTYPE_UNSIGNED;
        }
    }

    token->string[length] = '\0';
    NumberValue(token->string, token->subtype, &token->intValue,
                token->floatValue);
    if ((token->subtype & PC_TOKEN_SUBTYPE_FLOAT) == 0) {
        token->subtype |= PC_TOKEN_SUBTYPE_INTEGER;
    }

    return qtrue;
}

qboolean PS_ReadLiteral(script_t *script, token_t *token)
{
    token->type = PC_TOKEN_TYPE_LITERAL;
    token->string[0] = *script->script_p;
    script->script_p++;

    if (*script->script_p == '\0') {
        ScriptError(script, "end of file before trailing \'");
        return qfalse;
    }

    if (*script->script_p == '\\') {
        if (PS_ReadEscapeCharacter(script, &token->string[1]) == qfalse) {
            return qfalse;
        }
    } else {
        token->string[1] = *script->script_p;
        script->script_p++;
    }

    if (*script->script_p != '\'') {
        ScriptWarning(script, "too many characters in literal, ignored");
        while (*script->script_p != '\0' && *script->script_p != '\'' &&
               *script->script_p != '\n') {
            script->script_p++;
        }
        if (*script->script_p == '\'') {
            script->script_p++;
        }
    }

    /* ORIGINAL_BINARY_BUG: after recovery reaches a closing quote, NUL or
     * newline, stock copies and consumes that following byte as string[2].
     * See original-bugs/coduomp-precompiler-tokenizer-boundary-accesses.md. */
    token->string[2] = *script->script_p;
    script->script_p++;
    token->string[3] = '\0';
    token->subtype = (int8_t)token->string[1];
    return qtrue;
}

qboolean PS_ReadPunctuation(script_t *script, token_t *token)
{
    /* ORIGINAL_BINARY_BUG: the stock lookup repeats SetScriptPunctuations'
     * signed-byte bucket index and can read before the table.  See
     * original-bugs/coduomp-precompiler-tokenizer-boundary-accesses.md. */
    punctuation_t *punctuation =
        script->punctuationtable[(int8_t)*script->script_p];

    while (punctuation != NULL) {
        size_t length = strlen(punctuation->p);
        if (script->script_p + length <= script->end_p &&
            strncmp(script->script_p, punctuation->p, length) == 0) {
            strncpy(token->string, punctuation->p, PC_TOKEN_MAX_CHARS);
            script->script_p += length;
            token->type = PC_TOKEN_TYPE_PUNCTUATION;
            token->subtype = punctuation->n;
            return qtrue;
        }

        punctuation = punctuation->next;
    }

    return qfalse;
}

qboolean PS_ReadPrimitive(script_t *script, token_t *token)
{
    int32_t length = 0;

    while (*script->script_p > ' ' && *script->script_p != ';') {
        if (length > PC_TOKEN_MAX_CHARS - 1) {
            ScriptError(script, "primitive token longer than MAX_TOKEN = %d",
                         PC_TOKEN_MAX_CHARS);
            return qfalse;
        }

        token->string[length] = *script->script_p;
        script->script_p++;
        length++;
    }

    /* ORIGINAL_BINARY_BUG: an exactly 1,024-byte primitive exits the loop
     * with length == sizeof(string), so this NUL overwrites token.type.  See
     * original-bugs/coduomp-precompiler-tokenizer-boundary-accesses.md. */
    token->string[length] = '\0';
    memcpy(&script->token, token, sizeof(script->token));
    return qtrue;
}

qboolean PS_ReadToken(script_t *script, token_t *token)
{
    if (script->tokenavailable != 0) {
        script->tokenavailable = qfalse;
        memcpy(token, &script->token, sizeof(*token));
        return qtrue;
    }

    script->lastscript_p = script->script_p;
    script->lastline = script->line;
    memset(token, 0, sizeof(*token));

    script->whitespace_p = script->script_p;
    token->whitespaceStart = script->script_p;
    if (PS_ReadWhiteSpace(script) == qfalse) {
        return qfalse;
    }

    script->endwhitespace_p = script->script_p;
    token->whitespaceEnd = script->script_p;
    token->line = script->line;
    token->linesCrossed = script->line - script->lastline;

    if (*script->script_p == '"') {
        if (PS_ReadString(script, token, '"') == qfalse) {
            return qfalse;
        }
    } else if (*script->script_p == '\'') {
        if (PS_ReadString(script, token, '\'') == qfalse) {
            return qfalse;
        }
    } else if ((*script->script_p >= '0' && *script->script_p <= '9') ||
               (*script->script_p == '.' &&
                script->script_p[1] >= '0' && script->script_p[1] <= '9')) {
        if (PS_ReadNumber(script, token) == qfalse) {
            return qfalse;
        }
    } else {
        if ((script->flags & PC_SCRIPT_FLAG_PRIMITIVE) != 0) {
            return PS_ReadPrimitive(script, token);
        }

        if ((*script->script_p >= 'a' && *script->script_p <= 'z') ||
            (*script->script_p >= 'A' && *script->script_p <= 'Z') ||
            *script->script_p == '_') {
            if (PS_ReadName(script, token) == qfalse) {
                return qfalse;
            }
        } else if (PS_ReadPunctuation(script, token) == qfalse) {
            ScriptError(script, "can't read token");
            return qfalse;
        }
    }

    memcpy(&script->token, token, sizeof(script->token));
    return qtrue;
}

qboolean PS_ExpectTokenString(script_t *script, const char *string)
{
    token_t token;

    if (PS_ReadToken(script, &token) == qfalse) {
        ScriptError(script, "couldn't find expected %s", string);
        return qfalse;
    }

    if (strcmp(token.string, string) == 0) {
        return qtrue;
    }

    ScriptError(script, "expected %s, found %s", string, token.string);
    return qfalse;
}

qboolean PS_ExpectTokenType(script_t *script,
                      int32_t type,
                      int32_t subtype,
                      token_t *token)
{
    char expected[PC_TOKEN_MAX_CHARS];

    if (PS_ReadToken(script, token) == qfalse) {
        ScriptError(script, "couldn't read expected token");
        return qfalse;
    }

    if (token->type == type) {
        if (token->type == PC_TOKEN_TYPE_NUMBER &&
            (token->subtype & subtype) != subtype) {
            /* ORIGINAL_BINARY_BUG: no radix bit means none of the strcpy
             * initializers run before suffix text is appended to this
             * uninitialized stack array.  See original-bugs/
             * coduomp-precompiler-expectation-diagnostics.md. */
            if ((subtype & PC_TOKEN_SUBTYPE_DECIMAL) != 0) {
                strcpy(expected, "decimal");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_HEX) != 0) {
                strcpy(expected, "hex");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_OCTAL) != 0) {
                strcpy(expected, "octal");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_BINARY) != 0) {
                strcpy(expected, "binary");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_LONG) != 0) {
                strcat(expected, " long");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_UNSIGNED) != 0) {
                strcat(expected, " unsigned");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_FLOAT) != 0) {
                strcat(expected, " float");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_INTEGER) != 0) {
                strcat(expected, " integer");
            }

            ScriptError(script, "expected %s, found %s", expected,
                         token->string);
            return qfalse;
        }

        if (token->type == PC_TOKEN_TYPE_PUNCTUATION) {
            if (subtype < 0) {
                ScriptError(script, "BUG: wrong punctuation subtype");
                return qfalse;
            }
            if (token->subtype != subtype) {
                /* ORIGINAL_BINARY_BUG: stock indexes the zero-based record
                 * array with the one-based subtype and copies its p, n, and
                 * next words through `...`.  The two %s conversions consume
                 * p and n; token->string is shifted past both conversions.
                 * The explicit fields retain those slots when pointers widen.
                 * See original-bugs/
                 * coduomp-precompiler-expectation-diagnostics.md. */
                punctuation_t punctuation =
                    script->punctuations[subtype];
                ScriptError(script, "expected %s, found %s",
                             punctuation.p,
                             punctuation.n,
                             punctuation.next,
                             token->string);
                return qfalse;
            }
        }

        return qtrue;
    }

    if (type == PC_TOKEN_TYPE_STRING) {
        strcpy(expected, "string");
    }
    if (type == PC_TOKEN_TYPE_LITERAL) {
        strcpy(expected, "literal");
    }
    if (type == PC_TOKEN_TYPE_NUMBER) {
        strcpy(expected, "number");
    }
    if (type == PC_TOKEN_TYPE_NAME) {
        strcpy(expected, "name");
    }
    if (type == PC_TOKEN_TYPE_PUNCTUATION) {
        strcpy(expected, "punctuation");
    }

    ScriptError(script, "expected a %s, found %s", expected,
                 token->string);
    return qfalse;
}

qboolean PS_ReadTokenOrError(script_t *script, token_t *token)
{
    qboolean result = PS_ReadToken(script, token);
    if (result == qfalse) {
        ScriptError(script, "couldn't read expected token");
    }

    return result != qfalse ? qtrue : qfalse;
}

qboolean PS_CheckTokenString(script_t *script, const char *string)
{
    token_t token;

    if (PS_ReadToken(script, &token) == qfalse) {
        return qfalse;
    }

    if (strcmp(token.string, string) == 0) {
        return qtrue;
    }

    /* ORIGINAL_BINARY_BUG: stock rewinds only the byte cursor, not line, so
     * rejected lookahead that crossed whitespace can count a newline twice.
     * See original-bugs/coduomp-precompiler-lookahead-line-drift.md. */
    script->script_p = script->lastscript_p;
    return qfalse;
}

qboolean PS_CheckTokenType(script_t *script,
                      int32_t type,
                      int32_t subtype,
                      token_t *token)
{
    token_t readToken;

    if (PS_ReadToken(script, &readToken) == qfalse) {
        return qfalse;
    }

    if (readToken.type == type &&
        (subtype & readToken.subtype) == subtype) {
        memcpy(token, &readToken, sizeof(*token));
        return qtrue;
    }

    /* ORIGINAL_BINARY_BUG: this rejected lookahead repeats the cursor-only
     * rewind and leaves the advanced line number intact.  See
     * original-bugs/coduomp-precompiler-lookahead-line-drift.md. */
    script->script_p = script->lastscript_p;
    return qfalse;
}

qboolean PS_SkipUntilString(script_t *script, const char *string)
{
    token_t token;

    do {
        if (PS_ReadToken(script, &token) == qfalse) {
            return qfalse;
        }
    } while (strcmp(token.string, string) != 0);

    return qtrue;
}

void PS_UnreadLastToken(script_t *script)
{
    script->tokenavailable = qtrue;
}

void PS_UnreadToken(script_t *script, const token_t *token)
{
    memcpy(&script->token, token, sizeof(script->token));
    script->tokenavailable = qtrue;
}

int32_t PS_ReadWhitespaceChar(script_t *script)
{
    if (script->whitespace_p == script->endwhitespace_p) {
        return 0;
    }

    int32_t value = *script->whitespace_p;
    script->whitespace_p++;
    return value;
}

void StripDoubleQuotes(char *string)
{
    if (string[0] == '"') {
        /* ORIGINAL_BINARY_BUG: stock invokes strcpy on overlapping ranges.
         * It then reads string[-1] when the resulting string is empty.  See
         * original-bugs/coduomp-precompiler-tokenizer-boundary-accesses.md. */
        strcpy(string, string + 1);
    }

    /* ORIGINAL_BINARY_BUG: a directly supplied empty string reaches this
     * same string[-1] access without taking the overlapping-copy path. */
    size_t length = strlen(string);
    if (string[length - 1] == '"') {
        string[length - 1] = '\0';
    }
}

void StripSingleQuotes(char *string)
{
    if (string[0] == '\'') {
        /* ORIGINAL_BINARY_BUG: this helper repeats the overlapping strcpy and
         * empty-string string[-1] read.  See
         * original-bugs/coduomp-precompiler-tokenizer-boundary-accesses.md. */
        strcpy(string, string + 1);
    }

    /* ORIGINAL_BINARY_BUG: a directly supplied empty string likewise reads
     * one byte before the object here. */
    size_t length = strlen(string);
    if (string[length - 1] == '\'') {
        string[length - 1] = '\0';
    }
}

long double PS_ReadFloat(script_t *script)
{
    token_t token;
    /* 0x807f0b8/0x807f105: sign is an x87 long double constant
     * ({0,0x80000000,0x3fff/0xbfff} immediate stores), not an int. */
#if EMULATE_X87
    x87f sign = x87f_load_f32(1.0f);
#else
    long double sign = 1.0L;
#endif

    PS_ReadTokenOrError(script, &token);
    if (strcmp(token.string, "-") == 0) {
#if EMULATE_X87
        sign = x87f_load_f32(-1.0f);
#else
        sign = -1.0L;
#endif
        PS_ExpectTokenType(script, PC_TOKEN_TYPE_NUMBER, PC_TOKEN_SUBTYPE_NONE,
                     &token);
    } else if (token.type != PC_TOKEN_TYPE_NUMBER) {
        ScriptError(script, "expected float value, found %s\n",
                     token.string);
    }

#if EMULATE_X87
    /* fld TBYTE(token) * sign(±1.0), returned narrowed to the long double ABI
     * (double off x87). No in-tree callers; kept faithful for completeness. */
    x87f value = coduo_engine_pc_load_token_float80(token.floatValue);
    return (long double)x87f_store_f64(x87f_mul(sign, value));
#else
    long double value = 0.0L;
    size_t valueSize = sizeof(value);
    if (CODUO_PC_TOKEN_FLOAT_VALUE_SIZE < valueSize) {
        valueSize = CODUO_PC_TOKEN_FLOAT_VALUE_SIZE;
    }
    memcpy(&value, token.floatValue, valueSize);
    return sign * value;
#endif
}

int32_t PS_ReadInteger(script_t *script)
{
    token_t token;
    int32_t sign = PC_SCRIPT_SIGN_POSITIVE;

    PS_ReadTokenOrError(script, &token);
    if (strcmp(token.string, "-") == 0) {
        sign = PC_SCRIPT_SIGN_NEGATIVE;
        PS_ExpectTokenType(script, PC_TOKEN_TYPE_NUMBER, PC_TOKEN_SUBTYPE_INTEGER,
                     &token);
    } else if (token.type != PC_TOKEN_TYPE_NUMBER ||
               /* ORIGINAL_BINARY_BUG: stock compares the complete subtype to
                * FLOAT instead of testing its bit, so DECIMAL|FLOAT is
                * accepted.  See original-bugs/
                * coduomp-precompiler-readinteger-float-accepted.md. */
               token.subtype == PC_TOKEN_SUBTYPE_FLOAT) {
        ScriptError(script, "expected integer value, found %s\n",
                     token.string);
    }

    return sign * (int32_t)token.intValue;
}

void SetScriptFlags(script_t *script, int32_t flags)
{
    script->flags = flags;
}

int32_t GetScriptFlags(const script_t *script)
{
    return script->flags;
}

void ResetScript(script_t *script)
{
    script->script_p = script->buffer;
    script->lastscript_p = script->buffer;
    script->whitespace_p = NULL;
    script->endwhitespace_p = NULL;
    script->tokenavailable = PC_SCRIPT_NOT_AVAILABLE;
    script->line = PC_SCRIPT_INITIAL_LINE;
    script->lastline = PC_SCRIPT_INITIAL_LINE;
    memset(&script->token, 0, sizeof(script->token));
}

qboolean EndOfScript(script_t *script)
{
    return script->end_p <= script->script_p ? qtrue : qfalse;
}

int32_t PS_LinesCrossed(const script_t *script)
{
    return script->line - script->lastline;
}

qboolean PS_FindStringInScript(script_t *script, const char *string)
{
    char first = string[0];
    size_t length = strlen(string);

    while (PS_ReadWhiteSpace(script) != qfalse) {
        if (script->script_p[0] == first &&
            strncmp(script->script_p, string, length) == 0) {
            return qtrue;
        }

        script->script_p++;
    }

    return qfalse;
}

void PC_PushIndent(source_t *source, int32_t type, qboolean skip)
{
    indent_t *indent = Com_ZoneDebugAlloc(sizeof(*indent));

    indent->type = type;
    indent->script = source->scriptStack;
    indent->skip = skip != qfalse ? qtrue : qfalse;
    source->skip += indent->skip;
    indent->next = source->indentStack;
    source->indentStack = indent;
}

void PC_PopIndent(source_t *source, int32_t *type, qboolean *skip)
{
    *type = PC_INDENT_TYPE_NONE;
    *skip = qfalse;

    indent_t *indent = source->indentStack;
    if (indent == NULL || indent->script != source->scriptStack) {
        return;
    }

    *type = indent->type;
    *skip = indent->skip;
    source->indentStack = indent->next;
    source->skip -= indent->skip;
    Com_DebugFree(indent);
}

void PC_AddScriptToSource(source_t *source, script_t *script)
{
    for (script_t *scan = source->scriptStack; scan != NULL;
         scan = scan->next) {
        if (Q_stricmp(scan->filename, script->filename) == 0) {
            PC_SourceError(source, "%s recursively included",
                           script->filename);
            /* ORIGINAL_BINARY_BUG: stock returns without freeing the newly
             * loaded script.  See
             * original-bugs/coduomp-precompiler-error-path-leaks.md. */
            return;
        }
    }

    script->next = source->scriptStack;
    source->scriptStack = script;
}

void PC_InitTokenHeap(void)
{
}

token_t *PC_CopyToken(const token_t *token)
{
    token_t *copy =
        Com_ZoneDebugAlloc(sizeof(*copy));

    if (copy == NULL) {
        Com_Error(PC_SOURCE_ERROR_FATAL, "EXE_ERR_OUT_OF_MEMORY");
        return NULL;
    }

    memcpy(copy, token, sizeof(*copy));
    copy->next = NULL;
    pc_tokenNodeCount++;

    return copy;
}

void PC_FreeToken(token_t *token)
{
    Com_DebugFree(token);
    pc_tokenNodeCount--;
}

qboolean PC_ReadSourceToken(source_t *source, token_t *token)
{
    if (source->tokens != NULL) {
        memcpy(token, source->tokens, sizeof(*token));
        token_t *cachedToken = source->tokens;
        source->tokens = source->tokens->next;
        PC_FreeToken(cachedToken);
        return qtrue;
    }

    while (PS_ReadToken(source->scriptStack, token) == qfalse) {
        if (EndOfScript(source->scriptStack) != qfalse) {
            while (source->indentStack != NULL &&
                   source->indentStack->script == source->scriptStack) {
                int32_t indentType;
                qboolean indentSkip;

                PC_SourceWarning(source, "missing #endif");
                PC_PopIndent(source, &indentType, &indentSkip);
            }
        }

        if (source->scriptStack->next == NULL) {
            return qfalse;
        }

        script_t *script = source->scriptStack;
        source->scriptStack = source->scriptStack->next;
        FreeScript(script);
    }

    return qtrue;
}

qboolean PC_UnreadSourceToken(source_t *source,
                              const token_t *token)
{
    token_t *copy = PC_CopyToken(token);

    copy->next = source->tokens;
    source->tokens = copy;

    return qtrue;
}

qboolean PC_ReadDefineParms(source_t *source,
                            const define_t *define,
                            token_t **actualParms,
                            int32_t maxParms)
{
    token_t token;

    if (PC_ReadSourceToken(source, &token) == qfalse) {
        PC_SourceError(source, "define %s missing parms", define->name);
        return qfalse;
    }

    if (maxParms < define->numParms) {
        PC_SourceError(source, "define with more than %d parameters",
                       maxParms);
        return qfalse;
    }

    for (int32_t parmIndex = 0; parmIndex < define->numParms; ++parmIndex) {
        actualParms[parmIndex] = NULL;
    }

    if (strcmp(token.string, "(") != 0) {
        PC_UnreadSourceToken(source, &token);
        PC_SourceError(source, "define %s missing parms", define->name);
        return qfalse;
    }

    qboolean done = qfalse;
    int32_t parmIndex = 0;
    int32_t parentheses = 0;

    while (done == qfalse) {
        if (maxParms <= parmIndex) {
            PC_SourceError(source, "define %s with too many parms",
                           define->name);
            return qfalse;
        }

        if (define->numParms <= parmIndex) {
            PC_SourceWarning(source, "define %s has too many parms",
                             define->name);
            return qfalse;
        }

        actualParms[parmIndex] = NULL;

        qboolean firstToken = qtrue;
        token_t *lastToken = NULL;

        while (done == qfalse) {
            if (PC_ReadSourceToken(source, &token) == qfalse) {
                PC_SourceError(source, "define %s incomplete", define->name);
                return qfalse;
            }

            if (strcmp(token.string, ",") == 0 && parentheses < 1) {
                if (firstToken != qfalse) {
                    PC_SourceWarning(source, "too many comma's");
                }
                break;
            }

            firstToken = qfalse;

            if (strcmp(token.string, "(") == 0) {
                parentheses++;
                continue;
            } else if (strcmp(token.string, ")") == 0 && --parentheses < 1) {
                if (actualParms[define->numParms - 1] == NULL) {
                    PC_SourceWarning(source, "too few define parms");
                }
                done = qtrue;
                break;
            }

            if (parmIndex < define->numParms) {
                token_t *copy = PC_CopyToken(&token);

                copy->next = NULL;
                if (lastToken == NULL) {
                    actualParms[parmIndex] = copy;
                } else {
                    lastToken->next = copy;
                }
                lastToken = copy;
            }
        }

        parmIndex++;
    }

    return qtrue;
}

qboolean PC_StringizeTokens(token_t *tokens, token_t *token)
{
    token->type = PC_TOKEN_TYPE_STRING;
    token->whitespaceStart = PC_TOKEN_WHITESPACE_NONE;
    token->whitespaceEnd = PC_TOKEN_WHITESPACE_NONE;
    token->string[0] = '\0';

    strcat(token->string, "\"");

    /* ORIGINAL_BINARY_BUG: 0x080788fa
     * computes the strncat limit as 0x400 - strlen(dst), so a full token
     * lets strncat write its terminating NUL at string[0x400], zeroing the
     * low byte of the adjacent type field (offset 0x400).  Classic id
     * botlib MAX_TOKEN - strlen() idiom.  See
     * original-bugs/engine-pc-stringize-strncat-nul.md. */
    for (token_t *scan = tokens; scan != NULL; scan = scan->next) {
        size_t tokenLength = strlen(token->string);

        strncat(token->string, scan->string,
                CODUO_PC_TOKEN_STRING_SIZE - tokenLength);
    }

    size_t tokenLength = strlen(token->string);

    strncat(token->string, "\"", CODUO_PC_TOKEN_STRING_SIZE - tokenLength);
    return qtrue;
}

qboolean PC_MergeTokens(token_t *token, const token_t *next)
{
    if (token->type == PC_TOKEN_TYPE_NAME &&
        (next->type == PC_TOKEN_TYPE_NAME ||
         next->type == PC_TOKEN_TYPE_NUMBER)) {
        strcat(token->string, next->string);
        return qtrue;
    }

    if (token->type == PC_TOKEN_TYPE_STRING &&
        next->type == PC_TOKEN_TYPE_STRING) {
        token->string[strlen(token->string) - 1] = '\0';
        strcat(token->string, &next->string[1]);
        return qtrue;
    }

    return qfalse;
}

void PC_PrintDefineHashTable(define_t **defineHash)
{
    for (int32_t bucket = 0; bucket < CODUO_PC_DEFINE_HASH_BUCKET_COUNT;
         ++bucket) {
        Com_LogPrintf("%4d:", bucket);
        for (define_t *define = defineHash[bucket]; define != NULL;
             define = define->hashNext) {
            Com_LogPrintf(" %s", define->name);
        }
        Com_LogPrintf("\n");
    }
}

uint32_t PC_NameHash(const char *name)
{
    uint32_t hash = 0;

    for (int32_t index = 0; name[index] != '\0'; ++index) {
        hash += (uint32_t)((index + PC_DEFINE_HASH_INDEX_BIAS) *
                           coduo_int8_from_bits((uint8_t)name[index]));
    }

    return (hash ^ coduo_int32_sar_bits(hash, PC_DEFINE_HASH_SHIFT_A) ^
            coduo_int32_sar_bits(hash, PC_DEFINE_HASH_SHIFT_B)) &
           PC_DEFINE_HASH_BUCKET_MASK;
}

void PC_AddDefineToHash(define_t *define,
                        define_t **defineHash)
{
    uint32_t bucket = PC_NameHash(define->name);

    define->hashNext = defineHash[bucket];
    defineHash[bucket] = define;
}

define_t *PC_FindHashedDefine(
    define_t **defineHash, const char *name)
{
    uint32_t bucket = PC_NameHash(name);

    for (define_t *define = defineHash[bucket]; define != NULL;
         define = define->hashNext) {
        if (strcmp(define->name, name) == 0) {
            return define;
        }
    }

    return NULL;
}

define_t *PC_FindDefine(define_t *defines,
                                 const char *name)
{
    for (define_t *define = defines; define != NULL;
         define = define->next) {
        if (strcmp(define->name, name) == 0) {
            return define;
        }
    }

    return NULL;
}

int32_t PC_FindDefineParm(const define_t *define, const char *name)
{
    int32_t parmIndex = 0;

    for (token_t *parm = define->parms; parm != NULL;
         parm = parm->next) {
        if (strcmp(parm->string, name) == 0) {
            return parmIndex;
        }
        parmIndex++;
    }

    return PC_DEFINE_PARAMETER_NOT_FOUND;
}

void PC_FreeDefine(define_t *define)
{
    for (token_t *parm = define->parms; parm != NULL;) {
        token_t *next = parm->next;

        PC_FreeToken(parm);
        parm = next;
    }

    for (token_t *token = define->tokens; token != NULL;) {
        token_t *next = token->next;

        PC_FreeToken(token);
        token = next;
    }

    Com_DebugFree(define);
}

void PC_AddBuiltinDefines(source_t *source)
{
    static const struct {
        const char *name;
        int32_t builtin;
    } builtins[] = {
        {"__LINE__", PC_BUILTIN_LINE},
        {"__FILE__", PC_BUILTIN_FILE},
        {"__DATE__", PC_BUILTIN_DATE},
        {"__TIME__", PC_BUILTIN_TIME},
        {NULL, 0},
    };

    for (int32_t index = 0; builtins[index].name != NULL; ++index) {
        size_t nameLength = strlen(builtins[index].name);
        define_t *define =
            Com_ZoneDebugAlloc(sizeof(*define) + nameLength + 1);

        memset(define, 0, sizeof(*define));
        define->name = define->nameStorage;
        strcpy(define->name, builtins[index].name);
        define->flags |= PC_DEFINE_FLAG_BUILTIN;
        define->builtin = builtins[index].builtin;
        PC_AddDefineToHash(define, source->defineHash);
    }
}

qboolean PC_ExpandBuiltinDefine(source_t *source,
                                const token_t *token,
                                const define_t *define,
                                token_t **firstToken,
                                token_t **lastToken)
{
    token_t *expanded = PC_CopyToken(token);

    switch (define->builtin) {
    case PC_BUILTIN_LINE: {
        sprintf(expanded->string, "%d", token->line);
        expanded->intValue = token->line;

#if EMULATE_X87
        /* fild(line) widened into the 80-bit token TBYTE. */
        coduo_engine_pc_store_token_float80(
            expanded->floatValue, x87f_load_i32(token->line));
#else
        long double lineFloat = (long double)token->line;
        /* Stock copies the ten-byte i386 x87 extended payload here. */
        memcpy(expanded->floatValue, &lineFloat, 10);
#endif

        expanded->type = PC_TOKEN_TYPE_NUMBER;
        expanded->subtype = PC_TOKEN_SUBTYPE_DECIMAL_INTEGER;
        *firstToken = expanded;
        *lastToken = expanded;
        break;
    }
    case PC_BUILTIN_FILE:
        strcpy(expanded->string, source->scriptStack->filename);
        expanded->type = PC_TOKEN_TYPE_NAME;
        expanded->subtype = (int32_t)strlen(expanded->string);
        *firstToken = expanded;
        *lastToken = expanded;
        break;
    case PC_BUILTIN_DATE: {
        time_t rawTime = time(NULL);
        char *timeString = ctime(&rawTime);

        strcpy(expanded->string, "\"");
        strncat(expanded->string,
                &timeString[PC_CTIME_DATE_MONTH_DAY_OFFSET],
                PC_CTIME_DATE_MONTH_DAY_LENGTH);
        strncat(&expanded->string[PC_CTIME_DATE_MONTH_DAY_LENGTH],
                &timeString[PC_CTIME_DATE_YEAR_OFFSET],
                PC_CTIME_DATE_YEAR_LENGTH);
        strcat(expanded->string, "\"");
        /* ORIGINAL_BINARY_BUG: stock passes ctime's static return object to
         * free.  See original-bugs/engine-pc-builtin-time-invalid-free.md. */
        free(timeString);
        expanded->type = PC_TOKEN_TYPE_NAME;
        expanded->subtype = (int32_t)strlen(expanded->string);
        *firstToken = expanded;
        *lastToken = expanded;
        break;
    }
    case PC_BUILTIN_TIME: {
        time_t rawTime = time(NULL);
        char *timeString = ctime(&rawTime);

        strcpy(expanded->string, "\"");
        strncat(expanded->string, &timeString[PC_CTIME_TIME_OFFSET],
                PC_CTIME_TIME_LENGTH);
        strcat(expanded->string, "\"");
        /* ORIGINAL_BINARY_BUG: the __TIME__ case repeats the invalid free of
         * ctime's static return object.  See
         * original-bugs/engine-pc-builtin-time-invalid-free.md. */
        free(timeString);
        expanded->type = PC_TOKEN_TYPE_NAME;
        expanded->subtype = (int32_t)strlen(expanded->string);
        *firstToken = expanded;
        *lastToken = expanded;
        break;
    }
    default:
        *firstToken = NULL;
        *lastToken = NULL;
        break;
    }

    return qtrue;
}

qboolean PC_ExpandDefine(source_t *source,
                         const token_t *token,
                         const define_t *define,
                         token_t **firstToken,
                         token_t **lastToken)
{
    if (define->builtin != 0) {
        return PC_ExpandBuiltinDefine(source, token, define, firstToken,
                                      lastToken);
    }

    token_t *actualParms[PC_DEFINE_MAX_PARMS];
    if (define->numParms != 0 &&
        PC_ReadDefineParms(source, define, actualParms, PC_DEFINE_MAX_PARMS) ==
            qfalse) {
        return qfalse;
    }

    token_t *first = NULL;
    token_t *last = NULL;

    for (const token_t *scan = define->tokens; scan != NULL;
         scan = scan->next) {
        int32_t parmIndex = PC_DEFINE_PARAMETER_NOT_FOUND;

        if (scan->type == PC_TOKEN_TYPE_NAME) {
            parmIndex = PC_FindDefineParm(define, scan->string);
        }

        token_t *copy = NULL;
        if (parmIndex < 0) {
            if (strcmp(scan->string, "#") == 0) {
                const token_t *next = scan->next;
                if (next != NULL) {
                    parmIndex = PC_FindDefineParm(define, next->string);
                }
                if (parmIndex < 0) {
                    PC_SourceWarning(source,
                                     "stringizing operator without define parameter");
                    continue;
                }

                scan = next;
                token_t stringized;
                if (PC_StringizeTokens(actualParms[parmIndex], &stringized) ==
                    qfalse) {
                    PC_SourceError(source, "can't stringize tokens");
                    return qfalse;
                }
                copy = PC_CopyToken(&stringized);
            } else {
                copy = PC_CopyToken(scan);
            }

            copy->next = NULL;
            if (last == NULL) {
                first = copy;
            } else {
                last->next = copy;
            }
            last = copy;
            continue;
        }

        for (token_t *parmToken = actualParms[parmIndex];
             parmToken != NULL; parmToken = parmToken->next) {
            copy = PC_CopyToken(parmToken);
            copy->next = NULL;
            if (last == NULL) {
                first = copy;
            } else {
                last->next = copy;
            }
            last = copy;
        }
    }

    token_t *mergeToken = first;
    while (mergeToken != NULL) {
        if (mergeToken->next == NULL ||
            mergeToken->next->string[0] != '#' ||
            mergeToken->next->string[1] != '#') {
            mergeToken = mergeToken->next;
            continue;
        }

        token_t *hashHash = mergeToken->next;
        token_t *right = hashHash->next;
        if (right == NULL) {
            mergeToken = mergeToken->next;
            continue;
        }

        if (PC_MergeTokens(mergeToken, right) == qfalse) {
            PC_SourceError(source, "can't merge %s with %s",
                           mergeToken->string, right->string);
            return qfalse;
        }

        PC_FreeToken(hashHash);
        mergeToken->next = right->next;
        if (right == last) {
            last = mergeToken;
        }
        PC_FreeToken(right);

        /* 0x080793b5 jumps back to the loop test without advancing the left
         * token.  Rechecking it is behaviorally required for a ## b ## c. */
    }

    *firstToken = first;
    *lastToken = last;

    for (int32_t parmIndex = 0; parmIndex < define->numParms; ++parmIndex) {
        token_t *parmToken = actualParms[parmIndex];
        while (parmToken != NULL) {
            token_t *next = parmToken->next;
            PC_FreeToken(parmToken);
            parmToken = next;
        }
    }

    return qtrue;
}

qboolean PC_ExpandDefineIntoSource(source_t *source,
                                   const token_t *token,
                                   const define_t *define)
{
    token_t *firstToken;
    token_t *lastToken;

    if (PC_ExpandDefine(source, token, define, &firstToken, &lastToken) ==
        qfalse) {
        return qfalse;
    }

    if (firstToken == NULL || lastToken == NULL) {
        return qfalse;
    }

    lastToken->next = source->tokens;
    source->tokens = firstToken;
    return qtrue;
}

void PC_ConvertPath(char *path)
{
    char *cursor = path;

    while (*cursor != '\0') {
        if ((*cursor == '\\' || *cursor == '/') &&
            (cursor[1] == '\\' || cursor[1] == '/')) {
            strcpy(cursor, cursor + 1);
        } else {
            cursor++;
        }
    }

    for (cursor = path; *cursor != '\0'; ++cursor) {
        if (*cursor == '\\' || *cursor == '/') {
            *cursor = '/';
        }
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves the original strncat count while keeping
 * newer GCC object-size diagnostics from rejecting the recovered build. */
#if defined(__GNUC__)
__attribute__((noinline))
#endif
static char *PC_StrncatOriginalBound(char *dest, const char *src, size_t count)
{
    return strncat(dest, src, count);
}

qboolean PC_Directive_include(source_t *source)
{
    if (source->skip > 0) {
        return qtrue;
    }

    token_t token;
    if (PC_ReadSourceToken(source, &token) == qfalse ||
        token.linesCrossed > 0) {
        PC_SourceError(source, "#include without file name");
        return qfalse;
    }

    script_t *script = NULL;
    char includeName[CODUO_PC_SOURCE_INCLUDE_PATH_SIZE];
    includeName[0] = '\0';

    if (token.type == PC_TOKEN_TYPE_STRING) {
        StripDoubleQuotes(token.string);
        PC_ConvertPath(token.string);
        script = LoadScriptFile(token.string);
        if (script == NULL) {
            strcpy(includeName, source->includePath);
            strcat(includeName, token.string);
            script = LoadScriptFile(includeName);
        }
    } else {
        if (token.type != PC_TOKEN_TYPE_PUNCTUATION || token.string[0] != '<') {
            PC_SourceError(source, "#include without file name");
            return qfalse;
        }

        strcpy(includeName, source->includePath);
        while (PC_ReadSourceToken(source, &token) != qfalse) {
            if (token.linesCrossed > 0) {
                PC_UnreadSourceToken(source, &token);
                break;
            }
            if (token.type == PC_TOKEN_TYPE_PUNCTUATION &&
                token.string[0] == '>') {
                break;
            }
            /* ORIGINAL_BINARY_BUG: VA 0x08079741 passes the complete 0x40-byte
             * destination extent to each strncat rather than the remaining
             * capacity.  The string-include fallback also uses unbounded
             * strcpy/strcat.  See
             * original-bugs/coduomp-precompiler-include-path-stack-overflow.md.
             * GCC warns because the API use is unsafe, but the count is
             * machine-code-derived and intentional. */
            PC_StrncatOriginalBound(includeName, token.string,
                                    CODUO_PC_SOURCE_INCLUDE_PATH_SIZE);
        }

        if (token.string[0] != '>') {
            PC_SourceWarning(source, "#include missing trailing >");
        }
        if (includeName[0] == '\0') {
            PC_SourceError(source, "#include without file name between < >");
            return qfalse;
        }

        PC_ConvertPath(includeName);
        script = LoadScriptFile(includeName);
    }

    if (script == NULL) {
        PC_SourceError(source, "file %s not found", includeName);
        return qfalse;
    }

    PC_AddScriptToSource(source, script);
    return qtrue;
}

qboolean PC_ReadLine(source_t *source, token_t *token)
{
    int32_t continuationLines = 0;

    while (PC_ReadSourceToken(source, token) != qfalse) {
        if (continuationLines < token->linesCrossed) {
            PC_UnreadSourceToken(source, token);
            return qfalse;
        }

        continuationLines = 1;
        if (strcmp(token->string, "\\") != 0) {
            return qtrue;
        }
    }

    return qfalse;
}

qboolean PC_WhiteSpaceBeforeToken(const token_t *token)
{
    return token->whitespaceEnd - token->whitespaceStart > 0 ? qtrue
                                                             : qfalse;
}

void PC_ClearTokenWhiteSpace(token_t *token)
{
    token->whitespaceStart = PC_TOKEN_WHITESPACE_NONE;
    token->whitespaceEnd = PC_TOKEN_WHITESPACE_NONE;
    token->linesCrossed = 0;
}

qboolean PC_Directive_undef(source_t *source)
{
    if (source->skip > 0) {
        return qtrue;
    }

    token_t token;
    if (PC_ReadLine(source, &token) == qfalse) {
        PC_SourceError(source, "undef without name");
        return qfalse;
    }

    if (token.type != PC_TOKEN_TYPE_NAME) {
        PC_UnreadSourceToken(source, &token);
        PC_SourceError(source, "expected name, found %s", token.string);
        return qfalse;
    }

    uint32_t bucket = PC_NameHash(token.string);
    define_t *previous = NULL;
    define_t *define = source->defineHash[bucket];

    while (define != NULL) {
        if (strcmp(define->name, token.string) == 0) {
            if ((define->flags & PC_DEFINE_FLAG_BUILTIN) != 0) {
                PC_SourceWarning(source, "can't undef %s", token.string);
            } else {
                if (previous == NULL) {
                    source->defineHash[bucket] = define->hashNext;
                } else {
                    previous->hashNext = define->hashNext;
                }
                PC_FreeDefine(define);
            }
            break;
        }

        previous = define;
        define = define->hashNext;
    }

    return qtrue;
}

qboolean PC_Directive_define(source_t *source)
{
    if (source->skip > 0) {
        return qtrue;
    }

    token_t token;
    if (PC_ReadLine(source, &token) == qfalse) {
        PC_SourceError(source, "#define without name");
        return qfalse;
    }

    if (token.type != PC_TOKEN_TYPE_NAME) {
        PC_UnreadSourceToken(source, &token);
        PC_SourceError(source, "expected name after #define, found %s",
                       token.string);
        return qfalse;
    }

    define_t *existing =
        PC_FindHashedDefine(source->defineHash, token.string);
    if (existing != NULL) {
        if ((existing->flags & PC_DEFINE_FLAG_BUILTIN) != 0) {
            PC_SourceError(source, "can't redefine %s", token.string);
            return qfalse;
        }

        PC_SourceWarning(source, "redefinition of %s", token.string);
        PC_UnreadSourceToken(source, &token);
        if (PC_Directive_undef(source) == qfalse) {
            return qfalse;
        }
        PC_FindHashedDefine(source->defineHash, token.string);
    }

    size_t nameLength = strlen(token.string);
    define_t *define =
        Com_ZoneDebugAlloc(sizeof(*define) + nameLength + 1);

    memset(define, 0, sizeof(*define));
    define->name = define->nameStorage;
    strcpy(define->name, token.string);
    PC_AddDefineToHash(define, source->defineHash);

    if (PC_ReadLine(source, &token) == qfalse) {
        return qtrue;
    }

    qboolean hasFormalParms =
        PC_WhiteSpaceBeforeToken(&token) == qfalse &&
        strcmp(token.string, "(") == 0 ? qtrue : qfalse;

    if (hasFormalParms != qfalse) {
        token_t *lastParm = NULL;
        if (PC_CheckTokenString(source, ")") == qfalse) {
            while (qtrue) {
                if (PC_ReadLine(source, &token) == qfalse) {
                    PC_SourceError(source, "expected define parameter");
                    return qfalse;
                }
                if (token.type != PC_TOKEN_TYPE_NAME) {
                    PC_SourceError(source, "invalid define parameter");
                    return qfalse;
                }
                if (PC_FindDefineParm(define, token.string) >= 0) {
                    PC_SourceError(source,
                                   "two the same define parameters");
                    return qfalse;
                }

                token_t *parm = PC_CopyToken(&token);
                PC_ClearTokenWhiteSpace(parm);
                parm->next = NULL;
                if (lastParm == NULL) {
                    define->parms = parm;
                } else {
                    lastParm->next = parm;
                }
                define->numParms++;

                if (PC_ReadLine(source, &token) == qfalse) {
                    PC_SourceError(source, "define parameters not terminated");
                    return qfalse;
                }
                if (strcmp(token.string, ")") == 0) {
                    break;
                }

                lastParm = parm;
                if (strcmp(token.string, ",") != 0) {
                    PC_SourceError(source, "define not terminated");
                    return qfalse;
                }
            }
        }

        if (PC_ReadLine(source, &token) == qfalse) {
            return qtrue;
        }
    }

    token_t *lastToken = NULL;
    do {
        token_t *copy = PC_CopyToken(&token);
        if (copy->type == PC_TOKEN_TYPE_NAME &&
            strcmp(copy->string, define->name) == 0) {
            PC_SourceError(source, "recursive define (removed recursion)");
        } else {
            PC_ClearTokenWhiteSpace(copy);
            copy->next = NULL;
            if (lastToken == NULL) {
                define->tokens = copy;
            } else {
                lastToken->next = copy;
            }
            lastToken = copy;
        }
    } while (PC_ReadLine(source, &token) != qfalse);

    if (lastToken == NULL ||
        (strcmp(define->tokens->string, "##") != 0 &&
         strcmp(lastToken->string, "##") != 0)) {
        return qtrue;
    }

    PC_SourceError(source, "define with misplaced ##");
    return qfalse;
}

define_t *PC_DefineFromString(const char *string)
{
    PC_InitTokenHeap();

    script_t *script =
        LoadScriptMemory(string, strlen(string), "*extern");

    source_t source;
    memset(&source, 0, sizeof(source));
    strncpy(source.filename, "*extern", sizeof(source.filename));
    source.scriptStack = script;
    source.defineHash = Com_ZoneDebugAllocClear(CODUO_PC_DEFINE_HASH_TABLE_SIZE);

    qboolean parsed = PC_Directive_define(&source);

    while (source.tokens != NULL) {
        token_t *token = source.tokens;
        source.tokens = source.tokens->next;
        PC_FreeToken(token);
    }

    define_t *define = NULL;
    for (int32_t bucket = 0; bucket < CODUO_PC_DEFINE_HASH_BUCKET_COUNT;
         ++bucket) {
        if (source.defineHash[bucket] != NULL) {
            define = source.defineHash[bucket];
            break;
        }
    }

    Com_DebugFree(source.defineHash);
    FreeScript(script);

    if (parsed == qfalse) {
        /* ORIGINAL_BINARY_BUG: stock tests source.defines even though the
         * parsed row was linked only through defineHash, so malformed external
         * definitions leak.  See
         * original-bugs/coduomp-precompiler-error-path-leaks.md. */
        if (source.defines != NULL && define != NULL) {
            PC_FreeDefine(define);
        }
        return NULL;
    }

    return define;
}

qboolean PC_AddDefineToSource(source_t *source,
                              const char *string)
{
    define_t *define = PC_DefineFromString(string);
    if (define != NULL) {
        PC_AddDefineToHash(define, source->defineHash);
    }

    return define != NULL ? qtrue : qfalse;
}

qboolean PC_AddGlobalDefine(const char *string)
{
    define_t *define = PC_DefineFromString(string);
    if (define != NULL) {
        define->next = pc_globalDefines;
        pc_globalDefines = define;
    }

    return define != NULL ? qtrue : qfalse;
}

qboolean PC_RemoveGlobalDefine(const char *name)
{
    define_t *define = PC_FindDefine(pc_globalDefines, name);
    if (define != NULL) {
        /* ORIGINAL_BINARY_BUG: stock frees the found row without unlinking it
         * from pc_globalDefines.  See
         * original-bugs/coduomp-precompiler-global-define-dangling.md. */
        PC_FreeDefine(define);
    }

    return define != NULL ? qtrue : qfalse;
}

void PC_RemoveAllGlobalDefines(void)
{
    while (pc_globalDefines != NULL) {
        define_t *define = pc_globalDefines;
        pc_globalDefines = pc_globalDefines->next;
        PC_FreeDefine(define);
    }
}

define_t *PC_CopyDefine(source_t *source,
                                 const define_t *define)
{
    (void)source;

    size_t nameLength = strlen(define->name);
    define_t *copy =
        Com_ZoneDebugAlloc(sizeof(*copy) + nameLength + 1);

    copy->name = copy->nameStorage;
    strcpy(copy->name, define->name);
    copy->flags = define->flags;
    copy->builtin = define->builtin;
    copy->numParms = define->numParms;
    copy->next = NULL;
    copy->hashNext = NULL;
    copy->tokens = NULL;

    token_t *lastToken = NULL;
    for (token_t *token = define->tokens; token != NULL;
         token = token->next) {
        token_t *tokenCopy = PC_CopyToken(token);
        tokenCopy->next = NULL;
        if (lastToken == NULL) {
            copy->tokens = tokenCopy;
        } else {
            lastToken->next = tokenCopy;
        }
        lastToken = tokenCopy;
    }

    copy->parms = NULL;
    token_t *lastParm = NULL;
    for (token_t *parm = define->parms; parm != NULL;
         parm = parm->next) {
        token_t *parmCopy = PC_CopyToken(parm);
        parmCopy->next = NULL;
        if (lastParm == NULL) {
            copy->parms = parmCopy;
        } else {
            lastParm->next = parmCopy;
        }
        lastParm = parmCopy;
    }

    return copy;
}

void PC_AddGlobalDefinesToSource(source_t *source)
{
    for (define_t *define = pc_globalDefines; define != NULL;
         define = define->next) {
        define_t *copy = PC_CopyDefine(source, define);
        PC_AddDefineToHash(copy, source->defineHash);
    }
}

qboolean PC_Directive_if_def(source_t *source, int32_t type)
{
    token_t token;

    if (PC_ReadLine(source, &token) == qfalse) {
        PC_SourceError(source, "#ifdef without name");
        return qfalse;
    }

    if (token.type != PC_TOKEN_TYPE_NAME) {
        PC_UnreadSourceToken(source, &token);
        PC_SourceError(source, "expected name after #ifdef, found %s",
                       token.string);
        return qfalse;
    }

    qboolean undefined =
        PC_FindHashedDefine(source->defineHash, token.string) == NULL ? qtrue
                                                                      : qfalse;
    qboolean ifndef = type != PC_INDENT_TYPE_IFDEF ? qtrue : qfalse;
    PC_PushIndent(source, type, undefined != ifndef ? qtrue : qfalse);

    return qtrue;
}

qboolean PC_Directive_ifdef(source_t *source)
{
    return PC_Directive_if_def(source, PC_INDENT_TYPE_IFDEF);
}

qboolean PC_Directive_ifndef(source_t *source)
{
    return PC_Directive_if_def(source, PC_INDENT_TYPE_IFNDEF);
}

qboolean PC_Directive_else(source_t *source)
{
    int32_t type;
    qboolean skip;

    PC_PopIndent(source, &type, &skip);
    if (type == PC_INDENT_TYPE_NONE) {
        PC_SourceError(source, "misplaced #else");
        return qfalse;
    }

    if (type == PC_INDENT_TYPE_ELSE) {
        PC_SourceError(source, "#else after #else");
        return qfalse;
    }

    PC_PushIndent(source, PC_INDENT_TYPE_ELSE,
                  skip == qfalse ? qtrue : qfalse);
    return qtrue;
}

qboolean PC_Directive_endif(source_t *source)
{
    int32_t type;
    qboolean skip;

    PC_PopIndent(source, &type, &skip);
    if (type == PC_INDENT_TYPE_NONE) {
        PC_SourceError(source, "misplaced #endif");
    }

    return type != PC_INDENT_TYPE_NONE ? qtrue : qfalse;
}

int32_t PC_OperatorPriority(int32_t operatorSubtype)
{
    switch (operatorSubtype) {
    case 5:
        return PC_OPERATOR_PRIORITY_LOGICAL_AND;
    case 6:
        return PC_OPERATOR_PRIORITY_LOGICAL_OR;
    case 7:
    case 8:
    case 37:
    case 38:
        return PC_OPERATOR_PRIORITY_RELATIONAL;
    case 9:
    case 10:
        return PC_OPERATOR_PRIORITY_EQUALITY;
    case 21:
    case 22:
        return PC_OPERATOR_PRIORITY_SHIFT;
    case 26:
    case 27:
    case 28:
        return PC_OPERATOR_PRIORITY_MULTIPLICATIVE;
    case 29:
    case 30:
        return PC_OPERATOR_PRIORITY_ADDITIVE;
    case 32:
        return PC_OPERATOR_PRIORITY_BITWISE_AND;
    case 33:
        return PC_OPERATOR_PRIORITY_BITWISE_OR;
    case 34:
        return PC_OPERATOR_PRIORITY_BITWISE_XOR;
    case 35:
    case 36:
        return PC_OPERATOR_PRIORITY_UNARY;
    case 42:
    case 43:
        return PC_OPERATOR_PRIORITY_TERNARY;
    default:
        return PC_OPERATOR_PRIORITY_NONE;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: field-based setup for tokens synthesized by directives. */
static void PC_InitSyntheticToken(source_t *source,
                                  token_t *token,
                                  const char *string,
                                  int32_t type,
                                  int32_t subtype)
{
    memset(token, 0, sizeof(*token));
    snprintf(token->string, sizeof(token->string), "%s", string);
    token->type = type;
    token->subtype = subtype;
    token->whitespaceStart = source->scriptStack->script_p;
    token->whitespaceEnd = source->scriptStack->script_p;
    token->line = source->scriptStack->line;
    token->linesCrossed = 0;
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe copy into the original 12-byte x87 slot. */
static void PC_SetSyntheticTokenFloat(token_t *token,
                                      long double value)
{
    memset(token->floatValue, 0, sizeof(token->floatValue));
#if EMULATE_X87
    /* caller passes a double promoted to long double; widen it into the
     * 80-bit token TBYTE exactly (double->80 is lossless). */
    coduo_engine_pc_store_token_float80(token->floatValue,
                                         x87f_load_f64((double)value));
#else
    size_t copySize = sizeof(value);

    if (copySize > sizeof(token->floatValue)) {
        copySize = sizeof(token->floatValue);
    }

    memcpy(token->floatValue, &value, copySize);
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: writes stock fildl/fstpt bytes for int tokens. */
static void PC_SetSyntheticTokenFloatFromInt32(token_t *token,
                                               int32_t value)
{
    memset(token->floatValue, 0, sizeof(token->floatValue));
    if (value == 0) {
        return;
    }

    uint16_t sign = 0;
    uint32_t magnitude = (uint32_t)value;
    if (value < 0) {
        sign = PC_X87_EXTENDED_SIGN_MASK;
        magnitude = 0u - magnitude;
    }

    int32_t topBit = 31;
    while (((magnitude >> topBit) & 1u) == 0u) {
        topBit--;
    }

    uint64_t significand =
        (uint64_t)magnitude << (PC_X87_EXTENDED_SIGNIFICAND_BYTES * 8 - 1 -
                                topBit);
    uint16_t exponent =
        sign | (uint16_t)(PC_X87_EXTENDED_EXPONENT_BIAS + topBit);

    for (int32_t index = 0; index < PC_X87_EXTENDED_SIGNIFICAND_BYTES;
         index++) {
        token->floatValue[index] = (uint8_t)(significand >> (index * 8));
    }
    token->floatValue[PC_X87_EXTENDED_EXPONENT_OFFSET] = (uint8_t)exponent;
    token->floatValue[PC_X87_EXTENDED_EXPONENT_OFFSET + 1] =
        (uint8_t)(exponent >> 8);
    (void)PC_X87_EXTENDED_TBYTE_SIZE;
}

/* NOT_FROM_ORIGINAL_SOURCE: host-safe read from the original 12-byte x87 slot. */
static long double PC_TokenFloatValue(const token_t *token)
{
#if EMULATE_X87
    /* token is an 80-bit TBYTE; the sole caller narrows to double, so fld
     * TBYTE then fstp QWORD (80->64 near-even) here matches stock exactly. */
    return (long double)x87f_store_f64(
        coduo_engine_pc_load_token_float80(token->floatValue));
#else
    long double value = 0.0L;
    size_t copySize = sizeof(value);

    if (copySize > sizeof(token->floatValue)) {
        copySize = sizeof(token->floatValue);
    }

    memcpy(&value, token->floatValue, copySize);
    return value;
#endif
}

/* NOT_FROM_ORIGINAL_SOURCE: append a copied token to a temporary eval list. */
static void PC_AppendEvalToken(token_t **firstToken,
                               token_t **lastToken,
                               const token_t *token)
{
    token_t *copy = PC_CopyToken(token);

    copy->next = NULL;
    if (*lastToken == NULL) {
        *firstToken = copy;
    } else {
        (*lastToken)->next = copy;
    }
    *lastToken = copy;
}

/* NOT_FROM_ORIGINAL_SOURCE: free a temporary eval token list. */
static void PC_FreeEvalTokens(token_t *token)
{
    while (token != NULL) {
        token_t *next = token->next;
        PC_FreeToken(token);
        token = next;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: names the eval-only floating operand restriction. */
static qboolean PC_EvalIllegalFloatOperator(int32_t subtype)
{
    return subtype == PC_PUNCTUATION_SUBTYPE_COMPLEMENT ||
           subtype == PC_PUNCTUATION_SUBTYPE_MODULO ||
           subtype == PC_PUNCTUATION_SUBTYPE_SHIFT_RIGHT ||
           subtype == PC_PUNCTUATION_SUBTYPE_SHIFT_LEFT ||
           subtype == PC_PUNCTUATION_SUBTYPE_AMPERSAND ||
           subtype == PC_PUNCTUATION_SUBTYPE_OR ||
           subtype == PC_PUNCTUATION_SUBTYPE_XOR ? qtrue : qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: classifies precompiler expression operators. */
static qboolean PC_EvalBinaryOperator(int32_t subtype)
{
    switch (subtype) {
    case PC_PUNCTUATION_SUBTYPE_AND_AND:
    case PC_PUNCTUATION_SUBTYPE_OR_OR:
    case PC_PUNCTUATION_SUBTYPE_GREATER_EQUAL:
    case PC_PUNCTUATION_SUBTYPE_LESS_EQUAL:
    case PC_PUNCTUATION_SUBTYPE_EQUAL:
    case PC_PUNCTUATION_SUBTYPE_NOT_EQUAL:
    case PC_PUNCTUATION_SUBTYPE_SHIFT_RIGHT:
    case PC_PUNCTUATION_SUBTYPE_SHIFT_LEFT:
    case PC_PUNCTUATION_SUBTYPE_MULTIPLY:
    case PC_PUNCTUATION_SUBTYPE_DIVIDE:
    case PC_PUNCTUATION_SUBTYPE_MODULO:
    case PC_PUNCTUATION_SUBTYPE_PLUS:
    case PC_PUNCTUATION_SUBTYPE_MINUS:
    case PC_PUNCTUATION_SUBTYPE_AMPERSAND:
    case PC_PUNCTUATION_SUBTYPE_OR:
    case PC_PUNCTUATION_SUBTYPE_XOR:
    case PC_PUNCTUATION_SUBTYPE_GREATER:
    case PC_PUNCTUATION_SUBTYPE_LESS:
    case PC_PUNCTUATION_SUBTYPE_COLON:
    case PC_PUNCTUATION_SUBTYPE_QUESTION:
        return qtrue;
    default:
        return qfalse;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: classifies unary precompiler operators. */
static qboolean PC_EvalUnaryOperator(int32_t subtype)
{
    return subtype == PC_PUNCTUATION_SUBTYPE_COMPLEMENT ||
           subtype == PC_PUNCTUATION_SUBTYPE_NOT ? qtrue : qfalse;
}

qboolean PC_EvaluateTokens(source_t *source,
                      token_t *tokens,
                      int32_t *intValue,
                      double *floatValue,
                      qboolean integerEval)
{
    value_t values[PC_EVAL_MAX_VALUES];
    operator_t operators[PC_EVAL_MAX_OPERATORS];
    int32_t valueCount = 0;
    int32_t operatorCount = 0;
    int32_t parentheses = 0;
    qboolean hasValue = qfalse;
    qboolean negative = qfalse;
    qboolean parseError = qfalse;
    value_t *firstValue = NULL;
    value_t *lastValue = NULL;
    operator_t *firstOperator = NULL;
    operator_t *lastOperator = NULL;
    qboolean ternaryActive = qfalse;
    int32_t ternaryIntValue = 0;
    double ternaryFloatValue = 0.0;

    if (intValue != NULL) {
        *intValue = 0;
    }
    if (floatValue != NULL) {
        *floatValue = 0.0;
    }

    for (token_t *token = tokens; token != NULL;
         token = token->next) {
        if (token->type == PC_TOKEN_TYPE_NAME) {
            if (hasValue != qfalse || negative != qfalse) {
                PC_SourceError(source, "syntax error in #if/#elif");
                parseError = qtrue;
                break;
            }

            if (strcmp(token->string, "defined") != 0) {
                PC_SourceError(source, "undefined name %s in #if/#elif",
                               token->string);
                parseError = qtrue;
                break;
            }

            qboolean parenthesized = qfalse;
            token = token->next;
            /* ORIGINAL_BINARY_BUG: stock calls strcmp before proving the token
             * after "defined" exists.  See
             * original-bugs/coduomp-precompiler-evaluator-malformed-expression-crashes.md. */
            if (strcmp(token->string, "(") == 0) {
                parenthesized = qtrue;
                token = token->next;
            }

            if (token == NULL || token->type != PC_TOKEN_TYPE_NAME) {
                PC_SourceError(source, "defined without name in #if/#elif");
                parseError = qtrue;
                break;
            }

            if (valueCount >= PC_EVAL_MAX_VALUES) {
                PC_SourceError(source, "out of value space\n");
                parseError = qtrue;
                break;
            }

            value_t *value = &values[valueCount++];
            memset(value, 0, sizeof(*value));
            value->intValue =
                PC_FindHashedDefine(source->defineHash, token->string) != NULL
                    ? 1
                    : 0;
            value->floatValue = value->intValue != 0 ? 1.0 : 0.0;
            value->parentheses = parentheses;
            value->prev = lastValue;
            if (lastValue == NULL) {
                firstValue = value;
            } else {
                lastValue->next = value;
            }
            lastValue = value;

            if (parenthesized != qfalse) {
                token = token->next;
                if (token == NULL || strcmp(token->string, ")") != 0) {
                    PC_SourceError(source,
                                   "defined without ) in #if/#elif");
                    parseError = qtrue;
                    break;
                }
            }

            hasValue = qtrue;
            continue;
        }

        if (token->type == PC_TOKEN_TYPE_NUMBER) {
            if (hasValue != qfalse) {
                PC_SourceError(source, "syntax error in #if/#elif");
                parseError = qtrue;
                break;
            }

            if (valueCount >= PC_EVAL_MAX_VALUES) {
                PC_SourceError(source, "out of value space\n");
                parseError = qtrue;
                break;
            }

            value_t *value = &values[valueCount++];
            memset(value, 0, sizeof(*value));
            value->intValue = token->intValue;
            value->floatValue = (double)PC_TokenFloatValue(token);
            if (negative != qfalse) {
                value->intValue = -value->intValue;
                value->floatValue = -value->floatValue;
            }
            value->parentheses = parentheses;
            value->prev = lastValue;
            if (lastValue == NULL) {
                firstValue = value;
            } else {
                lastValue->next = value;
            }
            lastValue = value;
            hasValue = qtrue;
            negative = qfalse;
            continue;
        }

        if (token->type != PC_TOKEN_TYPE_PUNCTUATION) {
            PC_SourceError(source, "unknown %s in #if/#elif",
                           token->string);
            parseError = qtrue;
            break;
        }

        if (negative != qfalse) {
            PC_SourceError(source, "misplaced minus sign in #if/#elif");
            parseError = qtrue;
            break;
        }

        if (token->subtype == PC_PUNCTUATION_SUBTYPE_OPEN_PAREN) {
            parentheses++;
            continue;
        }

        if (token->subtype == PC_PUNCTUATION_SUBTYPE_CLOSE_PAREN) {
            parentheses--;
            if (parentheses < 0) {
                PC_SourceError(source, "too many ) in #if/#elsif");
                parseError = qtrue;
                break;
            }
            continue;
        }

        if (integerEval == qfalse &&
            PC_EvalIllegalFloatOperator(token->subtype) != qfalse) {
            PC_SourceError(source,
                           "illigal operator %s on floating point operands\n",
                           token->string);
            parseError = qtrue;
            break;
        }

        if (PC_EvalBinaryOperator(token->subtype) != qfalse) {
            if (hasValue == qfalse) {
                if (token->subtype == PC_PUNCTUATION_SUBTYPE_MINUS) {
                    negative = qtrue;
                    continue;
                }

                PC_SourceError(source,
                               "operator %s after operator in #if/#elif",
                               token->string);
                parseError = qtrue;
                break;
            }
        } else if (token->subtype == PC_PUNCTUATION_SUBTYPE_INCREMENT ||
                   token->subtype == PC_PUNCTUATION_SUBTYPE_DECREMENT) {
            /* ORIGINAL_BINARY_BUG: stock reports ++/-- but does not set
             * parseError, so reduction continues with the unsupported
             * operator.  See
             * original-bugs/coduomp-precompiler-evaluator-malformed-expression-crashes.md. */
            PC_SourceError(source, "++ or -- used in #if/#elif");
        } else if (PC_EvalUnaryOperator(token->subtype) != qfalse) {
            if (hasValue != qfalse) {
                PC_SourceError(source, "! or ~ after value in #if/#elif");
                parseError = qtrue;
                break;
            }
        } else {
            PC_SourceError(source, "invalid operator %s in #if/#elif",
                           token->string);
            parseError = qtrue;
            break;
        }

        if (operatorCount >= PC_EVAL_MAX_OPERATORS) {
            PC_SourceError(source, "out of operator space\n");
            parseError = qtrue;
            break;
        }

        operator_t *op = &operators[operatorCount++];
        memset(op, 0, sizeof(*op));
        op->operator = token->subtype;
        op->priority = PC_OperatorPriority(token->subtype);
        op->parentheses = parentheses;
        op->prev = lastOperator;
        if (lastOperator == NULL) {
            firstOperator = op;
        } else {
            lastOperator->next = op;
        }
        lastOperator = op;
        hasValue = qfalse;
    }

    if (parseError == qfalse) {
        if (hasValue == qfalse) {
            PC_SourceError(source, "trailing operator in #if/#elif");
            parseError = qtrue;
        } else if (parentheses != 0) {
            PC_SourceError(source, "too many ( in #if/#elif");
            parseError = qtrue;
        }
    }

    while (parseError == qfalse && firstOperator != NULL) {
        value_t *value = firstValue;
        operator_t *op = firstOperator;

        while (op->next != NULL &&
               (op->next->parentheses > op->parentheses ||
                (op->next->parentheses == op->parentheses &&
                 op->next->priority > op->priority))) {
            if (op->operator != PC_PUNCTUATION_SUBTYPE_NOT &&
                op->operator != PC_PUNCTUATION_SUBTYPE_COMPLEMENT) {
                value = value->next;
            }
            op = op->next;
            if (value == NULL) {
                break;
            }
        }

        if (value == NULL) {
            PC_SourceError(source, "mising values in #if/#elif");
            parseError = qtrue;
            break;
        }

        value_t *rhs = value->next;
        switch (op->operator) {
        case PC_PUNCTUATION_SUBTYPE_AND_AND:
            value->intValue =
                value->intValue != 0 && rhs->intValue != 0 ? 1 : 0;
            value->floatValue =
                value->floatValue != 0.0 && rhs->floatValue != 0.0 ? 1.0
                                                                   : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_OR_OR:
            value->intValue =
                value->intValue != 0 || rhs->intValue != 0 ? 1 : 0;
            value->floatValue =
                value->floatValue != 0.0 || rhs->floatValue != 0.0 ? 1.0
                                                                   : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_GREATER_EQUAL:
            value->intValue = value->intValue >= rhs->intValue ? 1 : 0;
            value->floatValue =
                value->floatValue >= rhs->floatValue ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_LESS_EQUAL:
            value->intValue = value->intValue <= rhs->intValue ? 1 : 0;
            value->floatValue =
                value->floatValue <= rhs->floatValue ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_EQUAL:
            value->intValue = value->intValue == rhs->intValue ? 1 : 0;
            value->floatValue =
                value->floatValue == rhs->floatValue ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_NOT_EQUAL:
            value->intValue = value->intValue != rhs->intValue ? 1 : 0;
            value->floatValue =
                value->floatValue != rhs->floatValue ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_SHIFT_RIGHT:
            value->intValue = coduo_int32_from_bits(coduo_int32_sar_bits(
                coduo_int32_bits(value->intValue),
                (unsigned int)rhs->intValue & 31U));
            break;
        case PC_PUNCTUATION_SUBTYPE_SHIFT_LEFT:
            value->intValue = coduo_int32_from_bits(
                coduo_int32_bits(value->intValue) <<
                ((unsigned int)rhs->intValue & 31U));
            break;
        case PC_PUNCTUATION_SUBTYPE_MULTIPLY:
            value->intValue *= rhs->intValue;
            value->floatValue *= rhs->floatValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_DIVIDE:
            if (rhs->intValue == 0 || rhs->floatValue == 0.0) {
                PC_SourceError(source, "divide by zero in #if/#elif\n");
                parseError = qtrue;
                break;
            }
            /* ORIGINAL_BINARY_BUG: the stock IDIV path does not reject
             * INT32_MIN / -1.  See
             * original-bugs/coduomp-precompiler-integer-division-overflow.md. */
            value->intValue /= rhs->intValue;
            value->floatValue /= rhs->floatValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_MODULO:
            if (rhs->intValue == 0) {
                PC_SourceError(source, "divide by zero in #if/#elif\n");
                parseError = qtrue;
                break;
            }
            /* ORIGINAL_BINARY_BUG: the stock remainder IDIV has the same
             * INT32_MIN / -1 processor exception.  See
             * original-bugs/coduomp-precompiler-integer-division-overflow.md. */
            value->intValue %= rhs->intValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_PLUS:
            value->intValue += rhs->intValue;
            value->floatValue += rhs->floatValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_MINUS:
            value->intValue -= rhs->intValue;
            value->floatValue -= rhs->floatValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_AMPERSAND:
            value->intValue &= rhs->intValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_OR:
            value->intValue |= rhs->intValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_XOR:
            value->intValue ^= rhs->intValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_COMPLEMENT:
            value->intValue = ~value->intValue;
            break;
        case PC_PUNCTUATION_SUBTYPE_NOT:
            value->intValue = value->intValue == 0 ? 1 : 0;
            value->floatValue = value->floatValue == 0.0 ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_GREATER:
            value->intValue = value->intValue > rhs->intValue ? 1 : 0;
            value->floatValue =
                value->floatValue > rhs->floatValue ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_LESS:
            value->intValue = value->intValue < rhs->intValue ? 1 : 0;
            value->floatValue =
                value->floatValue < rhs->floatValue ? 1.0 : 0.0;
            break;
        case PC_PUNCTUATION_SUBTYPE_COLON:
            if (ternaryActive == qfalse) {
                PC_SourceError(source, ": without ? in #if/#elif");
                parseError = qtrue;
                break;
            }
            if (integerEval == qfalse) {
                if (ternaryFloatValue == 0.0) {
                    value->floatValue = rhs->floatValue;
                }
            } else if (ternaryIntValue == 0) {
                value->intValue = rhs->intValue;
            }
            ternaryActive = qfalse;
            break;
        case PC_PUNCTUATION_SUBTYPE_QUESTION:
            if (ternaryActive != qfalse) {
                PC_SourceError(source, "? after ? in #if/#elif");
                parseError = qtrue;
                break;
            }
            ternaryIntValue = value->intValue;
            ternaryFloatValue = value->floatValue;
            ternaryActive = qtrue;
            break;
        default:
            break;
        }

        if (parseError != qfalse) {
            break;
        }

        if (op->operator != PC_PUNCTUATION_SUBTYPE_NOT &&
            op->operator != PC_PUNCTUATION_SUBTYPE_COMPLEMENT) {
            value_t *removeValue =
                op->operator == PC_PUNCTUATION_SUBTYPE_QUESTION ? value : rhs;

            if (removeValue->prev == NULL) {
                firstValue = removeValue->next;
            } else {
                removeValue->prev->next = removeValue->next;
            }
            if (removeValue->next == NULL) {
                lastValue = removeValue->prev;
            } else {
                removeValue->next->prev = removeValue->prev;
            }
        }

        if (op->prev == NULL) {
            firstOperator = op->next;
        } else {
            op->prev->next = op->next;
        }
        if (op->next == NULL) {
            lastOperator = op->prev;
        } else {
            op->next->prev = op->prev;
        }
        (void)lastOperator;
    }

    if (parseError == qfalse) {
        if (intValue != NULL) {
            if (firstValue != NULL) {
                *intValue = firstValue->intValue;
            }
        }
        if (floatValue != NULL) {
            if (firstValue != NULL) {
                *floatValue = firstValue->floatValue;
            }
        }
        return qtrue;
    }

    if (intValue != NULL) {
        *intValue = 0;
    }
    if (floatValue != NULL) {
        *floatValue = 0.0;
    }
    return qfalse;
}

qboolean PC_Evaluate(source_t *source, int32_t *intValue,
                      double *floatValue, qboolean integerEval)
{
    token_t token;
    /* ORIGINAL_BINARY_BUG: stock frees this copied list only after successful
     * evaluation; parse, expansion, and evaluation failures abandon it.  See
     * original-bugs/coduomp-precompiler-error-path-leaks.md. */
    token_t *firstToken = NULL;
    token_t *lastToken = NULL;
    qboolean definedName = qfalse;

    if (intValue != NULL) {
        *intValue = 0;
    }
    if (floatValue != NULL) {
        *floatValue = 0.0;
    }

    if (PC_ReadLine(source, &token) == qfalse) {
        PC_SourceError(source, "no value after #if/#elif");
        return qfalse;
    }

    do {
        if (token.type == PC_TOKEN_TYPE_NAME) {
            if (definedName != qfalse) {
                definedName = qfalse;
                PC_AppendEvalToken(&firstToken, &lastToken, &token);
            } else if (strcmp(token.string, "defined") == 0) {
                definedName = qtrue;
                PC_AppendEvalToken(&firstToken, &lastToken, &token);
            } else {
                define_t *define =
                    PC_FindHashedDefine(source->defineHash, token.string);
                if (define == NULL) {
                    PC_SourceError(source, "can't evaluate %s, not defined",
                                   token.string);
                    return qfalse;
                }

                if (PC_ExpandDefineIntoSource(source, &token, define) ==
                    qfalse) {
                    return qfalse;
                }
            }
        } else if (token.type == PC_TOKEN_TYPE_NUMBER ||
                   token.type == PC_TOKEN_TYPE_PUNCTUATION) {
            PC_AppendEvalToken(&firstToken, &lastToken, &token);
        } else {
            PC_SourceError(source, "can't evaluate %s", token.string);
            return qfalse;
        }
    } while (PC_ReadLine(source, &token) != qfalse);

    qboolean result =
        PC_EvaluateTokens(source, firstToken, intValue, floatValue, integerEval);
    if (result != qfalse) {
        PC_FreeEvalTokens(firstToken);
    }
    return result;
}

qboolean PC_DollarEvaluate(source_t *source, int32_t *intValue,
                      double *floatValue, qboolean integerEval)
{
    token_t token;
    /* ORIGINAL_BINARY_BUG: stock frees this copied list only after successful
     * evaluation; malformed and failed paths abandon it.  See
     * original-bugs/coduomp-precompiler-error-path-leaks.md. */
    token_t *firstToken = NULL;
    token_t *lastToken = NULL;
    int32_t parentheses = 1;
    qboolean definedName = qfalse;

    if (intValue != NULL) {
        *intValue = 0;
    }
    if (floatValue != NULL) {
        *floatValue = 0.0;
    }

    if (PC_ReadSourceToken(source, &token) == qfalse) {
        PC_SourceError(source, "no leading ( after $evalint/$evalfloat");
        return qfalse;
    }

    /* ORIGINAL_BINARY_BUG: stock discards the first token without checking it
     * is "(", and reaches evaluation after EOF even if parentheses never
     * close.  See
     * original-bugs/coduomp-precompiler-evaluator-malformed-expression-crashes.md. */
    if (PC_ReadSourceToken(source, &token) == qfalse) {
        PC_SourceError(source, "nothing to evaluate");
        return qfalse;
    }

    do {
        if (token.type == PC_TOKEN_TYPE_NAME) {
            if (definedName == qfalse) {
                if (strcmp(token.string, "defined") == 0) {
                    definedName = qtrue;
                    PC_AppendEvalToken(&firstToken, &lastToken, &token);
                } else {
                    define_t *define =
                        PC_FindHashedDefine(source->defineHash,
                                            token.string);
                    if (define == NULL) {
                        PC_SourceError(source,
                                       "can't evaluate %s, not defined",
                                       token.string);
                        return qfalse;
                    }
                    if (PC_ExpandDefineIntoSource(source, &token, define) ==
                        qfalse) {
                        return qfalse;
                    }
                }
            } else {
                definedName = qfalse;
                PC_AppendEvalToken(&firstToken, &lastToken, &token);
            }
        } else {
            if (token.type != PC_TOKEN_TYPE_NUMBER &&
                token.type != PC_TOKEN_TYPE_PUNCTUATION) {
                PC_SourceError(source, "can't evaluate %s", token.string);
                return qfalse;
            }

            if (strcmp(token.string, "(") == 0) {
                parentheses++;
            } else if (strcmp(token.string, ")") == 0) {
                parentheses--;
            }

            if (parentheses < 1) {
                break;
            }

            PC_AppendEvalToken(&firstToken, &lastToken, &token);
        }
    } while (PC_ReadSourceToken(source, &token) != qfalse);

    qboolean result =
        PC_EvaluateTokens(source, firstToken, intValue, floatValue, integerEval);
    if (result != qfalse) {
        PC_FreeEvalTokens(firstToken);
    }
    return result;
}

qboolean PC_Directive_elif(source_t *source)
{
    int32_t type;
    qboolean skip;
    int32_t value;

    PC_PopIndent(source, &type, &skip);
    if (type == PC_INDENT_TYPE_NONE || type == PC_INDENT_TYPE_ELSE) {
        PC_SourceError(source, "misplaced #elif");
        return qfalse;
    }

    if (PC_Evaluate(source, &value, NULL, qtrue) == qfalse) {
        return qfalse;
    }

    /* ORIGINAL_BINARY_BUG: stock ignores the popped skip state, so a true
     * #elif can activate after an earlier branch already ran.  See
     * original-bugs/coduomp-precompiler-elif-duplicate-active-branch.md. */
    PC_PushIndent(source, PC_INDENT_TYPE_ELIF,
                  value == 0 ? qtrue : qfalse);
    return qtrue;
}

qboolean PC_Directive_if(source_t *source)
{
    int32_t value;

    if (PC_Evaluate(source, &value, NULL, qtrue) == qfalse) {
        return qfalse;
    }

    PC_PushIndent(source, PC_INDENT_TYPE_IF,
                  value == 0 ? qtrue : qfalse);
    return qtrue;
}

qboolean PC_Directive_line(source_t *source)
{
    PC_SourceError(source, "#line directive not supported");
    return qfalse;
}

qboolean PC_Directive_error(source_t *source)
{
    token_t token;

    strcpy(token.string, "");
    PC_ReadSourceToken(source, &token);
    PC_SourceError(source, "#error directive: %s", token.string);
    return qfalse;
}

qboolean PC_Directive_pragma(source_t *source)
{
    token_t token;

    PC_SourceWarning(source, "#pragma directive not supported");
    while (PC_ReadLine(source, &token) != qfalse) {
    }

    return qtrue;
}

void UnreadSignToken(source_t *source)
{
    token_t token;

    strcpy(token.string, "-");
    token.type = PC_TOKEN_TYPE_PUNCTUATION;
    token.subtype = PC_PUNCTUATION_SUBTYPE_MINUS;
    token.whitespaceStart = source->scriptStack->script_p;
    token.whitespaceEnd = source->scriptStack->script_p;
    token.line = source->scriptStack->line;
    token.linesCrossed = 0;
    PC_UnreadSourceToken(source, &token);
}

qboolean PC_Directive_eval(source_t *source)
{
    token_t token;
    char text[PC_TOKEN_MAX_CHARS];
    int32_t value;
    int32_t absoluteValue;

    if (PC_Evaluate(source, &value, NULL, qtrue) == qfalse) {
        return qfalse;
    }

    /* ORIGINAL_BINARY_BUG: stock publishes this token without initializing
     * its intValue or floatValue payload.  See
     * original-bugs/coduomp-precompiler-eval-token-values-uninitialized.md. */
    absoluteValue = value < 0 ? -value : value;
    sprintf(text, "%d", absoluteValue);
    strcpy(token.string, text);
    token.type = PC_TOKEN_TYPE_NUMBER;
    token.subtype = PC_TOKEN_SUBTYPE_DECIMAL_INTEGER_LONG;
    token.whitespaceStart = source->scriptStack->script_p;
    token.whitespaceEnd = source->scriptStack->script_p;
    token.line = source->scriptStack->line;
    token.linesCrossed = 0;
    PC_UnreadSourceToken(source, &token);

    if (value < 0) {
        UnreadSignToken(source);
    }

    return qtrue;
}

qboolean PC_Directive_evalfloat(source_t *source)
{
    token_t token;
    double value;

    if (PC_Evaluate(source, NULL, &value, qfalse) == qfalse) {
        return qfalse;
    }

    /* ORIGINAL_BINARY_BUG: stock publishes this token without initializing
     * its intValue or floatValue payload.  See
     * original-bugs/coduomp-precompiler-eval-token-values-uninitialized.md. */
    sprintf(token.string, "%1.2f", fabs((double)value));
    token.type = PC_TOKEN_TYPE_NUMBER;
    token.subtype = PC_TOKEN_SUBTYPE_DECIMAL_FLOAT_LONG;
    token.whitespaceStart = source->scriptStack->script_p;
    token.whitespaceEnd = source->scriptStack->script_p;
    token.line = source->scriptStack->line;
    token.linesCrossed = 0;
    PC_UnreadSourceToken(source, &token);

    if (value < 0.0) {
        UnreadSignToken(source);
    }

    return qtrue;
}

qboolean PC_ReadDirective(source_t *source)
{
    token_t token;

    if (PC_ReadSourceToken(source, &token) == qfalse) {
        PC_SourceError(source, "found # without name");
        return qfalse;
    }

    if (token.linesCrossed >= 1) {
        PC_UnreadSourceToken(source, &token);
        PC_SourceError(source, "found # at end of line");
        return qfalse;
    }

    if (token.type == PC_TOKEN_TYPE_NAME) {
        if (strcmp(token.string, "if") == 0) {
            return PC_Directive_if(source);
        }
        if (strcmp(token.string, "ifdef") == 0) {
            return PC_Directive_ifdef(source);
        }
        if (strcmp(token.string, "ifndef") == 0) {
            return PC_Directive_ifndef(source);
        }
        if (strcmp(token.string, "elif") == 0) {
            return PC_Directive_elif(source);
        }
        if (strcmp(token.string, "else") == 0) {
            return PC_Directive_else(source);
        }
        if (strcmp(token.string, "endif") == 0) {
            return PC_Directive_endif(source);
        }
        if (strcmp(token.string, "include") == 0) {
            return PC_Directive_include(source);
        }
        if (strcmp(token.string, "define") == 0) {
            return PC_Directive_define(source);
        }
        if (strcmp(token.string, "undef") == 0) {
            return PC_Directive_undef(source);
        }
        if (strcmp(token.string, "line") == 0) {
            return PC_Directive_line(source);
        }
        if (strcmp(token.string, "error") == 0) {
            return PC_Directive_error(source);
        }
        if (strcmp(token.string, "pragma") == 0) {
            return PC_Directive_pragma(source);
        }
        if (strcmp(token.string, "eval") == 0) {
            return PC_Directive_eval(source);
        }
        if (strcmp(token.string, "evalfloat") == 0) {
            return PC_Directive_evalfloat(source);
        }
    }

    PC_SourceError(source, "unknown precompiler directive %s", token.string);
    return qfalse;
}

qboolean PC_DollarDirective_evalint(source_t *source)
{
    token_t token;
    char text[PC_TOKEN_MAX_CHARS];
    int32_t value;
    uint32_t absoluteBits;
    int32_t printableValue;

    if (PC_DollarEvaluate(source, &value, NULL, qtrue) == qfalse) {
        return qfalse;
    }

    absoluteBits = (uint32_t)value;
    if (value < 0) {
        absoluteBits = 0u - absoluteBits;
    }
    memcpy(&printableValue, &absoluteBits, sizeof(printableValue));
    sprintf(text, "%d", printableValue);
    PC_InitSyntheticToken(source, &token, text, PC_TOKEN_TYPE_NUMBER,
                          PC_TOKEN_SUBTYPE_DECIMAL_INTEGER_LONG);
    token.intValue = value;
    PC_SetSyntheticTokenFloatFromInt32(&token, value);
    PC_UnreadSourceToken(source, &token);

    if (value < 0) {
        UnreadSignToken(source);
    }

    return qtrue;
}

qboolean PC_DollarDirective_evalfloat(source_t *source)
{
    token_t token;
    char text[PC_TOKEN_MAX_CHARS];
    double value;

    if (PC_DollarEvaluate(source, NULL, &value, qfalse) == qfalse) {
        return qfalse;
    }

    sprintf(text, "%1.2f", fabs((double)value));
    PC_InitSyntheticToken(source, &token, text, PC_TOKEN_TYPE_NUMBER,
                          PC_TOKEN_SUBTYPE_DECIMAL_FLOAT_LONG);
    /* 0x807c572: fistp QWORD + low dword = unsigned-int conversion,
     * not the fistp DWORD a direct (int32_t) cast would emit. */
#if defined(__x86_64__)
    token.intValue = (int32_t)(uint32_t)
        CODUO_X87_TRUNCATE_I64((long double)value);
#else
    token.intValue = (int32_t)(uint32_t)value;
#endif
    PC_SetSyntheticTokenFloat(&token, value);
    PC_UnreadSourceToken(source, &token);

    if (value < 0.0) {
        UnreadSignToken(source);
    }

    return qtrue;
}

qboolean PC_ReadDollarDirective(source_t *source)
{
    token_t token;

    if (PC_ReadSourceToken(source, &token) == qfalse) {
        PC_SourceError(source, "found $ without name");
        return qfalse;
    }

    if (token.linesCrossed >= 1) {
        PC_UnreadSourceToken(source, &token);
        PC_SourceError(source, "found $ at end of line");
        return qfalse;
    }

    if (token.type == PC_TOKEN_TYPE_NAME) {
        if (strcmp(token.string, "evalint") == 0) {
            return PC_DollarDirective_evalint(source);
        }
        if (strcmp(token.string, "evalfloat") == 0) {
            return PC_DollarDirective_evalfloat(source);
        }
    }

    PC_UnreadSourceToken(source, &token);
    PC_SourceError(source, "unknown precompiler directive %s", token.string);
    return qfalse;
}

qboolean PC_ReadToken(source_t *source, token_t *token)
{
    while (qtrue) {
        if (PC_ReadSourceToken(source, token) == qfalse) {
            return qfalse;
        }

        if (token->type == PC_TOKEN_TYPE_PUNCTUATION && token->string[0] == '#') {
            if (PC_ReadDirective(source) == qfalse) {
                return qfalse;
            }
            continue;
        }

        if (token->type == PC_TOKEN_TYPE_PUNCTUATION && token->string[0] == '$') {
            if (PC_ReadDollarDirective(source) == qfalse) {
                return qfalse;
            }
            continue;
        }

        if (token->type == PC_TOKEN_TYPE_STRING) {
            token_t next;
            if (PC_ReadToken(source, &next) != qfalse) {
                if (next.type == PC_TOKEN_TYPE_STRING) {
                    size_t tokenLength = strlen(token->string);
                    size_t nextLength = strlen(next.string);

                    token->string[tokenLength - 1] = '\0';
                    if (PC_TOKEN_MAX_CHARS < tokenLength + nextLength) {
                        PC_SourceError(source,
                                       "string longer than MAX_TOKEN %d\n",
                                       PC_TOKEN_MAX_CHARS);
                        return qfalse;
                    }
                    strcat(token->string, next.string + 1);
                } else {
                    PC_UnreadTokenValue(source, &next);
                }
            }
        }

        if (source->skip != 0) {
            continue;
        }

        if (token->type == PC_TOKEN_TYPE_NAME) {
            define_t *define =
                PC_FindHashedDefine(source->defineHash, token->string);
            if (define != NULL) {
                if (PC_ExpandDefineIntoSource(source, token, define) ==
                    qfalse) {
                    return qfalse;
                }
                continue;
            }
        }

        memcpy(&source->token, token, sizeof(source->token));
        return qtrue;
    }
}

qboolean PC_ExpectTokenString(source_t *source, const char *string)
{
    token_t token;

    if (PC_ReadToken(source, &token) == qfalse) {
        PC_SourceError(source, "couldn't find expected %s", string);
        return qfalse;
    }

    if (strcmp(token.string, string) == 0) {
        return qtrue;
    }

    PC_SourceError(source, "expected %s, found %s", string, token.string);
    return qfalse;
}

qboolean PC_ExpectTokenType(source_t *source,
                            int32_t type,
                            int32_t subtype,
                            token_t *token)
{
    char expected[PC_TOKEN_MAX_CHARS];

    if (PC_ReadToken(source, token) == qfalse) {
        PC_SourceError(source, "couldn't read expected token");
        return qfalse;
    }

    if (token->type == type) {
        if (token->type == PC_TOKEN_TYPE_NUMBER &&
            (token->subtype & subtype) != subtype) {
            expected[0] = '\0';
            if ((subtype & PC_TOKEN_SUBTYPE_DECIMAL) != 0) {
                strcpy(expected, "decimal");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_HEX) != 0) {
                strcpy(expected, "hex");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_OCTAL) != 0) {
                strcpy(expected, "octal");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_BINARY) != 0) {
                strcpy(expected, "binary");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_LONG) != 0) {
                strcat(expected, " long");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_UNSIGNED) != 0) {
                strcat(expected, " unsigned");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_FLOAT) != 0) {
                strcat(expected, " float");
            }
            if ((subtype & PC_TOKEN_SUBTYPE_INTEGER) != 0) {
                strcat(expected, " integer");
            }

            PC_SourceError(source, "expected %s, found %s", expected,
                           token->string);
            return qfalse;
        }

        if (token->type == PC_TOKEN_TYPE_PUNCTUATION &&
            token->subtype != subtype) {
            PC_SourceError(source, "found %s", token->string);
            return qfalse;
        }

        return qtrue;
    }

    strcpy(expected, "");
    if (type == PC_TOKEN_TYPE_STRING) {
        strcpy(expected, "string");
    }
    if (type == PC_TOKEN_TYPE_LITERAL) {
        strcpy(expected, "literal");
    }
    if (type == PC_TOKEN_TYPE_NUMBER) {
        strcpy(expected, "number");
    }
    if (type == PC_TOKEN_TYPE_NAME) {
        strcpy(expected, "name");
    }
    if (type == PC_TOKEN_TYPE_PUNCTUATION) {
        strcpy(expected, "punctuation");
    }

    PC_SourceError(source, "expected a %s, found %s", expected,
                   token->string);
    return qfalse;
}

qboolean PC_ExpectAnyToken(source_t *source, token_t *token)
{
    qboolean result = PC_ReadToken(source, token);
    if (result == qfalse) {
        PC_SourceError(source, "couldn't read expected token");
    }

    return result != qfalse ? qtrue : qfalse;
}

qboolean PC_CheckTokenString(source_t *source, const char *string)
{
    token_t token;

    if (PC_ReadToken(source, &token) == qfalse) {
        return qfalse;
    }

    if (strcmp(token.string, string) == 0) {
        return qtrue;
    }

    PC_UnreadSourceToken(source, &token);
    return qfalse;
}

qboolean PC_CheckTokenType(source_t *source,
                           int32_t type,
                           int32_t subtype,
                           token_t *token)
{
    token_t readToken;

    if (PC_ReadToken(source, &readToken) == qfalse) {
        return qfalse;
    }

    if (readToken.type == type &&
        (subtype & readToken.subtype) == subtype) {
        memcpy(token, &readToken, sizeof(*token));
        return qtrue;
    }

    PC_UnreadSourceToken(source, &readToken);
    return qfalse;
}

qboolean PC_SkipUntilString(source_t *source, const char *string)
{
    token_t token;

    do {
        if (PC_ReadToken(source, &token) == qfalse) {
            return qfalse;
        }
    } while (strcmp(token.string, string) != 0);

    return qtrue;
}

void PC_UnreadToken(source_t *source)
{
    PC_UnreadSourceToken(source, &source->token);
}

void PC_UnreadTokenValue(source_t *source, const token_t *token)
{
    PC_UnreadSourceToken(source, token);
}

void PC_SetIncludePath(source_t *source, const char *path)
{
    /* ORIGINAL_BINARY_BUG: the 0x40-byte copy may be unterminated, an empty
     * path reads length-1, and a full path may overflow when "/" is appended.
     * See original-bugs/coduomp-precompiler-include-base-path-bounds.md. */
    strncpy(source->includePath, path, sizeof(source->includePath));

    size_t length = strlen(source->includePath);
    char last = source->includePath[length - 1];
    if (last != '\\' && last != '/') {
        strcat(source->includePath, "/");
    }
}

void PC_SetPunctuations(source_t *source,
                  punctuation_t *punctuations)
{
    /* ORIGINAL_BINARY_BUG: stock stores this field but tokenization reads only
     * each script's punctuation table.  See
     * original-bugs/coduomp-precompiler-custom-punctuation-no-effect.md. */
    source->punctuations = punctuations;
}

script_t *LoadScriptFile(const char *filename)
{
    char path[CODUO_PC_SCRIPT_FILENAME_SIZE];
    int32_t handle;

    if (pc_baseFolder[0] == '\0') {
        Com_sprintf(path, sizeof(path), "%s", filename);
    } else {
        Com_sprintf(path, sizeof(path), "%s/%s", pc_baseFolder, filename);
    }

    int fileLength = FS_FOpenFileByMode(path, &handle, CODUO_FS_READ);
    if (handle == 0) {
        return NULL;
    }

    script_t *script =
        Com_ZoneDebugAlloc(sizeof(*script) + (size_t)fileLength + 1);
    memset(script, 0, sizeof(*script));
    /* ORIGINAL_BINARY_BUG: stock strcpy can overrun the script's 0x40-byte
     * filename field.  See
     * original-bugs/coduomp-precompiler-script-name-overflow.md. */
    strcpy(script->filename, filename);
    script->buffer = (char *)(script + 1);
    script->buffer[fileLength] = '\0';
    script->length = fileLength;
    script->script_p = script->buffer;
    script->lastscript_p = script->buffer;
    script->end_p = script->buffer + fileLength;
    script->tokenavailable = PC_SCRIPT_NOT_AVAILABLE;
    script->line = PC_SCRIPT_INITIAL_LINE;
    script->lastline = PC_SCRIPT_INITIAL_LINE;

    PS_CreatePunctuationTable(script, NULL);
    FS_Read(script->buffer, fileLength, handle);
    FS_FCloseFile(handle);
    Com_Compress(script->buffer);
    script->length = (int32_t)strlen(script->buffer);

    return script;
}

script_t *LoadScriptMemory(const char *ptr, size_t length,
                                    const char *name)
{
    script_t *script =
        Com_ZoneDebugAllocClear(sizeof(*script) + length + 1);

    /* ORIGINAL_BINARY_BUG: stock strcpy can overrun the script's 0x40-byte
     * filename field.  See
     * original-bugs/coduomp-precompiler-script-name-overflow.md. */
    strcpy(script->filename, name);
    script->buffer = (char *)(script + 1);
    script->buffer[length] = '\0';
    script->length = (int32_t)length;
    script->script_p = script->buffer;
    script->lastscript_p = script->buffer;
    script->end_p = script->buffer + length;
    script->tokenavailable = PC_SCRIPT_NOT_AVAILABLE;
    script->line = PC_SCRIPT_INITIAL_LINE;
    script->lastline = PC_SCRIPT_INITIAL_LINE;

    PS_CreatePunctuationTable(script, NULL);
    memcpy(script->buffer, ptr, length);

    return script;
}

void FreeScript(script_t *script)
{
    if (script->punctuationtable != NULL) {
        Com_DebugFree(script->punctuationtable);
    }

    Com_DebugFree(script);
}

void PS_SetBaseFolder(const char *path)
{
    /* ORIGINAL_BINARY_BUG: stock supplies caller-controlled path text as the
     * Com_sprintf format with no matching variadic arguments.  See
     * original-bugs/coduomp-precompiler-base-folder-format-string.md. */
    Com_sprintf(pc_baseFolder, sizeof(pc_baseFolder), path);
}

source_t *LoadSourceFile(const char *filename)
{
    PC_InitTokenHeap();

    script_t *script = LoadScriptFile(filename);
    if (script == NULL) {
        return NULL;
    }

    script->next = NULL;

    /* The original i386 allocation is 0x4c8 == sizeof(source_t). Native host
     * pointers widen the parser record, so retaining the literal binary size
     * would under-allocate it even though the parser behavior is unchanged. */
    source_t *source = Com_ZoneDebugAlloc(sizeof(*source));
    memset(source, 0, sizeof(*source));
    /* ORIGINAL_BINARY_BUG: a name of at least 0x40 bytes replaces the whole
     * zeroed field without a terminator.  See
     * original-bugs/coduomp-precompiler-source-name-unterminated.md. */
    strncpy(source->filename, filename, sizeof(source->filename));
    source->scriptStack = script;
    source->defineHash =
        Com_ZoneDebugAllocClear(CODUO_PC_DEFINE_HASH_TABLE_SIZE);
    PC_AddGlobalDefinesToSource(source);

    return source;
}

source_t *LoadSourceMemory(const char *ptr, size_t length,
                                    const char *name)
{
    PC_InitTokenHeap();

    script_t *script = LoadScriptMemory(ptr, length, name);
    if (script == NULL) {
        return NULL;
    }

    script->next = NULL;

    /* The original i386 allocation is 0x4c8 == sizeof(source_t). Native host
     * pointers widen the parser record, so retaining the literal binary size
     * would under-allocate it even though the parser behavior is unchanged. */
    source_t *source = Com_ZoneDebugAlloc(sizeof(*source));
    memset(source, 0, sizeof(*source));
    /* ORIGINAL_BINARY_BUG: a name of at least 0x40 bytes replaces the whole
     * zeroed field without a terminator.  See
     * original-bugs/coduomp-precompiler-source-name-unterminated.md. */
    strncpy(source->filename, name, sizeof(source->filename));
    source->scriptStack = script;
    source->defineHash =
        Com_ZoneDebugAllocClear(CODUO_PC_DEFINE_HASH_TABLE_SIZE);
    PC_AddGlobalDefinesToSource(source);

    return source;
}

void FreeSource(source_t *source)
{
    while (source->scriptStack != NULL) {
        script_t *script = source->scriptStack;
        source->scriptStack = source->scriptStack->next;
        FreeScript(script);
    }

    while (source->tokens != NULL) {
        token_t *token = source->tokens;
        source->tokens = source->tokens->next;
        PC_FreeToken(token);
    }

    for (int32_t bucket = 0; bucket < CODUO_PC_DEFINE_HASH_BUCKET_COUNT;
         ++bucket) {
        while (source->defineHash[bucket] != NULL) {
            define_t *define = source->defineHash[bucket];
            source->defineHash[bucket] = define->hashNext;
            PC_FreeDefine(define);
        }
    }

    while (source->indentStack != NULL) {
        indent_t *indent = source->indentStack;
        source->indentStack = source->indentStack->next;
        Com_DebugFree(indent);
    }

    if (source->defineHash != NULL) {
        Com_DebugFree(source->defineHash);
    }
    Com_DebugFree(source);
}

int32_t PC_LoadSourceHandle(const char *filename)
{
    int32_t handle;
    for (handle = 1; handle < CODUO_PC_SOURCE_HANDLE_COUNT; ++handle) {
        if (pc_sourceFiles[handle] == NULL) {
            break;
        }
    }

    if (handle == CODUO_PC_SOURCE_HANDLE_COUNT) {
        return 0;
    }

    PS_SetBaseFolder("");

    source_t *source = LoadSourceFile(filename);
    if (source == NULL) {
        return 0;
    }

    pc_sourceFiles[handle] = source;
    return handle;
}

qboolean PC_FreeSourceHandle(int32_t handle)
{
    if (handle < 1 || handle >= CODUO_PC_SOURCE_HANDLE_COUNT) {
        return qfalse;
    }

    if (pc_sourceFiles[handle] == NULL) {
        return qfalse;
    }

    FreeSource(pc_sourceFiles[handle]);
    pc_sourceFiles[handle] = NULL;
    return qtrue;
}

qboolean PC_ReadTokenHandle(int32_t handle, coduo_pc_handle_token_t *token)
{
    if (handle < 1 || handle >= CODUO_PC_SOURCE_HANDLE_COUNT) {
        return qfalse;
    }

    source_t *source = pc_sourceFiles[handle];
    if (source == NULL) {
        return qfalse;
    }

    token_t readToken;
    qboolean readStatus = PC_ReadToken(source, &readToken);

    /* ORIGINAL_BINARY_BUG: stock publishes this text with strcpy even when a
     * maximum-length name/hex/binary scanner returned failure without adding
     * a NUL.  See original-bugs/
     * coduomp-precompiler-tokenizer-boundary-accesses.md. */
    strcpy(token->string, readToken.string);
    token->type = readToken.type;
    token->subtype = readToken.subtype;
    token->intValue = readToken.intValue;

#if EMULATE_X87
    /* token is an 80-bit TBYTE narrowed straight to float (fld TBYTE, fstp
     * DWORD = 80->32 near-even), NOT through an intermediate double. */
    token->floatValue =
        x87f_store_f32(
            coduo_engine_pc_load_token_float80(readToken.floatValue));
#else
    long double floatValue = 0.0L;
    size_t valueSize = sizeof(floatValue);
    if (CODUO_PC_TOKEN_FLOAT_VALUE_SIZE < valueSize) {
        valueSize = CODUO_PC_TOKEN_FLOAT_VALUE_SIZE;
    }
    memcpy(&floatValue, readToken.floatValue, valueSize);
    token->floatValue = (float)floatValue;
#endif

    if (token->type == PC_TOKEN_TYPE_STRING) {
        StripDoubleQuotes(token->string);
    }

    return readStatus;
}

qboolean PC_SourceFileAndLine(int32_t handle, char *filename, int32_t *line)
{
    if (handle < 1 || handle >= CODUO_PC_SOURCE_HANDLE_COUNT) {
        return qfalse;
    }

    source_t *source = pc_sourceFiles[handle];
    if (source == NULL) {
        return qfalse;
    }

    script_t *script = source->scriptStack;
    if (script == NULL) {
        strcpy(filename, source->filename);
        *line = 0;
    } else {
        strcpy(filename, script->filename);
        *line = script->line;
    }

    return qtrue;
}

void PC_CheckOpenSourceHandles(void)
{
    for (int32_t handle = 1; handle < CODUO_PC_SOURCE_HANDLE_COUNT;
         ++handle) {
        source_t *source = pc_sourceFiles[handle];

        if (source != NULL) {
            Com_Printf("^1Error: file %s still open in precompiler\n",
                       source->scriptStack->filename);
        }
    }
}

void PC_SetBaseFolder(const char *path)
{
    PS_SetBaseFolder(path);
}
