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}