Skip to main content

typed_ident/syntax/profile/
profile.rs

1// =============================================================================
2// USES
3// =============================================================================
4
5// -----------------------------------------------------------------------------
6use crate::syntax::Segmentation;
7use crate::syntax::profile::{AppendClosed, CharProfile};
8
9// =============================================================================
10// TYPES
11// =============================================================================
12
13/// Defines the valid composition of characters for a chunk (a slice of an
14/// identifier that contains no delimiters).
15///
16/// # Works on Character Code Points
17///
18/// Character profiles are defined in accordance with the default profile in
19/// [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/), which
20/// defines validation along a string of character code-points, not necessarily
21/// along a string of valid graphemes.
22///
23/// If you need more complex validation, such as the emoji profile which must
24/// validate a specific ordering of special characters for emoji handling, you
25/// need to use a wrapper type to add that extra validation.
26pub trait Profile: Sized {
27    /// Whether or not a fragment containing characters in this profile are
28    /// append-closed.
29    ///
30    /// It's always valid to set this to `Empty`, it just will lead to less
31    /// optimal codegen for your identifier.
32    ///
33    /// # What Is Append-Closed?
34    ///
35    /// Append-closed is a string property, where if we consider a set that
36    /// contains all valid permutations of a string, appending one value from
37    /// that set onto another equals some other value from the set.
38    ///
39    /// It's called "Append-Closed" because the operation we're doing is
40    /// "appending" one value onto another, and the operation will never result
41    /// in a value outside of the set (e.g. the set is closed given this
42    /// operation).
43    ///
44    /// # When Is a Profile `Chunk` Append-Closed?
45    ///
46    /// Simply put, if [`is_chunk_continue`] is a superset (or equal to) the set
47    /// of valid characters for [`is_ident_start`] *and* [`is_chunk_start`],
48    /// then you can set this to `Chunk`.
49    ///
50    /// Imagine we represent profile characters in a chunk as:
51    ///
52    /// * `S` = Chunk/Ident Start Character
53    /// * `C` = Chunk Continue Character
54    ///
55    /// Abstractly, all valid chunks would be:
56    ///
57    /// * `` `` and `SC*` (empty string, or `S` followed by one or more `C`)
58    ///
59    /// A valid chunk of characters may be `SCC`, after appending some other
60    /// string we may be left with `SCCSC`, but if `S ⊆ C`, then we can
61    /// interpret any `S` character as a `C` character, and thus it's
62    /// syntactically `SCCCC` (which is still valid and within this set).
63    ///
64    /// # When Is a Profile `Fragment` Append-Closed?
65    ///
66    /// This is a wider promise, which states that when used with a delimiter
67    /// that is `APPEND_CLOSED`, then all valid fragments are themselves also
68    /// append-closed (regardless of whether they contain delimiters or not).
69    ///
70    /// Simply put, the profile needs to have the following properties:
71    ///
72    /// * It must be `Chunk` append-closed (see above), *and...*
73    /// * [`is_chunk_start`] must be identical to [`is_chunk_continue`].
74    ///
75    /// It's easiest to understand this by looking at a case where it's *not*
76    /// true - `UpperCamel` (or `lowerCamel`, but let's just focus on upper).
77    ///
78    /// Two valid `UpperCamel` fragments are:
79    ///
80    /// * `Upper_` (a perfectly valid start, but also has a trailing delimiter).
81    /// * `camel` (a continuation of a chunk, not the start of one)
82    ///
83    /// If we append one onto the other, it may lead to an invalid fragment:
84    ///
85    /// * `Upper_` + `camel` = `Upper_camel` *(which is not valid)*
86    ///
87    /// # When Is a Profile `Identifier` Append-Closed?
88    ///
89    /// This is a wider promise, which states that if the profile were used
90    /// with a delimiter that *also* claimed to be `Identifier` append-closed,
91    /// then even identifiers themselves would be append-closed.
92    ///
93    /// Simply put, the profile needs to have the following properties:
94    ///
95    /// * It must be `Fragment` append-closed (see above), *and...*
96    /// * [`is_ident_start`] must be identical to [`is_chunk_start`] *and*
97    ///   [`is_chunk_continue`].
98    ///
99    /// # What Happens If This Is Set Incorrectly?
100    ///
101    /// If this is set incorrectly, then it may be possible for some invalid
102    /// fragments (and thus, identifiers) to be formed. It cannot lead to any
103    /// memory issues or anything like that, just logical issues.
104    ///
105    /// [`is_chunk_continue`]: Profile::is_chunk_continue
106    /// [`is_chunk_start`]: Profile::is_chunk_start
107    /// [`is_ident_start`]: Profile::is_ident_start
108    const APPEND_CLOSED: AppendClosed = AppendClosed::Empty;
109
110    /// The underlying profile that this profile is based on.
111    ///
112    /// This can be useful in identifying the core underlying profile for a type
113    /// in a generic way (instead of handling each profile type specially). It
114    /// comes in handy when there are case-conversions already present overtop
115    /// of a profile.
116    ///
117    /// # Implementation Suggestion
118    ///
119    /// If you are defining a *new* profile (e.g., like [`Ascii`], [`Unicode`]),
120    /// then you should set this to `Self`. If you are defining a new
121    /// `CasedProfile` (e.g., like [`Lower`], [`Upper`], etc). This should be
122    /// set to the profile that the cased profile wraps.
123    ///
124    /// [`Ascii`]: crate::syntax::profile::Ascii
125    /// [`Unicode`]: crate::syntax::profile::Unicode
126    /// [`Lower`]: crate::syntax::profile::Lower
127    /// [`Upper`]: crate::syntax::profile::Upper
128    type BaseProfile: CharProfile;
129
130    /// The segmentation strategy that this profile uses.
131    ///
132    /// Depending on the valid characters, different segmentation strategies
133    /// could apply. For instance, the [`Ascii`] profile rightly uses the
134    /// [`Char`] segmentation strategy (because it's safe for it to).
135    ///
136    /// The recommendation is:
137    ///
138    /// * If your profile only consists of ASCII characters, use [`Char`].
139    /// * Otherwise you should use the [`Grapheme`] strategy.
140    ///
141    /// # What Happens If This Is Set Incorrectly?
142    ///
143    /// If this is set incorrectly, validation will not be impacted, but things
144    /// relating to segmentation would be (so any of the [`segments`]-like
145    /// methods may appear to be missing boundaries, for instance).
146    ///
147    /// [`Ascii`]: crate::syntax::profile::Ascii
148    /// [`Char`]: crate::syntax::segmentation::Char
149    /// [`Grapheme`]: crate::syntax::segmentation::Grapheme
150    /// [`segments`]: crate::core::fragment::Fragment::segments
151    type Segmentation: Segmentation;
152
153    /// Whether or not the provided character can appear at the absolute start
154    /// of an identifier.
155    ///
156    /// # Important
157    ///
158    /// This is at the start of an *identifier*, not at the start of a
159    /// *fragment* or *chunk*. Valid at fragment-start is defined by
160    /// [`in_profile`]. Valid at chunk-start is defined by [`is_chunk_start`].
161    ///
162    /// However, note that it doesn't *require* presence. So it would be bad to
163    /// depend on this for a required character (like PHP's dollar sign).
164    ///
165    /// [`in_profile`]: Profile::in_profile
166    /// [`is_chunk_start`]: Profile::is_chunk_start
167    fn is_ident_start(c: char) -> bool;
168
169    /// Whether or not the provided character can appear at the start of a new
170    /// chunk (e.g. right after a delimiter).
171    ///
172    /// # Important
173    ///
174    /// This is at the start of a *chunk*, not at the start of a *fragment* or
175    /// *identifier*. Valid at fragment-start is defined by [`in_profile`].
176    /// Valid at identifier-start is defined by [`is_ident_start`].
177    ///
178    /// However, note that it doesn't *require* presence. So it would be bad to
179    /// depend on this for a required character (like PHP's dollar sign).
180    ///
181    /// [`in_profile`]: Profile::in_profile
182    /// [`is_ident_start`]: Profile::is_ident_start
183    fn is_chunk_start(c: char) -> bool;
184
185    /// Whether or not the provided character can appear at any point in a
186    /// chunk.
187    ///
188    /// This should always be a superset (or equal-to) [`is_ident_start`],
189    /// [`is_chunk_start`], *AND* [`is_chunk_continue`].
190    ///
191    /// [`is_chunk_start`]: Profile::is_chunk_start
192    /// [`is_ident_start`]: Profile::is_ident_start
193    /// [`is_chunk_continue`]: Profile::is_chunk_continue
194    #[inline]
195    fn in_profile(c: char) -> bool {
196        Self::is_ident_start(c) || Self::is_chunk_start(c) || Self::is_chunk_continue(c)
197    }
198
199    /// Whether or not the provided character is valid at any position after the
200    /// first character of a chunk (a run of non-delimiter characters).
201    fn is_chunk_continue(c: char) -> bool;
202}