neyuki 0.6.1

A small Lua-like scripting language implemented in Rust
//! Literal values.

use num_bigint::BigInt;
use std::fmt;

#[derive(Clone, Debug)]
pub enum Literal {
    Nil,
    Bool(bool),
    Int(BigInt),
    Float(f64),
    String(String),
}

impl PartialEq for Literal {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Nil, Self::Nil) => true,
            (Self::Bool(a), Self::Bool(b)) => a == b,
            (Self::Int(a), Self::Int(b)) => a == b,
            (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
            (Self::String(a), Self::String(b)) => a == b,
            _ => false,
        }
    }
}

impl Eq for Literal {}

impl Literal {
    pub fn parse_number(s: &str) -> Option<Self> {
        let normalized = s.replace('_', "");
        let integer = if let Some(hex) = normalized
            .strip_prefix("0x")
            .or_else(|| normalized.strip_prefix("0X"))
        {
            BigInt::parse_bytes(hex.as_bytes(), 16)
        } else if let Some(bin) = normalized
            .strip_prefix("0b")
            .or_else(|| normalized.strip_prefix("0B"))
        {
            BigInt::parse_bytes(bin.as_bytes(), 2)
        } else if let Some(oct) = normalized
            .strip_prefix("0o")
            .or_else(|| normalized.strip_prefix("0O"))
        {
            BigInt::parse_bytes(oct.as_bytes(), 8)
        } else if !normalized.contains('.')
            && !normalized.contains('e')
            && !normalized.contains('E')
        {
            BigInt::parse_bytes(normalized.as_bytes(), 10)
        } else {
            None
        };
        if let Some(bi) = integer {
            Some(Literal::Int(bi))
        } else if let Ok(f) = normalized.parse::<f64>() {
            Some(Literal::Float(f))
        } else {
            None
        }
    }

    pub fn is_nil(&self) -> bool {
        matches!(self, Literal::Nil)
    }

    pub fn is_truthy(&self) -> bool {
        match self {
            Literal::Nil => false,
            Literal::Bool(b) => *b,
            _ => true,
        }
    }

    pub fn type_name(&self) -> &'static str {
        match self {
            Literal::Nil => "nil",
            Literal::Bool(_) => "bool",
            Literal::Int(_) => "int",
            Literal::Float(_) => "float",
            Literal::String(_) => "string",
        }
    }

    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Literal::Bool(b) => Some(*b),
            _ => None,
        }
    }

    pub fn as_int(&self) -> Option<&BigInt> {
        match self {
            Literal::Int(i) => Some(i),
            _ => None,
        }
    }

    pub fn as_float(&self) -> Option<f64> {
        match self {
            Literal::Float(f) => Some(*f),
            _ => None,
        }
    }

    pub fn as_str(&self) -> Option<&str> {
        match self {
            Literal::String(s) => Some(s),
            _ => None,
        }
    }
}

impl fmt::Display for Literal {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Literal::Nil => write!(f, "nil"),
            Literal::Bool(b) => write!(f, "{}", b),
            Literal::Int(i) => write!(f, "{}", i),
            Literal::Float(fl) => write!(f, "{}", fl),
            Literal::String(s) => write!(f, "\"{}\"", s.escape_debug()),
        }
    }
}