use std::collections::BTreeSet;
use crate::parser::{RawLocalVar, RawProto, RawString};
pub(super) fn alphabetical_name(index: usize) -> Option<String> {
const NAMES: &[&str] = &[
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m", "n", "p", "q", "r", "s", "t",
"u", "v", "w", "x", "y", "z",
];
NAMES.get(index).map(|name| (*name).to_owned())
}
pub(super) fn as_valid_name(value: &Option<String>) -> Option<String> {
value.as_deref().and_then(normalize_identifier)
}
pub(super) fn normalize_identifier(candidate: &str) -> Option<String> {
if candidate.is_empty() {
return None;
}
if is_valid_identifier(candidate) {
return Some(candidate.to_owned());
}
let mut normalized = String::with_capacity(candidate.len());
for ch in candidate.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' {
normalized.push(ch);
} else if !normalized.ends_with('_') {
normalized.push('_');
}
}
let normalized = normalized.trim_matches('_');
if normalized.is_empty() {
return None;
}
let mut result = normalized.to_owned();
if result
.chars()
.next()
.is_some_and(|first| first.is_ascii_digit())
{
result.insert(0, '_');
}
if is_valid_identifier(&result) {
Some(result)
} else {
None
}
}
pub(super) fn is_valid_identifier(candidate: &str) -> bool {
let mut chars = candidate.chars();
let Some(first) = chars.next() else {
return false;
};
if !(first.is_ascii_alphabetic() || first == '_') {
return false;
}
chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}
pub(super) fn is_lua_keyword(candidate: &str) -> bool {
matches!(
candidate,
"and"
| "break"
| "do"
| "else"
| "elseif"
| "end"
| "false"
| "for"
| "function"
| "goto"
| "if"
| "in"
| "local"
| "nil"
| "not"
| "or"
| "repeat"
| "return"
| "then"
| "true"
| "until"
| "while"
| "global"
)
}
pub(super) fn lua_keywords() -> BTreeSet<String> {
[
"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if",
"in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while",
"global",
]
.into_iter()
.map(str::to_owned)
.collect()
}
pub(super) fn debug_local_name_for_reg_at_pc(
proto: &RawProto,
reg: usize,
pc: u32,
) -> Option<String> {
proto
.common
.debug_info
.common
.local_vars
.iter()
.filter(|local| debug_local_is_active_at_pc(local, pc))
.nth(reg)
.map(|local| decode_raw_string(&local.name))
}
fn debug_local_is_active_at_pc(local: &RawLocalVar, pc: u32) -> bool {
local.start_pc <= pc && pc < local.end_pc
}
pub(super) fn decode_raw_string(raw: &RawString) -> String {
raw.text
.as_ref()
.map(|text| text.value.clone())
.unwrap_or_else(|| String::from_utf8_lossy(&raw.bytes).into_owned())
}