diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-05-29 22:52:10 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-05-29 22:52:10 -0300 |
| commit | f6c8c67094de3fe19f1e7871a7e78a91fb9bf3dd (patch) | |
| tree | 16d4bf88e2a3cc37a160d768fa26b82390796cb7 /src/strings.cc | |
| parent | e5b60ae075e571a29868993a5f0d191d91f8d266 (diff) | |
| download | game-f6c8c67094de3fe19f1e7871a7e78a91fb9bf3dd.tar.gz game-f6c8c67094de3fe19f1e7871a7e78a91fb9bf3dd.zip | |
finish `strings.cc` impl
Diffstat (limited to 'src/strings.cc')
| -rw-r--r-- | src/strings.cc | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/src/strings.cc b/src/strings.cc index 41fdc44..aef9a81 100644 --- a/src/strings.cc +++ b/src/strings.cc @@ -214,3 +214,386 @@ void InitStrings(void){ void ExitStrings(void){ // no-op } + +// String Utility +// ============================================================================= +// TODO(fusion): I'm not sure why we have these separate from the other string +// utility defined in `util.cc`. We should probably just move them all here. + +bool IsCountable(const char *s){ + if(strncmp(s, "some ", 5) == 0){ + return false; + } + + return strncmp(s, "a ", 2) == 0 + || strncmp(s, "an ", 3) == 0; +} + +const char *Plural(const char *s, int Count){ + static char ObjectNameString[50]; + + ObjectNameString[0] = 0; + if(s == NULL){ + error("Plural: Übergebener String existiert nicht.\n"); + return ObjectNameString; + } + + strcpy(ObjectNameString, s); + if(Count == 1 || !IsCountable(ObjectNameString)){ + return ObjectNameString; + } + + // NOTE(fusion): `IsCountable` will only return true for `a XXXX` or `an XXXX`. + if(ObjectNameString[1] == 'n'){ + memmove(&ObjectNameString[0], + &ObjectNameString[3], + sizeof(ObjectNameString) - 3); + }else{ + memmove(&ObjectNameString[0], + &ObjectNameString[2], + sizeof(ObjectNameString) - 2); + } + + if(Count < 0){ + return ObjectNameString; + } + + char Help[sizeof(ObjectNameString)]; + if(char *Suffix = strstr(ObjectNameString, " of ")){ + strcpy(Help, Suffix); + Suffix[0] = 0; + }else{ + Help[0] = 0; + } + + // TODO(fusion): This method for pluralization is limited and incomplete but + // it probably works with the current set of object names. + if(strcmp(ObjectNameString, "knife") == 0){ + strcpy(ObjectNameString, "knives"); + }else if(strcmp(ObjectNameString, "throwing knife") == 0){ + strcpy(ObjectNameString, "throwing knives"); + }else if(strcmp(ObjectNameString, "orc spearman") == 0){ + strcpy(ObjectNameString, "orc spearmen"); + }else if(strcmp(ObjectNameString, "deer") != 0 + && strcmp(ObjectNameString, "dead deer") != 0 + && strcmp(ObjectNameString, "sheep") != 0 + && strcmp(ObjectNameString, "black sheep") != 0 + && strcmp(ObjectNameString, "fish") != 0){ + int NameLen = (int)strlen(ObjectNameString); + if(NameLen > 0){ + int Last = toLower(ObjectNameString[NameLen - 1]); + int Prev = 0; + if(NameLen > 1){ + Prev = toLower(ObjectNameString[NameLen - 2]); + } + + if(Last == 's' || Last == 'x' + || (Last == 'h' && (Prev == 's' || Prev == 'c'))){ + strcat(ObjectNameString, "es"); + }else if(Last == 'y' && Prev != 'a' && Prev != 'e' + && Prev != 'i' && Prev != 'o' && Prev != 'u'){ + ObjectNameString[NameLen - 1] = 'i'; + strcat(ObjectNameString, "es"); + }else if(Last == 'f' && Prev == 'l'){ + ObjectNameString[NameLen - 1] = 'v'; + strcat(ObjectNameString, "es"); + }else if(Last == 'o'){ + strcat(ObjectNameString, "es"); + }else{ + strcat(ObjectNameString, "s"); + } + } + } + + strcat(ObjectNameString, Help); + if(Count != 0){ + strcpy(Help, ObjectNameString); + snprintf(ObjectNameString, sizeof(ObjectNameString), "%d %s", Count, Help); + } + + return ObjectNameString; +} + +const char *SearchForWord(const char *Pattern, const char *Text){ + if(Pattern == NULL || Pattern[0] == 0){ + error("SearchForWord: Übergebenes Suchwort existiert nicht.\n"); + return NULL; + } + + if(Text == NULL){ + error("SearchForWord: Übergebener Text existiert nicht.\n"); + return NULL; + } + + int PatternLength = (int)strlen(Pattern); + bool WholeWord = false; + if(Pattern[PatternLength - 1] == '$'){ + PatternLength -= 1; + WholeWord = true; + } + + bool WordStart = true; + const char *Match = NULL; + for(int i = 0; Text[i] != 0; i += 1){ + // TODO(fusion): The original function would only check for spaces to + // determine the beginning of a word which can be problematic for sentences + // with punctuation. + //if(isSpace(Text[i])){ + if(!isAlpha(Text[i]) && !isDigit(Text[i])){ + WordStart = true; + }else if(WordStart){ + int j = 0; + while(j < PatternLength){ + if(toLower(Pattern[j]) != toLower(Text[i + j])){ + break; + } + j += 1; + } + + if(j == PatternLength){ + if(!WholeWord || (!isAlpha(Text[i + j]) && !isDigit(Text[i + j]))){ + Match = &Text[i]; + break; + } + } + + WordStart = false; + } + } + + return Match; +} + +const char *SearchForNumber(int Count, const char *Text){ + if(Count < 1){ + error("SearchForNumber: Illegale Suchnummer %d.\n", Count); + return NULL; + } + + if(Text == NULL){ + error("SearchForNumber: Übergebener Text existiert nicht.\n"); + return NULL; + } + + bool WordStart = true; + const char *Match = NULL; + for(int i = 0; Text[i] != 0; i += 1){ + // TODO(fusion): Same as `SearchForWord`. + //if(isSpace(Text[i])){ + if(!isAlpha(Text[i]) && !isDigit(Text[i])){ + WordStart = true; + }else if(WordStart){ + int j = 0; + while(isDigit(Text[i + j])){ + j += 1; + } + + // TODO(fusion): Same as `SearchForWord`. + //if(j > 0 && (Text[i + j] == 0 || isSpace(Text[i + j]))){ + if(j > 0 && !isAlpha(Text[i + j])){ + Count -= 1; + if(Count == 0){ + Match = &Text[i]; + break; + } + } + + WordStart = false; + } + } + + return Match; +} + +bool MatchString(const char *Pattern, const char *String){ + if(Pattern == NULL){ + error("MatchString: Pattern ist NULL.\n"); + return false; + } + + if(String == NULL){ + error("MatchString: String ist NULL.\n"); + return false; + } + + // NOTE(fusion): The original function was allocating an integer table on + // the stack, with dimensions (StringLength + 1) x (PatternLength + 1) to + // compute the edit distance. This made the decompiled version difficult + // to read and could potentially blow up the stack for large inputs. + // We'll use a fixed buffer instead, that will store the distances for the + // last and current table rows which is all is needed to compute the same + // edit distance. + + int StringLength = (int)strlen(String); + int PatternLength = (int)strlen(Pattern); + + constexpr int MaxStringLength = 255; + if(StringLength > MaxStringLength){ + error("MatchString: String is too large (Length = %d, MaxLength = %d)\n", + StringLength, MaxStringLength); + return false; + } + + int DistanceBuffer[2 * (MaxStringLength + 1)] = {}; + int *PrevRow = &DistanceBuffer[0]; + int *CurRow = &DistanceBuffer[StringLength + 1]; + for(int i = 0; i < (StringLength + 1); i += 1){ + PrevRow[i] = i; + } + + for(int i = 0; i < PatternLength; i += 1){ + int Ins = (Pattern[i] != '*'); + int Del = (Pattern[i] != '*'); + CurRow[0] = PrevRow[0] + Del; + for(int j = 0; j < StringLength; j += 1){ + int Edit = (Pattern[i] != '?' + && toLower(Pattern[i]) != toLower(String[j])); + + int Min = PrevRow[j + 1] + Ins; + if(Min > (PrevRow[j] + Edit)){ + Min = PrevRow[j] + Edit; + } + if(Min > (CurRow[j] + Del)){ + Min = CurRow[j] + Del; + } + + CurRow[j + 1] = Min; + } + + std::swap(PrevRow, CurRow); + } + + return PrevRow[StringLength] == 0; +} + +// TODO(fusion): This function is unsafe like `strcpy`. +void AddSlashes(char *Destination, const char *Source){ + if(Source == NULL){ + error("AddSlashes: Source ist NULL.\n"); + return; + } + + if(Destination == NULL){ + error("AddSlashes: Destination ist NULL.\n"); + return; + } + + int ReadIndex = 0; + int WriteIndex = 0; + while(Source[ReadIndex] != 0){ + if(Source[ReadIndex] == '"' || Source[ReadIndex] == '#' + || Source[ReadIndex] == '\'' || Source[ReadIndex] == '\\'){ + Destination[WriteIndex] = '\\'; + WriteIndex += 1; + } + + Destination[WriteIndex] = Source[ReadIndex]; + WriteIndex += 1; + ReadIndex += 1; + } + + Destination[WriteIndex] = 0; + WriteIndex += 1; +} + +void Trim(char *Text){ + if(Text == NULL){ + error("Trim: Text ist NULL.\n"); + return; + } + + // NOTE(fusion): `Start` is inclusive. + int Start = 0; + while(Text[Start] != 0 && isSpace(Text[Start])){ + Start += 1; + } + + int OldLength = Start; + while(Text[OldLength] != 0){ + OldLength += 1; + } + + if(Start < OldLength){ + // NOTE(fusion): `End` is inclusive. + int End = OldLength - 1; + while(End > Start && isSpace(Text[End])){ + End -= 1; + } + + int NewLength = End - Start + 1; + memmove(&Text[0], &Text[Start], NewLength); + Text[NewLength] = 0; + }else{ + Text[0] = 0; + } +} + +// TODO(fusion): This is a copy of the function above, except that we write to +// `Destination` instead of modifying the original string. It is unsafe as `strcpy`. +void Trim(char *Destination, const char *Source){ + if(Source == NULL){ + error("Trim: Source ist NULL.\n"); + return; + } + + if(Destination == NULL){ + error("Trim: Destination ist NULL.\n"); + return; + } + + int Start = 0; + while(Source[Start] != 0 && isSpace(Source[Start])){ + Start += 1; + } + + int OldLength = Start; + while(Source[OldLength] != 0){ + OldLength += 1; + } + + if(Start < OldLength){ + // NOTE(fusion): `End` is inclusive. + int End = OldLength - 1; + while(End > Start && isSpace(Source[End])){ + End -= 1; + } + + int NewLength = End - Start + 1; + memcpy(&Destination[0], &Source[Start], NewLength); + Destination[NewLength] = 0; + }else{ + Destination[0] = 0; + } +} + +void ChangeWildcards(char *String){ + if(String == NULL){ + error("ChangeWildcards: String ist NULL.\n"); + return; + } + + for(int i = 0; String[i] != 0; i += 1){ + if(String[i] == '*'){ + String[i] = '%'; + }else if(String[i] == '?'){ + String[i] = '_'; + } + } +} + +char *Capitals(char *Text){ + bool WordStart = true; + for(int i = 0; Text[i] != 0; i += 1){ + // TODO(fusion): The original function wouldn't consider digits which + // I'd argue is a mistake if there is a number prefixing a word. + if(!isAlpha(Text[i]) && !isDigit(Text[i])){ + WordStart = true; + }else if(WordStart){ + if(isAlpha(Text[i])){ + Text[i] = toUpper(Text[i]); + } + WordStart = false; + } + } + return Text; +} |
