///|
/// Naming subsystem.
///
/// Converts OpenAPI identifiers into stable MoonBit identifiers.
///|
/// MoonBit reserved keywords, reserved type names, and generated helper names.
let reserved : Map[String, Bool] = {
let m : Map[String, Bool] = Map([])
let words = [
"as", "async", "await", "break", "catch", "const", "continue", "derive", "else",
"enum", "false", "fn", "for", "guard", "if", "impl", "import", "in", "init",
"is", "let", "loop", "match", "mut", "noraise", "not", "null", "op", "or", "override",
"package", "priv", "pub", "pure", "raise", "readonly", "ref", "return", "self",
"static", "struct", "suberror", "test", "trait", "true", "try", "type", "var",
"while", "with", "Error", "all", "client", "config", "from_json", "json", "new",
"open",
"to_json", "to_wire",
]
for kw in words {
m[kw] = true
}
m
}
///|
/// Get first char of a string.
fn first_char(s : String) -> Char {
for ch in s {
return ch
}
' '
}
///|
/// Build a string from an array of words joined by separator.
fn join_words(parts : Array[String], sep : String) -> String {
let mut result = ""
for i, part in parts {
if i > 0 {
result = result + sep
}
result = result + part
}
result
}
///|
/// Check if a character is alphanumeric.
fn is_alnum(ch : Char) -> Bool {
(ch >= '0' && ch <= '9') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= 'a' && ch <= 'z')
}
///|
/// Check if a character is uppercase.
fn is_upper(ch : Char) -> Bool {
ch >= 'A' && ch <= 'Z'
}
///|
/// Check if a character is a digit.
fn is_digit(ch : Char) -> Bool {
ch >= '0' && ch <= '9'
}
///|
/// Convert a Char to uppercase.
fn to_upper_char(ch : Char) -> Char {
if ch >= 'a' && ch <= 'z' {
Int::unsafe_to_char(ch.to_int() - 32)
} else {
ch
}
}
///|
/// Convert a string to its uppercase first char version.
fn first_upper(s : String) -> String {
if s.length() == 0 {
return s
}
let first = first_char(s)
let upper = to_upper_char(first)
let buf = StringBuilder()
buf.write_char(upper)
let mut passed_first = false
for ch in s {
if !passed_first {
passed_first = true
} else {
buf.write_char(ch)
}
}
buf.to_string()
}
///|
/// Convert a string to lowercase using StringBuilder.
fn to_lower_str(s : String) -> String {
let buf = StringBuilder()
for ch in s {
if ch >= 'A' && ch <= 'Z' {
buf.write_char(Int::unsafe_to_char(ch.to_int() + 32))
} else {
buf.write_char(ch)
}
}
buf.to_string()
}
///|
/// Split a camelCase/PascalCase/kebab-case name into lowercase words.
pub fn words(name : String) -> Array[String] {
let out : Array[String] = []
let mut buf = StringBuilder()
let mut prev_lower = false
for ch in name {
if is_alnum(ch) {
if prev_lower && is_upper(ch) {
let word = buf.to_string()
if word.length() > 0 {
out.push(to_lower_str(word))
}
buf = StringBuilder()
}
buf.write_char(ch)
prev_lower = !is_upper(ch) && !is_digit(ch)
} else {
let word = buf.to_string()
if word.length() > 0 {
out.push(to_lower_str(word))
}
buf = StringBuilder()
prev_lower = false
}
}
let word = buf.to_string()
if word.length() > 0 {
out.push(to_lower_str(word))
}
if out.length() == 0 {
out.push("value")
}
out
}
///|
/// Convert to snake_case.
pub fn snake(name : String) -> String {
let parts = words(name)
let candidate = join_words(parts, "_")
if reserved.contains(candidate) {
candidate + "_"
} else {
candidate
}
}
///|
/// Convert to PascalCase.
pub fn pascal(name : String) -> String {
let parts = words(name)
let mut candidate = ""
for part in parts {
if part.length() > 0 {
candidate = candidate + first_upper(part)
}
}
if reserved.contains(candidate) {
candidate + "Value"
} else {
candidate
}
}
///|
/// Return `base`, or the first free `base_N`.
pub fn unique(base : String, taken : Map[String, Bool]) -> String {
if !taken.contains(base) {
taken[base] = true
return base
}
let mut i = 2
let mut candidate = base + "_" + i.to_string()
while taken.contains(candidate) {
i = i + 1
candidate = base + "_" + i.to_string()
}
taken[candidate] = true
candidate
}
///|
/// OperationID to snake_case function name.
pub fn operation_fn_name(operation_id : String) -> String {
snake(operation_id)
}