Skip to main content

java_bin_name/
lib.rs

1//! Java binary name parser and writer.
2//!
3//! # Parsing
4//!
5//! Types who implemented `Parse` trait could be parsed from a string slice using [`parse`] function.
6//!
7//! Differences between JLS and class file representation will be handled automatically.
8//!
9//! ## Error Handling
10//!
11//! This crate won't handle naming errors unless necessary.
12//! Validate the string slices after parse if desired.
13//!
14//! # Writing
15//!
16//! Use `Display` or `ToString` for writing.
17//!
18//! # Supported Items
19//!
20//! - Classes or interfaces: [`ClassName`]
21//! - Methods: [`MethodDescriptor`]
22//! - Field types: [`FieldType`]
23//!
24//! - Type signatures: [`TypeSignature`]
25//! - Class signatures: [`ClassSignature`]
26//! - Method signatures: [`MethodSignature`]
27//! - Field signatures: [`FieldSignature`]
28
29#![no_std]
30
31extern crate alloc;
32
33mod class;
34mod method;
35mod ty;
36
37use core::{convert::Infallible, fmt::Debug};
38
39pub use class::*;
40pub use method::*;
41pub use ty::*;
42
43/// Signature of a field, formal parameter, local variable, or record component declaration.
44pub type FieldSignature<'a> = TypeSignature<'a>;
45
46/// Types that could be parsed from a borrowed string cursor.
47pub trait Parse<'s>: Sized {
48    /// The error type.
49    type Error;
50
51    /// Parse from the given string cursor.
52    #[allow(clippy::missing_errors_doc)]
53    fn parse_from(cursor: &mut Cursor<'s>) -> Result<Self, Self::Error>;
54}
55
56/// Representation form of a component across different contexts.
57#[non_exhaustive]
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub enum ReprForm {
60    /// JLS-specified standard binary name format.
61    JLS,
62    /// Internal representation used by class files.
63    ///
64    /// See [JVMS 4.2.1](https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.2.1)
65    /// for more information about internal form of a class, interface or package.
66    Internal,
67}
68
69impl ReprForm {
70    #[inline]
71    const fn package_separator(&self) -> char {
72        match self {
73            Self::JLS => '.',
74            Self::Internal => '/',
75        }
76    }
77}
78
79/// Cursor of a string slice.
80#[derive(Debug, Clone)]
81pub struct Cursor<'a>(&'a str);
82
83impl<'a> Cursor<'a> {
84    /// Creates a new string cursor from slice.
85    #[inline]
86    pub const fn new(s: &'a str) -> Self {
87        Self(s)
88    }
89
90    /// Reads a single character.
91    ///
92    /// # Panics
93    ///
94    /// Panics if there's no valid character left in this slice.
95    pub fn get_char(&mut self) -> char {
96        let c = self.0.chars().next().expect("end of file");
97        self.advance_by(c.len_utf8());
98        c
99    }
100
101    /// Advances the inner cursor with given function.
102    #[inline]
103    #[allow(clippy::missing_panics_doc)] // infalliable
104    pub fn advance<F, U>(&mut self, f: F) -> U
105    where
106        F: FnOnce(&'a str) -> (U, &'a str),
107    {
108        self.try_advance(|s| Result::<_, Infallible>::Ok(f(s)))
109            .unwrap()
110    }
111
112    /// Advances the inner cursor if successed.
113    ///
114    /// # Errors
115    ///
116    /// Returns an error if the given function returns one.
117    #[inline]
118    pub fn try_advance<F, U, Err>(&mut self, f: F) -> Result<U, Err>
119    where
120        F: FnOnce(&'a str) -> Result<(U, &'a str), Err>,
121    {
122        let (ret, leftover) = f(self.0)?;
123        self.0 = leftover;
124        Ok(ret)
125    }
126
127    /// Returns the underlying slice.
128    #[inline]
129    pub const fn get(&self) -> &'a str {
130        self.0
131    }
132
133    /// Overrides the underlying slice.
134    #[inline]
135    pub const fn set(&mut self, s: &'a str) {
136        self.0 = s;
137    }
138
139    /// Advances the inner cursor by `n` bytes.
140    ///
141    /// # Panics
142    ///
143    /// Panics if the inner slice doesn't has `n` remaining bytes.
144    #[inline]
145    pub fn advance_by(&mut self, n: usize) {
146        self.advance(|s| ((), &s[n..]))
147    }
148
149    /// Discards this cursor.
150    #[inline]
151    pub fn clear(&mut self) {
152        self.0 = "";
153    }
154}
155
156fn strip_digits_prefix(s: &str) -> (Option<u32>, &str) {
157    let mut chars = s.char_indices();
158    let (digits, last_index) = chars
159        .by_ref()
160        .map_while(|(index, c)| c.to_digit(10).map(|d| (index, d)))
161        .fold((0, None), |(n, _), (ci, d)| (n * 10 + d, Some(ci)));
162    let leftover = last_index.map_or(s, |i| &s[i + 1..]);
163    (last_index.map(|_| digits), leftover)
164}
165
166fn angle_safe_offset(src: &str, sep: &[char]) -> usize {
167    if let Some((a, b)) = src.split_once('<')
168        && !a.contains(sep)
169    {
170        let mut rem = b;
171        let mut layers = 1usize;
172        while layers > 0 {
173            let Some(pos) = rem.find(['<', '>']) else {
174                return 0;
175            };
176            match rem.as_bytes()[pos] {
177                b'<' => layers += 1,
178                b'>' => layers -= 1,
179                _ => unreachable!(),
180            }
181            rem = &rem[pos + 1..];
182        }
183        unsafe { rem.as_ptr().byte_offset_from_unsigned(src.as_ptr()) }
184    } else {
185        0
186    }
187}
188
189/// Split a string slice with given separator candidates, skipping paired angles.
190///
191/// The separator is conserved in the latter slice.
192fn angle_safe_split<'a>(src: &'a str, sep: &[char]) -> Option<(&'a str, &'a str)> {
193    let offset = angle_safe_offset(src, sep);
194    Some(src.split_at(offset + src[offset..].find(sep)?))
195}
196
197/// Split a string slice with given separator candidates, skipping paired angles.
198///
199/// The separator is conserved in the previous slice.
200fn angle_safe_rsplit<'a>(src: &'a str, sep: &[char]) -> Option<(&'a str, &'a str)> {
201    let offset = angle_safe_offset(src, sep);
202    Some(src.split_at(offset + src[offset..].find(sep)? + 1))
203}
204
205/// Parses a name.
206#[allow(clippy::missing_errors_doc)]
207#[inline]
208pub fn parse<'a, T>(value: &'a str) -> Result<T, T::Error>
209where
210    T: Parse<'a>,
211{
212    T::parse_from(&mut Cursor(value))
213}
214
215#[cfg(test)]
216fn validate_rw<'a, T>(value: &'a str)
217where
218    T: Parse<'a> + core::fmt::Display,
219    T::Error: Debug,
220{
221    use core::marker::PhantomData;
222
223    use alloc::string::String;
224
225    struct ClearValidator<T>(String, PhantomData<T>);
226
227    impl<'a, T> Parse<'a> for ClearValidator<T>
228    where
229        T: Parse<'a> + core::fmt::Display,
230    {
231        type Error = T::Error;
232
233        fn parse_from(cursor: &mut Cursor<'a>) -> Result<Self, Self::Error> {
234            use alloc::string::ToString as _;
235
236            let val = T::parse_from(cursor)?;
237            assert_eq!(cursor.get(), "", "non-empty string buf left");
238            Ok(Self(val.to_string(), PhantomData))
239        }
240    }
241
242    assert_eq!(
243        parse::<'a, ClearValidator<T>>(value).map(|o| o.0).unwrap(),
244        value
245    );
246}