Files
Odin/core/bindgen/generator-helpers.odin
T
phillvancejr 42364f2fce sync with main
2022-02-04 13:16:40 -05:00

393 lines
10 KiB
Odin

package bindgen
import "core:fmt"
import "core:os"
import "core:io"
import "core:strings"
import "core:unicode/utf8"
Case :: enum {
Unknown,
Camel,
Constant,
Kebab,
Pascal,
Snake,
}
WordCase :: enum {
Unknown,
Up,
Low,
FirstUp,
// When first upping, numbers are followed always by a capital
FirstUpNumberReset,
}
// Change a character to a capital.
to_uppercase :: proc(c : rune) -> rune {
c := c;
if c >= 'a' && c <= 'z' {
c = c - 'a' + 'A';
}
return c;
}
// Change a character to lowercase.
to_lowercase :: proc(c : rune) -> rune {
c := c;
if c >= 'A' && c <= 'Z' {
c = c - 'A' + 'a';
}
return c;
}
// @note Stolen tprint and fprint from fmt package, because it was confusing due to args: ..any and sep default parameter.
tcat :: proc(args: ..any) -> string {
return fmt.tprint(args=args, sep="");
}
fcat :: proc(fd: os.Handle, args: ..any) -> int {
return fmt.fprint(fd=fd, args=args, sep="");
}
// Change the case convention of a word.
change_word_case :: proc(str : string, targetCase : WordCase) -> string {
newStr : string;
if targetCase == WordCase.Up {
for c in str {
newStr = tcat(newStr, to_uppercase(c));
}
}
else if targetCase == WordCase.Low {
for c in str {
newStr = tcat(newStr, to_lowercase(c));
}
}
else if targetCase == WordCase.FirstUp {
for c, i in str {
if i == 0 {
newStr = tcat(newStr, to_uppercase(c));
} else {
newStr = tcat(newStr, to_lowercase(c));
}
}
}
else if targetCase == WordCase.FirstUpNumberReset {
for c, i in str {
if i == 0 || (str[i - 1] >= '0' && str[i - 1] <= '9') {
newStr = tcat(newStr, to_uppercase(c));
} else {
newStr = tcat(newStr, to_lowercase(c));
}
}
}
return newStr;
}
// Change the case convention of a string by detecting original convention,
// then splitting it into words.
change_case :: proc(str : string, targetCase : Case) -> string {
if targetCase == Case.Unknown {
return str;
}
// Split
parts := autosplit_string(str);
// Join
newStr : string;
if targetCase == Case.Pascal {
for part, i in parts {
newStr = tcat(newStr, change_word_case(part, WordCase.FirstUpNumberReset));
}
}
else if targetCase == Case.Snake {
for part, i in parts {
newStr = tcat(newStr, change_word_case(part, WordCase.Low), (i != len(parts) - 1) ? "_" : "");
}
}
else if targetCase == Case.Kebab {
for part, i in parts {
newStr = tcat(newStr, change_word_case(part, WordCase.Low), (i != len(parts) - 1) ? "-" : "");
}
}
else if targetCase == Case.Camel {
for part, i in parts {
if i == 0 {
newStr = tcat(newStr, change_word_case(part, WordCase.Low));
} else {
newStr = tcat(newStr, change_word_case(part, WordCase.FirstUpNumberReset));
}
}
}
else if targetCase == Case.Constant {
for part, i in parts {
newStr = tcat(newStr, change_word_case(part, WordCase.Up), (i != len(parts) - 1) ? "_" : "");
}
}
return newStr;
}
// Identify the case of the provided string.
// Full lowercase with no separator is identified as camelCase.
find_case :: proc(str : string) -> Case {
refuted : bool;
// CONSTANT_CASE
refuted = false;
for c in str {
if (c != '_') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') {
refuted = true;
break;
}
}
if !refuted do return Case.Constant;
for c in str {
// snake_case
if c == '_' {
return Case.Snake;
} // kebab-case
else if c == '-' {
return Case.Kebab;
}
}
// PascalCase
if str[0] >= 'A' && str[0] <= 'Z' {
return Case.Pascal;
}
// camelCase
return Case.Camel;
}
// Splits the string according to detected case.
// HeyBuddy -> {"Hey", "Buddy"}
// hey-buddy -> {"hey", "buddy"}
// _hey_buddy -> {"", "hey", "buddy"}
// and such...
autosplit_string :: proc(str : string) -> [dynamic]string {
lowCount := 0;
upCount := 0;
for c in str {
// If any '_', split according to that (CONSTANT_CASE or snake_case)
if c == '_' {
return split_from_separator(str, '_');
} // If any '-', split according to that (kebab-case)
else if c == '-' {
return split_from_separator(str, '-');
}
else if c >= 'a' && c <= 'z' {
lowCount += 1;
}
else if c >= 'A' && c <= 'Z' {
upCount += 1;
}
}
// If it seems to be only one word
if lowCount == 0 || upCount == 0 {
parts : [dynamic]string;
append(&parts, str);
return parts;
}
// Split at each uppercase letter (PascalCase or camelCase)
return split_from_capital(str);
}
split_from_separator :: proc(str : string, sep : rune) -> [dynamic]string {
parts : [dynamic]string;
lastI := 0;
// Empty strings for starting separators in string
for c in str {
if c == sep {
append(&parts, "");
lastI += 1;
} else {
break;
}
}
// Ignore non letter prefix
if lastI == 0 {
for c in str {
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
lastI += 1;
}
else {
break;
}
}
}
for c, i in str {
if i > lastI + 1 && c == sep {
append(&parts, str[lastI:i]);
lastI = i + 1;
}
}
append(&parts, str[lastI:]);
return parts;
}
split_from_capital :: proc(str : string) -> [dynamic]string {
parts : [dynamic]string;
// Ignore non letter prefix
lastI := 0;
for c in str {
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
lastI += 1;
}
else {
break;
}
}
// We want to handle:
// myBrainIsCRAZY -> my Brain Is Crazy
// myCRAZYBrain -> my CRAZY Brain
// SOLO -> SOLO
// Do split
for i := 1; i < len(str); i += 1 {
if str[i] >= 'A' && str[i] <= 'Z' {
// Do not split too much if it seems to be a capitalized word
if (lastI == i - 1) && (str[lastI] >= 'A' && str[lastI] <= 'Z') {
for ; i + 1 < len(str); i += 1 {
if str[i + 1] < 'A' || str[i + 1] > 'Z' {
break;
}
}
if (i + 1 == len(str)) && (str[i] >= 'A' && str[i] <= 'Z') {
i += 1;
}
}
append(&parts, str[lastI:i]);
lastI = i;
}
}
if lastI != len(str) {
append(&parts, str[lastI:]);
}
return parts;
}
// Check if str if prefixed with any of the provided strings,
// even combinaisons of those, and remove them.
remove_prefixes :: proc(str : string, prefixes : []string, transparentPrefixes : []string = nil) -> string {
str := str;
transparentStr := "";
found := true;
for found {
found = false;
// Remove effective prefixes
for prefix in prefixes {
if len(str) >= len(prefix) &&
str[:len(prefix)] == prefix {
str = str[len(prefix):];
if len(str) != 0 && (str[0] == '_' || str[0] == '-') {
str = str[1:];
}
found = true;
break;
}
}
if found do continue;
// Remove transparent ones, only one by one,
// as we want effective ones to be fully removed.
for prefix in transparentPrefixes {
if len(str) >= len(prefix) &&
str[:len(prefix)] == prefix {
str = str[len(prefix):];
transparentStr = tcat(transparentStr, prefix);
if len(str) != 0 && (str[0] == '_' || str[0] == '-') {
str = str[1:];
transparentStr = tcat(transparentStr, '_');
}
found = true;
break;
}
}
}
return tcat(transparentStr, str);
}
// Check if str if postfixes with any of the provided strings,
// even combinaisons of those, and remove them.
remove_postfixes_with_removed :: proc(
str : string,
postfixes : []string,
transparentPostfixes : []string = nil) -> (string, [dynamic]string) {
str := str;
removedPostfixes : [dynamic]string;
transparentStr := "";
found := true;
for found {
found = false;
// Remove effective postfixes
for postfix in postfixes {
if ends_with(str, postfix) {
str = str[:len(str) - len(postfix)];
if len(str) != 0 && (str[len(str)-1] == '_' || str[len(str)-1] == '-') {
str = str[:len(str)-1];
}
append(&removedPostfixes, postfix);
found = true;
break;
}
}
if found do continue;
// Remove transparent ones, only one by one,
// as we want effective ones to be fully removed.
for postfix in transparentPostfixes {
if ends_with(str, postfix) {
str = str[:len(str) - len(postfix)];
transparentStr = tcat(postfix, transparentStr);
if len(str) != 0 && (str[len(str)-1] == '_' || str[len(str)-1] == '-') {
str = str[:len(str)-1];
transparentStr = tcat('_', transparentStr);
}
found = true;
break;
}
}
}
return tcat(str, transparentStr), removedPostfixes;
}
remove_postfixes :: proc(
str : string,
postfixes : []string,
transparentPostfixes : []string = nil) -> string {
str := str;
removedPostfixes : [dynamic]string;
str, removedPostfixes = remove_postfixes_with_removed(str, postfixes, transparentPostfixes);
return str;
}
ends_with :: proc(str : string, postfix : string) -> bool {
return len(str) >= len(postfix) && str[len(str) - len(postfix):] == postfix;
}