geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use ecow::EcoString;
use jiter::{JiterError, JiterErrorType, JsonErrorType};

#[derive(Debug, PartialEq, Eq)]
pub(super) enum DecodeFailure {
    EndOfInput,
    Byte(EcoString),
    Sequence(EcoString),
}

impl DecodeFailure {
    pub(super) fn from_jiter(input: &[u8], error: JiterError) -> Self {
        match error.error_type {
            JiterErrorType::JsonError(
                JsonErrorType::EofWhileParsingList
                | JsonErrorType::EofWhileParsingObject
                | JsonErrorType::EofWhileParsingString
                | JsonErrorType::EofWhileParsingValue
                | JsonErrorType::UnexpectedEndOfHexEscape,
            ) => Self::EndOfInput,
            JiterErrorType::JsonError(
                JsonErrorType::InvalidEscape
                | JsonErrorType::InvalidUnicodeCodePoint
                | JsonErrorType::LoneLeadingSurrogateInHexEscape,
            ) => Self::Sequence(unicode_sequence(input, error.index)),
            _ => match input.get(error.index).copied() {
                Some(byte) => Self::Byte(format!("0x{byte:02X}").into()),
                None => Self::EndOfInput,
            },
        }
    }

    pub(super) fn overflow(number: &[u8]) -> Self {
        Self::Sequence(normalize_number(number))
    }
}

fn unicode_sequence(input: &[u8], index: usize) -> EcoString {
    let search_end = index.min(input.len());
    let start = (0..search_end)
        .rev()
        .find(|start| input[*start] == b'\\' && input.get(*start + 1) == Some(&b'u'))
        .unwrap_or(search_end);
    let end = (start + 6).min(input.len());
    String::from_utf8_lossy(&input[start..end])
        .into_owned()
        .into()
}

fn normalize_number(number: &[u8]) -> EcoString {
    let input = String::from_utf8_lossy(number);
    let normalized = input.replace('E', "e");
    let Some(exponent_index) = normalized.find('e') else {
        return normalized.into();
    };
    let (mantissa, exponent) = normalized.split_at(exponent_index);
    if mantissa.contains('.') {
        normalized.into()
    } else {
        format!("{mantissa}.0{exponent}").into()
    }
}

#[cfg(test)]
mod tests {
    use super::{DecodeFailure, normalize_number, unicode_sequence};
    use jiter::{JiterError, JiterErrorType, JsonErrorType};

    #[test]
    fn maps_jiter_errors_to_the_official_decode_error_families() {
        assert_eq!(
            DecodeFailure::from_jiter(
                b"[",
                JiterError {
                    error_type: JiterErrorType::JsonError(JsonErrorType::EofWhileParsingList),
                    index: 1,
                },
            ),
            DecodeFailure::EndOfInput,
        );
        assert_eq!(
            DecodeFailure::from_jiter(
                b"[}",
                JiterError {
                    error_type: JiterErrorType::JsonError(JsonErrorType::ExpectedSomeValue),
                    index: 1,
                },
            ),
            DecodeFailure::Byte("0x7D".into()),
        );
        assert_eq!(
            DecodeFailure::from_jiter(
                br#""\uxxxx""#,
                JiterError {
                    error_type: JiterErrorType::JsonError(JsonErrorType::InvalidEscape),
                    index: 4,
                },
            ),
            DecodeFailure::Sequence(r#"\uxxxx"#.into()),
        );
        assert_eq!(
            DecodeFailure::from_jiter(
                b"x",
                JiterError {
                    error_type: JiterErrorType::JsonError(JsonErrorType::ExpectedSomeValue),
                    index: 1,
                },
            ),
            DecodeFailure::EndOfInput,
        );
    }

    #[test]
    fn extracts_unicode_sequences_and_normalizes_overflow_numbers() {
        assert_eq!(unicode_sequence(br#""x\u12xx""#, 7), r#"\u12xx"#);
        assert_eq!(normalize_number(b"1e400"), "1.0e400");
        assert_eq!(normalize_number(b"-1E+400"), "-1.0e+400");
        assert_eq!(normalize_number(b"1.25e400"), "1.25e400");
        assert_eq!(normalize_number(b"123"), "123");
    }
}