unluac 1.3.1

Multi-dialect Lua decompiler written in Rust.
Documentation
//! Lua 字符串字面量的字节值。
//!
//! Lua string 是任意字节序列,不等同于 Rust `String`。HIR 之后的表达式层必须保留
//! 原始字节;只有在需要生成字段名、全局名或调试名这类文本身份时,才临时尝试 UTF-8
//! 视图。

use std::hash::{Hash, Hasher};

use crate::parser::{RawString, StringEncoding};

#[derive(Debug, Clone, Default)]
pub struct LuaString {
    bytes: Vec<u8>,
    text: Option<String>,
    encoding: Option<StringEncoding>,
}

impl PartialEq for LuaString {
    fn eq(&self, other: &Self) -> bool {
        self.bytes == other.bytes
    }
}

impl Eq for LuaString {}

impl PartialOrd for LuaString {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for LuaString {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.bytes.cmp(&other.bytes)
    }
}

impl Hash for LuaString {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.bytes.hash(state);
    }
}

impl LuaString {
    pub fn from_raw(raw: &RawString) -> Self {
        Self {
            bytes: raw.bytes.clone(),
            text: raw.text.as_ref().map(|text| text.value.clone()),
            encoding: raw.text.as_ref().map(|text| text.encoding),
        }
    }

    pub fn from_bytes(bytes: Vec<u8>) -> Self {
        Self {
            bytes,
            text: None,
            encoding: None,
        }
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.bytes
    }

    pub fn decoded_text(&self) -> Option<&str> {
        self.text.as_deref()
    }

    pub fn preferred_text(&self) -> Option<&str> {
        match self.encoding {
            // auto 检测到 windows-1252 时常只是给任意单字节高位数据一个展示视图;
            // 这里保留 byte escape,避免把二进制字符串改成 UTF-8 文本。
            Some(StringEncoding::EncodingRs(enc))
                if std::ptr::eq(enc, encoding_rs::WINDOWS_1252) =>
            {
                None
            }
            _ => self.decoded_text(),
        }
    }

    pub fn as_utf8(&self) -> Option<&str> {
        std::str::from_utf8(&self.bytes).ok()
    }

    pub fn debug_literal(&self) -> String {
        self.preferred_text()
            .or_else(|| self.as_utf8())
            .map_or_else(
                || {
                    let bytes = self
                        .bytes
                        .iter()
                        .map(|byte| format!("{byte:02x}"))
                        .collect::<Vec<_>>()
                        .join(" ");
                    format!("<{} bytes: {bytes}>", self.bytes.len())
                },
                |text| format!("{text:?}"),
            )
    }
}

impl From<&str> for LuaString {
    fn from(value: &str) -> Self {
        Self {
            bytes: value.as_bytes().to_vec(),
            text: Some(value.to_owned()),
            encoding: Some(StringEncoding::Utf8),
        }
    }
}

impl From<String> for LuaString {
    fn from(value: String) -> Self {
        Self {
            bytes: value.as_bytes().to_vec(),
            text: Some(value),
            encoding: Some(StringEncoding::Utf8),
        }
    }
}