807 lines
20 KiB
C
807 lines
20 KiB
C
/**
|
|
* Copyright (c) 2026 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "assetlocaleloader.h"
|
|
#include "util/memory.h"
|
|
#include "util/math.h"
|
|
#include "util/string.h"
|
|
#include "assert/assert.h"
|
|
|
|
errorret_t assetLocaleFileInit(
|
|
assetlocalefile_t *localeFile,
|
|
const char_t *path
|
|
) {
|
|
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
|
assertNotNull(path, "Locale file path cannot be NULL.");
|
|
|
|
memoryZero(localeFile, sizeof(assetlocalefile_t));
|
|
|
|
// Init the asset file.
|
|
errorChain(assetFileInit(&localeFile->file, path, NULL, NULL));
|
|
|
|
// Open the file handle
|
|
errorChain(assetFileOpen(&localeFile->file));
|
|
|
|
// Get the blank key, this is basically the header info for po files
|
|
char_t buffer[1024];
|
|
errorChain(assetLocaleGetString(localeFile, "", 0, buffer, sizeof(buffer)));
|
|
errorChain(assetLocaleParseHeader(localeFile, buffer, sizeof(buffer)));
|
|
|
|
errorOk();
|
|
}
|
|
|
|
errorret_t assetLocaleFileDispose(assetlocalefile_t *localeFile) {
|
|
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
|
|
|
errorChain(assetFileClose(&localeFile->file));
|
|
errorChain(assetFileDispose(&localeFile->file));
|
|
|
|
errorOk();
|
|
}
|
|
|
|
errorret_t assetLocaleParseHeader(
|
|
assetlocalefile_t *localeFile,
|
|
char_t *headerBuffer,
|
|
const size_t headerBufferSize
|
|
) {
|
|
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
|
assertNotNull(headerBuffer, "Header buffer cannot be NULL.");
|
|
assertTrue(headerBufferSize > 0, "Header buffer size must be > 0.");
|
|
|
|
// Find "Plural-Forms: " line and parse out plural form info
|
|
char_t *pluralFormsLine = strstr(headerBuffer, "Plural-Forms:");
|
|
if(!pluralFormsLine) {
|
|
errorOk();
|
|
}
|
|
|
|
pluralFormsLine += strlen("Plural-Forms:");
|
|
|
|
// Expect nplurals
|
|
char_t *npluralsStr = strstr(pluralFormsLine, "nplurals=");
|
|
if(!npluralsStr) {
|
|
errorThrow("Failed to find nplurals in Plural-Forms header.");
|
|
}
|
|
npluralsStr += strlen("nplurals=");
|
|
localeFile->pluralStateCount = (uint8_t)atoi(npluralsStr);
|
|
|
|
if(localeFile->pluralStateCount == 0) {
|
|
errorThrow("nplurals must be greater than 0.");
|
|
}
|
|
if(localeFile->pluralStateCount > ASSET_LOCALE_FILE_PLURAL_FORM_COUNT) {
|
|
errorThrow(
|
|
"nplurals exceeds maximum supported plural forms: %d > %d",
|
|
localeFile->pluralStateCount,
|
|
ASSET_LOCALE_FILE_PLURAL_FORM_COUNT
|
|
);
|
|
}
|
|
|
|
// Expect plural=
|
|
char_t *pluralStr = strstr(pluralFormsLine, "plural=");
|
|
if(!pluralStr) {
|
|
errorThrow("Failed to find plural in Plural-Forms header.");
|
|
}
|
|
pluralStr += strlen("plural=");
|
|
|
|
// Expect ( [expressions] )
|
|
char_t *openParen = strchr(pluralStr, '(');
|
|
char_t *closeParen = strrchr(pluralStr, ')');
|
|
if(!openParen || !closeParen || closeParen < openParen) {
|
|
errorThrow("Failed to find plural expression in Plural-Forms header.");
|
|
}
|
|
|
|
// Parse:
|
|
// n [op] value ? index : n [op] value ? index : ... : final_index
|
|
char_t *ptr = openParen + 1;
|
|
uint8_t pluralIndex = 0;
|
|
uint8_t definedCount = 0;
|
|
|
|
while(1) {
|
|
while(*ptr == ' ') ptr++;
|
|
|
|
// Allow grouped subexpressions like:
|
|
// (n<7 ? 2 : 3)
|
|
// or
|
|
// (((3)))
|
|
uint8_t parenDepth = 0;
|
|
while(*ptr == '(') {
|
|
parenDepth++;
|
|
ptr++;
|
|
while(*ptr == ' ') ptr++;
|
|
}
|
|
|
|
// Final fallback: just an integer
|
|
if(*ptr != 'n') {
|
|
char_t *endPtr = NULL;
|
|
int32_t finalIndex = (int32_t)strtol(ptr, &endPtr, 10);
|
|
if(endPtr == ptr) {
|
|
errorThrow("Expected final plural index.");
|
|
}
|
|
ptr = endPtr;
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
while(parenDepth > 0) {
|
|
if(*ptr != ')') {
|
|
errorThrow("Expected ')' after final plural index.");
|
|
}
|
|
ptr++;
|
|
parenDepth--;
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
}
|
|
|
|
if(*ptr != ')') {
|
|
errorThrow("Expected ')' at end of plural expression.");
|
|
}
|
|
|
|
if(finalIndex < 0 || finalIndex >= localeFile->pluralStateCount) {
|
|
errorThrow(
|
|
"Final plural expression index out of bounds: %d (nplurals: %d)",
|
|
finalIndex,
|
|
localeFile->pluralStateCount
|
|
);
|
|
}
|
|
|
|
localeFile->pluralDefaultIndex = (uint8_t)finalIndex;
|
|
definedCount++;
|
|
break;
|
|
}
|
|
|
|
if(pluralIndex >= localeFile->pluralStateCount - 1) {
|
|
errorThrow(
|
|
"Too many plural conditions. Expected %d conditional clauses for nplurals=%d.",
|
|
localeFile->pluralStateCount - 1,
|
|
localeFile->pluralStateCount
|
|
);
|
|
}
|
|
|
|
ptr++; // skip 'n'
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
|
|
// Determine operator
|
|
assetlocalepluraloperation_t op;
|
|
if(strncmp(ptr, "==", 2) == 0) {
|
|
op = ASSET_LOCALE_PLURAL_OP_EQUAL;
|
|
ptr += 2;
|
|
} else if(strncmp(ptr, "!=", 2) == 0) {
|
|
op = ASSET_LOCALE_PLURAL_OP_NOT_EQUAL;
|
|
ptr += 2;
|
|
} else if(strncmp(ptr, "<=", 2) == 0) {
|
|
op = ASSET_LOCALE_PLURAL_OP_LESS_EQUAL;
|
|
ptr += 2;
|
|
} else if(strncmp(ptr, ">=", 2) == 0) {
|
|
op = ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL;
|
|
ptr += 2;
|
|
} else if(*ptr == '<') {
|
|
op = ASSET_LOCALE_PLURAL_OP_LESS;
|
|
ptr++;
|
|
} else if(*ptr == '>') {
|
|
op = ASSET_LOCALE_PLURAL_OP_GREATER;
|
|
ptr++;
|
|
} else {
|
|
errorThrow("Unsupported plural operator.");
|
|
}
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
|
|
// Parse the comparitor value
|
|
char_t *endPtr = NULL;
|
|
int32_t value = (int32_t)strtol(ptr, &endPtr, 10);
|
|
if(endPtr == ptr) {
|
|
errorThrow("Expected value for plural expression.");
|
|
}
|
|
ptr = endPtr;
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
|
|
// Parse ternary operator
|
|
if(*ptr != '?') {
|
|
errorThrow("Expected '?' after plural expression.");
|
|
}
|
|
ptr++;
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
|
|
// Parse the indice
|
|
endPtr = NULL;
|
|
int32_t index = (int32_t)strtol(ptr, &endPtr, 10);
|
|
if(endPtr == ptr) {
|
|
errorThrow("Expected index for plural expression.");
|
|
}
|
|
ptr = endPtr;
|
|
|
|
if(index < 0 || index >= localeFile->pluralStateCount) {
|
|
errorThrow(
|
|
"Plural expression index out of bounds: %d (nplurals: %d)",
|
|
index,
|
|
localeFile->pluralStateCount
|
|
);
|
|
}
|
|
|
|
// Store plural expression.
|
|
localeFile->pluralIndices[pluralIndex] = (uint8_t)index;
|
|
localeFile->pluralOps[pluralIndex] = op;
|
|
localeFile->pluralValues[pluralIndex] = value;
|
|
pluralIndex++;
|
|
definedCount++;
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
|
|
// Close any grouping parens that wrapped this conditional branch
|
|
while(parenDepth > 0) {
|
|
if(*ptr != ')') {
|
|
break;
|
|
}
|
|
ptr++;
|
|
parenDepth--;
|
|
|
|
while(*ptr == ' ') ptr++;
|
|
}
|
|
|
|
if(*ptr != ':') {
|
|
errorThrow("Expected ':' after plural expression.");
|
|
}
|
|
ptr++;
|
|
}
|
|
|
|
// Must define exactly nplurals outcomes:
|
|
// (nplurals - 1) conditional results + 1 final fallback result
|
|
if(
|
|
pluralIndex != localeFile->pluralStateCount - 1 ||
|
|
definedCount != localeFile->pluralStateCount
|
|
) {
|
|
errorThrow("Plural expression count does not match nplurals.");
|
|
}
|
|
|
|
errorOk();
|
|
}
|
|
|
|
|
|
uint8_t assetLocaleEvaluatePlural(
|
|
assetlocalefile_t *file,
|
|
const int32_t pluralCount
|
|
) {
|
|
assertNotNull(file, "Locale file cannot be NULL.");
|
|
assertTrue(pluralCount >= 0, "Plural count cannot be negative.");
|
|
|
|
for(uint8_t i = 0; i < file->pluralStateCount - 1; i++) {
|
|
int32_t value = file->pluralValues[i];
|
|
switch(file->pluralOps[i]) {
|
|
case ASSET_LOCALE_PLURAL_OP_EQUAL:
|
|
if(pluralCount == value) return file->pluralIndices[i];
|
|
break;
|
|
|
|
case ASSET_LOCALE_PLURAL_OP_NOT_EQUAL:
|
|
if(pluralCount != value) return file->pluralIndices[i];
|
|
break;
|
|
|
|
case ASSET_LOCALE_PLURAL_OP_LESS:
|
|
if(pluralCount < value) return file->pluralIndices[i];
|
|
break;
|
|
|
|
case ASSET_LOCALE_PLURAL_OP_LESS_EQUAL:
|
|
if(pluralCount <= value) return file->pluralIndices[i];
|
|
break;
|
|
|
|
case ASSET_LOCALE_PLURAL_OP_GREATER:
|
|
if(pluralCount > value) return file->pluralIndices[i];
|
|
break;
|
|
|
|
case ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL:
|
|
if(pluralCount >= value) return file->pluralIndices[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return file->pluralDefaultIndex;
|
|
}
|
|
|
|
errorret_t assetLocaleLineSkipBlanks(
|
|
assetfilelinereader_t *reader,
|
|
uint8_t *lineBuffer
|
|
) {
|
|
while(!reader->eof) {
|
|
// Skip blank lines
|
|
if(lineBuffer[0] == '\0') {
|
|
errorChain(assetFileLineReaderNext(reader));
|
|
continue;
|
|
}
|
|
|
|
// Skip comment lines
|
|
if(lineBuffer[0] == '#') {
|
|
errorChain(assetFileLineReaderNext(reader));
|
|
continue;
|
|
}
|
|
|
|
// Is line only spaces?
|
|
size_t lineLength = strlen((char_t *)lineBuffer);
|
|
size_t i;
|
|
bool_t onlySpaces = true;
|
|
for(i = 0; i < lineLength; i++) {
|
|
if(lineBuffer[i] != ' ') {
|
|
onlySpaces = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(onlySpaces) {
|
|
errorChain(assetFileLineReaderNext(reader));
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
errorOk();
|
|
}
|
|
|
|
errorret_t assetLocaleLineUnbuffer(
|
|
assetfilelinereader_t *reader,
|
|
uint8_t *lineBuffer,
|
|
uint8_t *stringBuffer,
|
|
const size_t stringBufferSize
|
|
) {
|
|
stringBuffer[0] = '\0';
|
|
|
|
// At the point this funciton is called, we are looking for an opening quote.
|
|
char_t *start = strchr((char_t *)lineBuffer, '"');
|
|
if(!start) {
|
|
errorThrow("Expected open (0) \"");
|
|
}
|
|
|
|
char *end = strchr(start + 1, '"');
|
|
if(!end) {
|
|
errorThrow("Expected close (0) \"");
|
|
}
|
|
*end = '\0';
|
|
|
|
if(strlen(start) >= stringBufferSize) {
|
|
errorThrow("String buffer overflow");
|
|
}
|
|
memoryCopy(stringBuffer, start + 1, strlen(start));
|
|
|
|
// Now start buffering lines out
|
|
while(!reader->eof) {
|
|
errorChain(assetFileLineReaderNext(reader));
|
|
|
|
// Skip blank lines
|
|
errorChain(assetLocaleLineSkipBlanks(reader, lineBuffer));
|
|
|
|
// Skip starting spaces
|
|
char_t *ptr = (char_t *)lineBuffer;
|
|
while(*ptr == ' ') {
|
|
ptr++;
|
|
}
|
|
|
|
// Only consider lines starting with quote
|
|
if(*ptr != '"') {
|
|
break;
|
|
}
|
|
|
|
ptr++; // move past first quote
|
|
|
|
bool_t escaping = false;
|
|
char_t *dest = (char_t *)stringBuffer + strlen((char_t *)stringBuffer);
|
|
while(*ptr) {
|
|
if(escaping) {
|
|
// Handle escape sequences
|
|
switch(*ptr) {
|
|
case 'n': *dest++ = '\n'; break;
|
|
case 't': *dest++ = '\t'; break;
|
|
case '\\': *dest++ = '\\'; break;
|
|
case '"': *dest++ = '"'; break;
|
|
default:
|
|
errorThrow("Unknown escape sequence: \\%c", *ptr);
|
|
}
|
|
escaping = false;
|
|
} else if(*ptr == '\\') {
|
|
escaping = true;
|
|
} else if(*ptr == '"') {
|
|
// End of string
|
|
break;
|
|
} else {
|
|
// Regular character
|
|
*dest++ = *ptr;
|
|
}
|
|
if((size_t)(dest - (char_t *)stringBuffer) >= stringBufferSize) {
|
|
errorThrow("String buffer overflow");
|
|
}
|
|
ptr++;
|
|
}
|
|
*dest = '\0';
|
|
}
|
|
|
|
errorOk();
|
|
}
|
|
|
|
errorret_t assetLocaleGetString(
|
|
assetlocalefile_t *file,
|
|
const char_t *messageId,
|
|
const int32_t pluralCount,
|
|
char_t *stringBuffer,
|
|
const size_t stringBufferSize
|
|
) {
|
|
assertNotNull(file, "Asset file cannot be NULL.");
|
|
assertNotNull(messageId, "Message ID cannot be NULL.");
|
|
assertTrue(pluralCount >= 0, "Plural index cannot be negative.");
|
|
assertNotNull(stringBuffer, "String buffer cannot be NULL.");
|
|
assertTrue(stringBufferSize > 0, "String buffer size must be > 0");
|
|
assetfilelinereader_t reader;
|
|
|
|
bool_t msgidFound = false, msgidPluralFound = false, msgstrFound = false;
|
|
uint8_t msgidBuffer[256];
|
|
uint8_t msgidPluralBuffer[256];
|
|
uint8_t readBuffer[1024];
|
|
uint8_t lineBuffer[1024];
|
|
uint8_t pluralIndex = 0xFF;
|
|
|
|
msgidBuffer[0] = '\0';
|
|
msgidPluralBuffer[0] = '\0';
|
|
stringBuffer[0] = '\0';
|
|
|
|
// Rewind and start reading lines.
|
|
errorChain(assetFileRewind(&file->file));
|
|
assetFileLineReaderInit(
|
|
&reader,
|
|
&file->file,
|
|
readBuffer,
|
|
sizeof(readBuffer),
|
|
lineBuffer,
|
|
sizeof(lineBuffer)
|
|
);
|
|
|
|
// Skip blanks, comments, etc and start looking for msgid's
|
|
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
|
|
|
while(!reader.eof) {
|
|
// Is this msgid?
|
|
if(memoryCompare(lineBuffer, "msgid", 5) != 0) {
|
|
errorChain(assetFileLineReaderNext(&reader));
|
|
msgidBuffer[0] = '\0';
|
|
continue;
|
|
}
|
|
|
|
// Unbuffer the msgid
|
|
assetLocaleLineUnbuffer(
|
|
&reader, lineBuffer, (uint8_t *)msgidBuffer, sizeof(msgidBuffer)
|
|
);
|
|
|
|
// Is this the needle?
|
|
if(memoryCompare(msgidBuffer, messageId, strlen(messageId)) != 0) {
|
|
continue;
|
|
}
|
|
|
|
msgidFound = true;
|
|
break;
|
|
}
|
|
if(!msgidFound) {
|
|
errorThrow("Failed to find message ID: %s", messageId);
|
|
}
|
|
|
|
// We are either going to see a msgstr or a msgid_plural
|
|
while(!reader.eof) {
|
|
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
|
|
|
// Is msgid_plural?
|
|
if(
|
|
!msgidPluralFound &&
|
|
memoryCompare(lineBuffer, "msgid_plural", 12) == 0
|
|
) {
|
|
// Yes, start reading plural ID
|
|
assetLocaleLineUnbuffer(
|
|
&reader,
|
|
lineBuffer,
|
|
(uint8_t *)msgidPluralBuffer,
|
|
sizeof(msgidPluralBuffer)
|
|
);
|
|
msgidPluralFound = true;
|
|
|
|
// At this point we determine the plural index to use by running the
|
|
// plural formula
|
|
pluralIndex = assetLocaleEvaluatePlural(
|
|
file,
|
|
pluralCount
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// Should be msgstr if not plural.
|
|
if(memoryCompare(lineBuffer, "msgstr", 6) != 0) {
|
|
errorThrow("Expected msgstr after msgid, found: %s", lineBuffer);
|
|
continue;
|
|
}
|
|
|
|
// If plural we need an index
|
|
if(msgidPluralFound) {
|
|
// Skip blank chars
|
|
char_t *ptr = (char_t *)lineBuffer + 6;
|
|
while(*ptr == ' ') {
|
|
ptr++;
|
|
}
|
|
|
|
if(*ptr != '[') {
|
|
errorThrow("Expected [ for plural form, found: %s", lineBuffer);
|
|
}
|
|
|
|
ptr++; // move past [
|
|
|
|
// Parse until ]
|
|
char *end = strchr(ptr, ']');
|
|
if(!end) {
|
|
errorThrow("Expected ] for plural form, found: %s", lineBuffer);
|
|
}
|
|
*end = '\0';
|
|
|
|
int32_t index = atoi(ptr);
|
|
if(index != pluralIndex) {
|
|
// Not the plural form we want, skip to the next useable line
|
|
while(!reader.eof) {
|
|
errorChain(assetFileLineReaderNext(&reader));
|
|
errorChain(assetLocaleLineSkipBlanks(&reader, lineBuffer));
|
|
if(
|
|
lineBuffer[0] == '\"' ||
|
|
lineBuffer[0] == '\0' ||
|
|
lineBuffer[0] == '#'
|
|
) continue;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Undo damage to line buffer from unbuffering.
|
|
*end = ']';
|
|
}
|
|
|
|
// Parse out msgstr
|
|
errorChain(assetLocaleLineUnbuffer(
|
|
&reader, lineBuffer, (uint8_t *)stringBuffer, stringBufferSize
|
|
));
|
|
msgstrFound = true;
|
|
break;
|
|
};
|
|
|
|
if(!msgstrFound) {
|
|
errorThrow("Failed to find msgstr for message ID: %s", messageId);
|
|
}
|
|
|
|
errorOk();
|
|
}
|
|
|
|
errorret_t assetLocaleGetStringWithVA(
|
|
assetlocalefile_t *file,
|
|
const char_t *messageId,
|
|
const int32_t pluralIndex,
|
|
char_t *buffer,
|
|
const size_t bufferSize,
|
|
...
|
|
) {
|
|
assertNotNull(file, "Asset file cannot be NULL.");
|
|
assertNotNull(messageId, "Message ID cannot be NULL.");
|
|
assertNotNull(buffer, "Buffer cannot be NULL.");
|
|
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
|
assertTrue(pluralIndex >= 0, "Plural cannot be negative.");
|
|
|
|
char_t *tempBuffer;
|
|
tempBuffer = memoryAllocate(bufferSize);
|
|
errorret_t ret = assetLocaleGetString(
|
|
file,
|
|
messageId,
|
|
pluralIndex,
|
|
tempBuffer,
|
|
bufferSize
|
|
);
|
|
if(ret.code != ERROR_OK) {
|
|
memoryFree(tempBuffer);
|
|
return ret;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, bufferSize);
|
|
int result = vsnprintf(buffer, bufferSize, tempBuffer, args);
|
|
va_end(args);
|
|
memoryFree(tempBuffer);
|
|
|
|
if(result < 0) {
|
|
errorThrow("Failed to format locale string for ID: %s", messageId);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
errorret_t assetLocaleGetStringWithArgs(
|
|
assetlocalefile_t *file,
|
|
const char_t *id,
|
|
const int32_t plural,
|
|
char_t *buffer,
|
|
const size_t bufferSize,
|
|
const assetlocalearg_t *args,
|
|
const size_t argCount
|
|
) {
|
|
assertNotNull(id, "Message ID cannot be NULL.");
|
|
assertNotNull(buffer, "Buffer cannot be NULL.");
|
|
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
|
assertTrue(plural >= 0, "Plural cannot be negative.");
|
|
assertTrue(
|
|
argCount == 0 || args != NULL,
|
|
"Args cannot be NULL when argCount > 0."
|
|
);
|
|
|
|
char_t *format = memoryAllocate(bufferSize);
|
|
if(format == NULL) {
|
|
errorThrow("Failed to allocate format buffer.");
|
|
}
|
|
|
|
errorret_t ret = assetLocaleGetString(
|
|
file,
|
|
id,
|
|
plural,
|
|
format,
|
|
bufferSize
|
|
);
|
|
if(ret.code != ERROR_OK) {
|
|
memoryFree(format);
|
|
return ret;
|
|
}
|
|
|
|
size_t inIndex = 0;
|
|
size_t outIndex = 0;
|
|
size_t nextArg = 0;
|
|
|
|
buffer[0] = '\0';
|
|
|
|
while(format[inIndex] != '\0') {
|
|
if(format[inIndex] != '%') {
|
|
if(outIndex + 1 >= bufferSize) {
|
|
memoryFree(format);
|
|
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
|
}
|
|
|
|
buffer[outIndex++] = format[inIndex++];
|
|
continue;
|
|
}
|
|
|
|
inIndex++;
|
|
|
|
/* Escaped percent */
|
|
if(format[inIndex] == '%') {
|
|
if(outIndex + 1 >= bufferSize) {
|
|
memoryFree(format);
|
|
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
|
}
|
|
|
|
buffer[outIndex++] = '%';
|
|
inIndex++;
|
|
continue;
|
|
}
|
|
|
|
if(nextArg >= argCount) {
|
|
memoryFree(format);
|
|
errorThrow("Not enough locale arguments for ID: %s", id);
|
|
}
|
|
|
|
{
|
|
char_t specBuffer[32];
|
|
char_t valueBuffer[256];
|
|
size_t specLength = 0;
|
|
int written = 0;
|
|
char_t specifier;
|
|
|
|
specBuffer[specLength++] = '%';
|
|
|
|
/* Allow flags / width / precision */
|
|
while(format[inIndex] != '\0') {
|
|
char_t ch = format[inIndex];
|
|
|
|
if(
|
|
ch == '-' || ch == '+' || ch == ' ' || ch == '#' || ch == '0' ||
|
|
ch == '.' || (ch >= '0' && ch <= '9')
|
|
) {
|
|
if(specLength + 1 >= sizeof(specBuffer)) {
|
|
memoryFree(format);
|
|
errorThrow("Format specifier too long for ID: %s", id);
|
|
}
|
|
|
|
specBuffer[specLength++] = ch;
|
|
inIndex++;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if(format[inIndex] == '\0') {
|
|
memoryFree(format);
|
|
errorThrow("Incomplete format specifier for ID: %s", id);
|
|
}
|
|
|
|
specifier = format[inIndex++];
|
|
|
|
if(specifier != 's' && specifier != 'd' && specifier != 'f') {
|
|
memoryFree(format);
|
|
errorThrow(
|
|
"Unsupported format specifier '%%%c' for ID: %s",
|
|
specifier,
|
|
id
|
|
);
|
|
}
|
|
|
|
specBuffer[specLength++] = specifier;
|
|
specBuffer[specLength] = '\0';
|
|
|
|
switch(specifier) {
|
|
case 's':
|
|
if(args[nextArg].type != ASSET_LOCALE_ARG_STRING) {
|
|
memoryFree(format);
|
|
errorThrow("Expected string locale argument for ID: %s", id);
|
|
}
|
|
|
|
written = snprintf(
|
|
valueBuffer,
|
|
sizeof(valueBuffer),
|
|
specBuffer,
|
|
args[nextArg].stringValue ? args[nextArg].stringValue : ""
|
|
);
|
|
break;
|
|
|
|
case 'd':
|
|
if(args[nextArg].type != ASSET_LOCALE_ARG_INT) {
|
|
memoryFree(format);
|
|
errorThrow("Expected int locale argument for ID: %s", id);
|
|
}
|
|
|
|
written = snprintf(
|
|
valueBuffer,
|
|
sizeof(valueBuffer),
|
|
specBuffer,
|
|
args[nextArg].intValue
|
|
);
|
|
break;
|
|
|
|
case 'f':
|
|
if(
|
|
args[nextArg].type != ASSET_LOCALE_ARG_FLOAT &&
|
|
args[nextArg].type != ASSET_LOCALE_ARG_INT
|
|
) {
|
|
memoryFree(format);
|
|
errorThrow("Expected float or int locale argument for ID: %s", id);
|
|
}
|
|
|
|
float_t floatValue = (
|
|
args[nextArg].type == ASSET_LOCALE_ARG_FLOAT ?
|
|
args[nextArg].floatValue :
|
|
(float_t)args[nextArg].intValue
|
|
);
|
|
|
|
written = snprintf(
|
|
valueBuffer,
|
|
sizeof(valueBuffer),
|
|
specBuffer,
|
|
floatValue
|
|
);
|
|
break;
|
|
}
|
|
|
|
nextArg++;
|
|
|
|
if(written < 0) {
|
|
memoryFree(format);
|
|
errorThrow("Failed to format locale string for ID: %s", id);
|
|
}
|
|
|
|
if(outIndex + (size_t)written >= bufferSize) {
|
|
memoryFree(format);
|
|
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
|
}
|
|
|
|
memoryCopy(buffer + outIndex, valueBuffer, (size_t)written);
|
|
outIndex += (size_t)written;
|
|
}
|
|
}
|
|
|
|
buffer[outIndex] = '\0';
|
|
memoryFree(format);
|
|
errorOk();
|
|
} |