///|
/// The words that MoonBit 0.10.15 refuses or reserves as identifiers. I found
/// them with the compiler: each is an error or a `reserved_keyword` warning.
let moonbit_keywords : Array[String] = [
"abstract", "alias", "and", "as", "assert", "async", "await", "break", "catch",
"const", "continue", "declare", "defer", "derive", "do", "dyn", "else", "enum",
"enumview", "export", "extern", "false", "final", "fn", "fnalias", "for", "guard",
"if", "impl", "import", "in", "include", "is", "lazy", "let", "letrec", "lexmatch",
"local", "loop", "macro", "match", "member", "method", "module", "move", "mut",
"namespace", "nobreak", "noraise", "override", "package", "priv", "protected",
"pub", "raise", "readonly", "recur", "ref", "resume", "return", "sealed", "static",
"struct", "suberror", "super", "test", "throw", "trait", "traitalias", "true",
"try", "type", "typealias", "typeof", "unsafe", "use", "using", "var", "virtual",
"void", "volatile", "where", "while", "with", "yield",
]
///|
/// The type and trait names that the generated code uses from the core
/// library. A shape with one of these names gets a `_` suffix.
let moonbit_type_names : Array[String] = [
"Array", "Bool", "Byte", "Bytes", "Char", "Double", "Error", "Float", "FixedArray",
"Int", "Int16", "Int64", "Json", "Map", "Option", "Result", "Self", "Set", "String",
"StringBuilder", "UInt", "UInt16", "UInt64", "Unit", "Compare", "Debug", "Default",
"Eq", "FromJson", "Hash", "Logger", "Show", "ToJson",
]
///|
/// The words of a name. A word ends at `_`, `-`, `.`, `$`, at a change from a
/// lower-case letter or a digit to an upper-case letter, and before the last
/// upper-case letter of a run that a lower-case letter follows (`HTTPServer` is
/// `HTTP` and `Server`).
fn words(name : String) -> Array[String] {
let chars = name.to_array()
let out : Array[String] = []
let current = StringBuilder()
let flush = () => {
let word = current.to_string()
if !word.is_empty() {
out.push(word)
}
current.reset()
}
for index, c in chars {
if c == '_' || c == '-' || c == '.' || c == '$' || c == ' ' || c == '/' {
flush()
continue
}
if c.is_ascii_uppercase() && index > 0 {
let before = chars[index - 1]
let after_lower = index + 1 < chars.length() &&
chars[index + 1].is_ascii_lowercase()
if before.is_ascii_lowercase() ||
before.is_ascii_digit() ||
(before.is_ascii_uppercase() && after_lower) {
flush()
}
}
current.write_char(c)
}
flush()
out
}
///|
fn capitalize(word : String) -> String {
let chars = word.to_lower().to_array()
if chars.is_empty() {
return ""
}
chars[0] = chars[0].to_ascii_uppercase()
String::from_array(chars)
}
///|
/// A `snake_case` name for a field, a function or a method. A keyword gets a
/// `_` suffix. A name that starts with a digit gets an `n` prefix.
pub fn snake_name(name : String) -> String {
let text = words(name).map(word => word.to_lower()).join("_")
let text = if text.is_empty() { "value" } else { text }
let text = if text.to_array()[0].is_ascii_digit() { "n" + text } else { text }
if moonbit_keywords.contains(text) {
text + "_"
} else {
text
}
}
///|
/// A `PascalCase` name for an enum or a union variant. `INSERT_CARD` is
/// `InsertCard`, `manualEntry` is `ManualEntry` and `USD` is `Usd`.
pub fn variant_name(name : String) -> String {
let text = words(name).map(capitalize).join("")
let text = if text.is_empty() { "Value" } else { text }
if text.to_array()[0].is_ascii_digit() {
"V" + text
} else {
text
}
}
///|
/// The name of a generated type: the contextual name with an upper-case first
/// letter. A name that the core library uses gets a `_` suffix.
pub fn type_name(name : String) -> String {
let chars = name.to_array()
let text = if chars.is_empty() {
"Value"
} else {
chars[0] = chars[0].to_ascii_uppercase()
String::from_array(chars)
}
if moonbit_type_names.contains(text) {
text + "_"
} else {
text
}
}
///|
/// The text of a MoonBit string literal.
fn mbt_string(text : String) -> String {
let out = StringBuilder()
out.write_char('"')
for c in text {
match c {
'"' => out.write_string("\\\"")
'\\' => out.write_string("\\\\")
'\n' => out.write_string("\\n")
'\r' => out.write_string("\\r")
'\t' => out.write_string("\\t")
c if c.to_int() < 0x20 => {
let hex = c.to_int().to_string(radix=16)
out.write_string("\\u{" + hex + "}")
}
c => out.write_char(c)
}
}
out.write_char('"')
out.to_string()
}
///|
/// One JSON Pointer token (RFC 6901): `~` is `~0` and `/` is `~1`.
fn pointer_token(name : String) -> String {
name.replace_all(old="~", new="~0").replace_all(old="/", new="~1")
}