Skip to main content

rustpython_common/encodings/
raw_unicode_escape.rs

1//! `raw-unicode-escape` encode / incremental decode.
2
3use super::*;
4use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
5
6pub const ENCODING_NAME: &str = "rawunicodeescape";
7
8/// Encode `s`. Code points below 0x100 stay as a Latin-1 byte; the rest
9/// become `\uXXXX` / `\UXXXXXXXX`.
10#[must_use]
11pub fn encode_bytes(s: &Wtf8) -> Vec<u8> {
12    let mut out = Vec::new();
13    for cp in s.code_points() {
14        let v = cp.to_u32();
15        if v < 0x100 {
16            out.push(v as u8);
17        } else if v < 0x10000 {
18            out.extend_from_slice(format!("\\u{v:04x}").as_bytes());
19        } else {
20            out.extend_from_slice(format!("\\U{v:08x}").as_bytes());
21        }
22    }
23    out
24}
25
26pub fn encode<Ctx, E>(ctx: Ctx, _errors: &E) -> Result<Vec<u8>, Ctx::Error>
27where
28    Ctx: EncodeContext,
29    E: EncodeErrorHandler<Ctx>,
30{
31    Ok(encode_bytes(ctx.remaining_data()))
32}
33
34pub fn decode<Ctx, E>(
35    mut ctx: Ctx,
36    errors: &E,
37    final_decode: bool,
38) -> Result<(Wtf8Buf, usize), Ctx::Error>
39where
40    Ctx: DecodeContext,
41    E: DecodeErrorHandler<Ctx>,
42{
43    let mut out = Wtf8Buf::new();
44    loop {
45        let rest = ctx.remaining_data();
46        if rest.is_empty() {
47            break;
48        }
49        let b = rest[0];
50        if b != b'\\' {
51            out.push_char(b as char);
52            ctx.advance(1);
53            continue;
54        }
55        let kind = rest.get(1).copied();
56        let want = match kind {
57            Some(b'u') => 4usize,
58            Some(b'U') => 8usize,
59            _ => 0,
60        };
61        if want != 0 {
62            let escape_start = ctx.position();
63            let digits_start = 2;
64            if !final_decode && rest.len() < digits_start + want {
65                break;
66            }
67            let available = (digits_start + want).min(rest.len());
68            let mut hex_end = digits_start;
69            while hex_end < available && rest[hex_end].is_ascii_hexdigit() {
70                hex_end += 1;
71            }
72            let numeric = if available == digits_start + want && hex_end == available {
73                core::str::from_utf8(&rest[digits_start..available])
74                    .ok()
75                    .and_then(|s| u32::from_str_radix(s, 16).ok())
76            } else {
77                None
78            };
79            if let Some(c) = numeric.and_then(CodePoint::from_u32) {
80                out.push(c);
81                ctx.advance(available);
82                continue;
83            }
84            let error_end = ctx.position()
85                + if numeric.is_some() {
86                    available
87                } else {
88                    hex_end
89                };
90            let reason = if numeric.is_some() {
91                "illegal Unicode character"
92            } else if want == 4 {
93                "truncated \\uXXXX escape"
94            } else {
95                "truncated \\UXXXXXXXX escape"
96            };
97            let replace = ctx.handle_error(errors, escape_start..error_end, Some(reason))?;
98            out.push_wtf8(replace.as_ref());
99            continue;
100        }
101        if kind.is_none() && !final_decode {
102            break;
103        }
104        out.push_char(b as char);
105        ctx.advance(1);
106        if let Some(next) = kind {
107            out.push_char(next as char);
108            ctx.advance(1);
109        }
110    }
111    Ok((out, ctx.position()))
112}