ccarp 0.1.2

(trans)Compile C And Rust Partially
Documentation
//! Rust Primitive Structures
//! 
//! This module includes definitions associated with primitive structures.
//! 
//! Important definitions:
//! - `RIdent`, which signifies a Rust identifier
//! - `TypedIdent`, which signifies a typed Rust identifier
//! - `RLit`, which signifies a Rust literal
use std::fmt::Display;

use crate::{ccarp::error::{c2rust_err, CCErr, Result}, ccarp_c::tt::{Identifier, Literal}};

use super::{defs::{Context, RFrom}, rustdecl::RType};

/// Rust Identifier
/// 
/// These can be:
/// - Variables
/// - Method names
/// - Function names
/// - Field names
/// - etc.
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct RIdent(pub String);
impl From<Identifier> for RIdent {
    fn from(value: Identifier) -> Self {
        Self(rustify_ident(&value.0))
    }
}
impl Display for RIdent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f,"{}",self.0)
    }
}

pub type IsGlobal=bool;

/// Rust Identifier with a Type
/// 
/// A Typed Rust Identifier signifying a variable (or function declaration)
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct TypedIdent(pub RIdent, pub Box<RType>, pub IsGlobal);
impl RFrom<RIdent> for TypedIdent {
    fn rfrom(value: RIdent, context: &mut Context) -> Result<Self> {
        let ty=context.get_variable_ty(&value.0).ok_or_else(|| c2rust_err!("Could not find type of variable '{}' inside context!",value.0))?;
        let is_global=context.is_variable_mutable_global(&value.0);
        Ok(Self(value, Box::new(ty), is_global))
    }
}
impl Display for TypedIdent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f,"{}",self.0)
    }
}

fn rustify_ident(ident: &str) -> String {
    let ident=ident.trim();
    if ident=="_" { "_0".to_string() }
    else { ident.to_string() }
}

/// Rust Literal with a Type
/// 
/// These can be:
/// - Int literals
/// - Floating Point Literals
/// - or String literals
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum RLit {
    Int(String,RType),
    Float(String,RType),
    String(String,RType)
}
impl From<Literal> for RLit {
    fn from(value: Literal) -> Self {
        match value {
            Literal::Int(int) => { let (lit,ty)=rustify_int(&int); Self::Int(lit,ty) },
            Literal::Float(float) => { let (lit,ty)=rustify_float(&float); Self::Float(lit,ty) },
            Literal::Char(chr) => { let (lit,ty)=rustify_char(&chr); Self::String(lit,ty) },
            Literal::String(string) => { let (lit,ty)=rustify_string(&string); Self::String(lit,ty) },
        }
    }
}
impl Display for RLit {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Int(int,_) => write!(f,"{int}"),
            Self::Float(float,_) => write!(f,"{float}"),
            Self::String(string,_) => write!(f,"{string}"),
        }
    }
}

// This is not working as specified in the ISO
fn rustify_int(int: &str) -> (String,RType) {
    let int=int.trim();
    let mut out=int.replace('U', "u").replace('L', "l");    // case insensitivity
    if out.starts_with("0x") || int.starts_with("0X") { out=out.replacen("0X", "0x", 1); }  // hexadecimal
    else if out.starts_with('0') && !["0","0u","0l","0ll","0ul","0lu","0ull","0llu"].contains(&out.as_str()) { out=out.replacen('0', "0o", 1); } // octal
    let ty=
    if out.contains('l') {
        out=out.replace('l', "");
        if out.contains('u') {
            out=out.replace('u', "");
            out+="u64";
            RType::U64  // lu / llu
        }
        else { 
            out+="i64";
            RType::I64  // l / ll
        }
    }
    else if out.contains('u') {
        out=out.replace('u', "");
        out+="u32";
        RType::U32      // u
    }
    else { RType::I32 };// no postfix
    (out,ty)
}

// No long double
#[allow(clippy::cast_precision_loss)]
fn rustify_float(float: &str) -> (String,RType) {
    fn hex_to_int(h: char) -> isize {
        isize::from_str_radix(h.to_string().as_str(), 16).unwrap_or_default()
    }
    let mut out=float.trim().replace('F', "f").replace('L', "l");  // case insensitivity;
    let ty=
    if out.starts_with("0x") || out.starts_with("0X") { // hexadecimal float
        out=out[2..].replace('P', "p"); // remove prefix
        let suffix={
            if out.contains('f') { out=out.replace('f', ""); "f32"}         // f -> f32
            else if out.contains('l') { out=out.replace('l', ""); "f64"}    // l -> f128, but there is no such type in Rust => f64
            else { "" } // derived type (most likely f64)
        };
        let mut res=0.0;
        let mut sp=out.split('p');
        let num=sp.next().unwrap_or_default();
        let mut sp2=num.split('.');
        let whole=sp2.next().unwrap_or_default().replace("0x","");  // whole part
        if !whole.is_empty() { res=isize::from_str_radix(whole.as_str(), 16).unwrap_or_default() as f64; }
        let frac=sp2.next();    // fractional part
        if let Some(frac)=frac {
            let mut div=1.0;
            for h in frac.chars() {
                div*=16.0;
                res+=hex_to_int(h) as f64/div;
            }
        }
        let exp=sp.next();  // exponent
        if let Some(exp)=exp {
            let exp=str::parse::<isize>(exp).unwrap_or_default();
            res*=f64::powf(2.0, exp as f64);
        }
        out=format!("{res}{suffix}");
        if suffix.contains("f32") { RType::F32 }    // f32
        else { RType::F64 }                         // f64
    }
    else {
        if out.starts_with('.') { out.insert(0, '0'); }
        if !out.contains('.') { out.push('.'); }
        out=out.replace('f', "f32").replace('l', "f64");    // suffix replacemenet
        if out.contains("f32") { RType::F32 }   // f32
        else { RType::F64 }                     // f64
    };
    (out,ty)
}

// Only simple escape sequences are implemented (no octal or hexadecimal)
// No wide char
fn rustify_char(ch: &str) -> (String,RType) {
    fn chr_to_hex(ch: char) -> String {
        format!("{:x}", ch as u8)
    }
    let ch=ch[1..ch.len()-1].replace("\\v", "\\x0b").replace("\\?", "?").replace("\\a", "\\x07").replace("\\b", "\\x08").replace("\\f", "\\x0c");
    if ch.len()==1 || ch.starts_with('\\') && ch.len()==2 || ch.starts_with("\\x") && ch.len()==4 { (format!("b'{ch}'"),RType::U8) }
    else {
        let mut out="0x".to_string();
        for c in ch.chars() {
            out+=chr_to_hex(c).as_str();
        }
        (out,RType::I32)
    }
}

// Only simple escape sequences are implemented (no octal or hexadecimal)
// No wide string
fn rustify_string(s: &str) -> (String,RType) {
    let out=s.replace("\\v", "\\x0b").replace("\\?", "?").replace("\\a", "\\x07").replace("\\b", "\\x08").replace("\\f", "\\x0c");
    (format!("&mut b{out}.to_vec()[..]"),RType::RefMut(vec![],Box::new(RType::U8)))
}