use crate::double::Double;
use crate::vm::{VmError, VmResult};
pub fn parse_num(bytes: &[u8], base: u32) -> Option<usize> {
if bytes.is_empty() {
return None;
}
if bytes.len() == 3 && bytes[0] == b'\'' && bytes[2] == b'\'' {
return Some(bytes[1] as usize);
}
let (base, rest) = if let Some((&b'#', rest)) = bytes.split_first() {
(10u32, rest)
} else if let Some((&b'$', rest)) = bytes.split_first() {
(16u32, rest)
} else if let Some((&b'%', rest)) = bytes.split_first() {
(2u32, rest)
} else {
(base, bytes)
};
let (neg, rest) = if let Some((&b'-', rest)) = rest.split_first() {
(true, rest)
} else {
(false, rest)
};
if rest.is_empty() {
return None;
}
let (acc, rest) = to_number(Double(0), rest, base);
if !rest.is_empty() {
return None;
}
if neg {
Some(acc.0.wrapping_neg() as usize)
} else {
Some(acc.0 as usize)
}
}
pub fn parse(src: &[u8], delim: u8) -> (usize, usize) {
let is_delim = |c: u8| {
if delim == b' ' {
c.is_ascii_whitespace()
} else {
c == delim
}
};
let len = src.iter().position(|&c| is_delim(c)).unwrap_or(src.len());
let read = if len < src.len() { len + 1 } else { len };
(read, len)
}
pub fn parse_escaped(src: &[u8], dst: &mut [u8]) -> VmResult<(usize, usize)> {
let mut input_pos = 0;
let mut output_pos = 0;
let mut put = |i: &mut usize, c: u8| -> VmResult<()> {
*dst.get_mut(*i).ok_or(VmError::ParsedStringOverflow)? = c;
*i += 1;
Ok(())
};
while input_pos < src.len() {
match src[input_pos] {
b'"' => {
input_pos += 1;
break;
}
b'\\' => {
input_pos += 1;
let c = *src.get(input_pos).ok_or(VmError::InvalidEscape(b'\\'))?;
input_pos += 1;
match c {
b'a' => put(&mut output_pos, 0x07)?, b'b' => put(&mut output_pos, 0x08)?, b'e' => put(&mut output_pos, 0x1b)?, b'f' => put(&mut output_pos, 0x0c)?, b'l' => put(&mut output_pos, b'\n')?, b'm' => {
put(&mut output_pos, 0x0d)?; put(&mut output_pos, 0x0a)?; }
b'n' => put(&mut output_pos, 0x0a)?, b'q' | b'"' => put(&mut output_pos, b'"')?, b'r' => put(&mut output_pos, b'\r')?, b't' => put(&mut output_pos, b'\t')?, b'v' => put(&mut output_pos, 0x0b)?, b'z' => put(&mut output_pos, b'\0')?, b'\\' => put(&mut output_pos, b'\\')?,
b'x' => {
let hex = &src[input_pos..src.len().min(input_pos + 2)];
let (n, rest) = to_number(Double(0), hex, 16);
if hex.len() - rest.len() != 2 {
return Err(VmError::InvalidEscape(b'x'));
}
input_pos += 2;
put(&mut output_pos, n.0 as u8)?;
}
_ => return Err(VmError::InvalidEscape(c)),
}
}
b => {
put(&mut output_pos, b)?;
input_pos += 1;
}
}
}
Ok((input_pos, output_pos))
}
pub fn to_number(mut acc: Double, bytes: &[u8], base: u32) -> (Double, &[u8]) {
let digit = |c: u8| -> Option<u32> {
let d = match c {
b'0'..=b'9' => u32::from(c - b'0'),
b'a'..=b'z' => u32::from(c - b'a') + 10,
b'A'..=b'Z' => u32::from(c - b'A') + 10,
_ => return None,
};
(d < base).then_some(d)
};
let mut rest = bytes;
while let Some((&c, tail)) = rest.split_first() {
let Some(d) = digit(c) else { break };
acc = Double(acc.0 * Double::from(base).0 + Double::from(d).0);
rest = tail;
}
(acc, rest)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_num_char() {
assert_eq!(parse_num(b"' '", 10), Some(0x20));
}
#[test]
fn parse_num_decimal() {
assert_eq!(parse_num(b"10", 10), Some(10));
assert_eq!(parse_num(b"#10", 8), Some(10));
assert_eq!(parse_num(b"-10", 10), Some((-10isize) as usize));
}
#[test]
fn parse_num_hex() {
assert_eq!(parse_num(b"$10", 10), Some(16));
assert_eq!(parse_num(b"ff", 16), Some(255));
}
#[test]
fn parse_num_binary() {
assert_eq!(parse_num(b"%10", 10), Some(2));
}
#[test]
fn parse_num_invalid() {
assert_eq!(parse_num(b"", 10), None);
assert_eq!(parse_num(b"-", 10), None);
assert_eq!(parse_num(b"foo", 10), None);
assert_eq!(parse_num(b"$", 10), None);
}
#[test]
fn to_number_decimal() {
assert_eq!(to_number(Double(0), b"123", 10), (Double(123), &b""[..]));
}
#[test]
fn to_number_empty() {
assert_eq!(to_number(Double(42), b"", 10), (Double(42), &b""[..]));
}
#[test]
fn to_number_partial() {
assert_eq!(to_number(Double(0), b"10z", 10), (Double(10), &b"z"[..]));
}
#[test]
fn to_number_continues() {
assert_eq!(
to_number(Double(0xff), b"ff", 16),
(Double(0xffff), &b""[..])
);
}
#[test]
fn to_number_case_folding() {
assert_eq!(to_number(Double(0), b"abc", 16), (Double(0xabc), &b""[..]));
assert_eq!(to_number(Double(0), b"ABC", 16), (Double(0xabc), &b""[..]));
}
#[test]
fn parse_stops_at_delimiter() {
assert_eq!(parse(b"abc def", b' '), (4, 3));
}
#[test]
fn parse_no_delimiter() {
assert_eq!(parse(b"abc", b' '), (3, 3));
}
#[test]
fn parse_empty() {
assert_eq!(parse(b"", b' '), (0, 0));
}
#[test]
fn parse_leading_delimiter() {
assert_eq!(parse(b" abc", b' '), (1, 0));
}
#[test]
fn parse_custom_delimiter() {
assert_eq!(parse(br#"abc"xyz"#, b'"'), (4, 3));
}
#[test]
fn parse_space_folds_whitespace() {
assert_eq!(parse(b"abc\tdef", b' '), (4, 3));
assert_eq!(parse(b"abc\ndef", b' '), (4, 3));
}
#[test]
fn parse_custom_delimiter_keeps_whitespace() {
assert_eq!(parse(br#"a b"c"#, b'"'), (4, 3));
}
#[test]
fn parse_escaped_literal() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"abc"xyz"#, &mut dst), Ok((4, 3)));
assert_eq!(&dst[..3], b"abc");
}
#[test]
fn parse_escaped_alert() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\a""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x07]);
}
#[test]
fn parse_escaped_backspace() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\b""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x08]);
}
#[test]
fn parse_escaped_escape() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\e""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x1b]);
}
#[test]
fn parse_escaped_form_feed() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\f""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x0c]);
}
#[test]
fn parse_escaped_line_feed() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\l""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x0a]);
}
#[test]
fn parse_escaped_crlf() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\m""#, &mut dst), Ok((3, 2)));
assert_eq!(&dst[..2], &[0x0d, 0x0a]);
}
#[test]
fn parse_escaped_newline() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\n""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x0a]);
}
#[test]
fn parse_escaped_quote() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\q""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], b"\"");
assert_eq!(parse_escaped(br#"\""""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], b"\"");
}
#[test]
fn parse_escaped_carriage_return() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\r""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x0d]);
}
#[test]
fn parse_escaped_tab() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\t""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x09]);
}
#[test]
fn parse_escaped_vertical_tab() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\v""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x0b]);
}
#[test]
fn parse_escaped_nul() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\z""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], &[0x00]);
}
#[test]
fn parse_escaped_backslash() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\\""#, &mut dst), Ok((3, 1)));
assert_eq!(&dst[..1], b"\\");
}
#[test]
fn parse_escaped_hex() {
let mut dst = [0u8; 8];
assert_eq!(parse_escaped(br#"\x41""#, &mut dst), Ok((5, 1)));
assert_eq!(&dst[..1], b"A");
}
#[test]
fn parse_escaped_unknown_escape() {
let mut dst = [0u8; 8];
assert_eq!(
parse_escaped(br#"\y""#, &mut dst),
Err(VmError::InvalidEscape(b'y'))
);
}
#[test]
fn parse_escaped_trailing_backslash() {
let mut dst = [0u8; 8];
assert_eq!(
parse_escaped(b"\\", &mut dst),
Err(VmError::InvalidEscape(b'\\'))
);
}
#[test]
fn parse_escaped_short_hex() {
let mut dst = [0u8; 8];
assert_eq!(
parse_escaped(br#"\x4""#, &mut dst),
Err(VmError::InvalidEscape(b'x'))
);
}
#[test]
fn parse_escaped_overflow() {
let mut dst = [0u8; 2];
assert_eq!(
parse_escaped(br#"abcd""#, &mut dst),
Err(VmError::ParsedStringOverflow)
);
}
}