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//! - Type signatures: [`TypeSignature`]
24
25#![no_std]
26
27extern crate alloc;
28
29mod class;
30mod method;
31mod ty;
32
33use core::{convert::Infallible, fmt::Debug};
34
35pub use class::*;
36pub use method::*;
37pub use ty::*;
38
39/// Types that could be parsed from a borrowed string cursor.
40pub trait Parse<'s>: Sized {
41    /// The error type.
42    type Error;
43
44    /// Parse from the given string cursor.
45    #[allow(clippy::missing_errors_doc)]
46    fn parse_from(cursor: &mut Cursor<'s>) -> Result<Self, Self::Error>;
47}
48
49/// Representation form of a component across different contexts.
50#[non_exhaustive]
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum ReprForm {
53    /// JLS-specified standard binary name format.
54    JLS,
55    /// Internal representation used by class files.
56    ///
57    /// See [JVMS 4.2.1](https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.2.1)
58    /// for more information about internal form of a class, interface or package.
59    Internal,
60}
61
62impl ReprForm {
63    #[inline]
64    const fn package_separator(&self) -> char {
65        match self {
66            Self::JLS => '.',
67            Self::Internal => '/',
68        }
69    }
70}
71
72/// Cursor of a string slice.
73#[derive(Debug, Clone)]
74pub struct Cursor<'a>(&'a str);
75
76impl<'a> Cursor<'a> {
77    /// Creates a new string cursor from slice.
78    #[inline]
79    pub const fn new(s: &'a str) -> Self {
80        Self(s)
81    }
82
83    /// Reads a single character.
84    ///
85    /// # Panics
86    ///
87    /// Panics if there's no valid character left in this slice.
88    pub fn get_char(&mut self) -> char {
89        let c = self.0.chars().next().expect("end of file");
90        self.advance_by(c.len_utf8());
91        c
92    }
93
94    /// Advances the inner cursor with given function.
95    #[inline]
96    #[allow(clippy::missing_panics_doc)] // infalliable
97    pub fn advance<F, U>(&mut self, f: F) -> U
98    where
99        F: FnOnce(&'a str) -> (U, &'a str),
100    {
101        self.try_advance(|s| Result::<_, Infallible>::Ok(f(s)))
102            .unwrap()
103    }
104
105    /// Advances the inner cursor if successed.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if the given function returns one.
110    #[inline]
111    pub fn try_advance<F, U, Err>(&mut self, f: F) -> Result<U, Err>
112    where
113        F: FnOnce(&'a str) -> Result<(U, &'a str), Err>,
114    {
115        let (ret, leftover) = f(self.0)?;
116        self.0 = leftover;
117        Ok(ret)
118    }
119
120    /// Returns the underlying slice.
121    #[inline]
122    pub const fn get(&self) -> &'a str {
123        self.0
124    }
125
126    /// Overrides the underlying slice.
127    #[inline]
128    pub const fn set(&mut self, s: &'a str) {
129        self.0 = s;
130    }
131
132    /// Advances the inner cursor by `n` bytes.
133    ///
134    /// # Panics
135    ///
136    /// Panics if the inner slice doesn't has `n` remaining bytes.
137    #[inline]
138    pub fn advance_by(&mut self, n: usize) {
139        self.advance(|s| ((), &s[n..]))
140    }
141
142    /// Discards this cursor.
143    #[inline]
144    pub fn clear(&mut self) {
145        self.0 = "";
146    }
147}
148
149fn strip_digits_prefix(s: &str) -> (Option<u32>, &str) {
150    let mut chars = s.char_indices();
151    let (digits, last_index) = chars
152        .by_ref()
153        .map_while(|(index, c)| c.to_digit(10).map(|d| (index, d)))
154        .fold((0, None), |(n, _), (ci, d)| (n * 10 + d, Some(ci)));
155    let leftover = last_index.map_or(s, |i| &s[i + 1..]);
156    (last_index.map(|_| digits), leftover)
157}
158
159/// Parses a name.
160#[allow(clippy::missing_errors_doc)]
161#[inline]
162pub fn parse<'a, T>(value: &'a str) -> Result<T, T::Error>
163where
164    T: Parse<'a>,
165{
166    T::parse_from(&mut Cursor(value))
167}
168
169#[cfg(test)]
170fn validate_rw<'a, T>(value: &'a str)
171where
172    T: Parse<'a> + core::fmt::Display,
173    T::Error: Debug,
174{
175    use core::marker::PhantomData;
176
177    use alloc::string::String;
178
179    struct ClearValidator<T>(String, PhantomData<T>);
180
181    impl<'a, T> Parse<'a> for ClearValidator<T>
182    where
183        T: Parse<'a> + core::fmt::Display,
184    {
185        type Error = T::Error;
186
187        fn parse_from(cursor: &mut Cursor<'a>) -> Result<Self, Self::Error> {
188            use alloc::string::ToString as _;
189
190            let val = T::parse_from(cursor)?;
191            assert_eq!(cursor.get(), "", "non-empty string buf left");
192            Ok(Self(val.to_string(), PhantomData))
193        }
194    }
195
196    assert_eq!(
197        parse::<'a, ClearValidator<T>>(value).map(|o| o.0).unwrap(),
198        value
199    );
200}