1use crate::core::moniker::{Moniker, MonikerBuilder};
2
3pub(crate) fn extend_segment(parent: &Moniker, kind: &[u8], name: &[u8]) -> Moniker {
4 let mut b = MonikerBuilder::from_view(parent.as_view());
5 b.segment(kind, name);
6 b.build()
7}
8
9pub(crate) fn extend_segment_u32(parent: &Moniker, kind: &[u8], n: u32) -> Moniker {
10 let mut buf = [0u8; 10];
11 extend_segment(parent, kind, decimal_bytes(n as u64, &mut buf))
12}
13
14fn decimal_bytes(n: u64, buf: &mut [u8]) -> &[u8] {
15 if n == 0 {
16 buf[buf.len() - 1] = b'0';
17 return &buf[buf.len() - 1..];
18 }
19 let mut i = buf.len();
20 let mut x = n;
21 while x > 0 {
22 i -= 1;
23 buf[i] = b'0' + (x % 10) as u8;
24 x /= 10;
25 }
26 &buf[i..]
27}
28
29pub(crate) fn append_dir_module_segments(
30 b: &mut MonikerBuilder,
31 path: &str,
32 dir_kind: &[u8],
33 module_kind: &[u8],
34) {
35 let mut iter = path.split('/').filter(|s| !s.is_empty() && *s != ".");
36 let Some(mut prev) = iter.next() else { return };
37 for piece in iter {
38 b.segment(dir_kind, prev.as_bytes());
39 prev = piece;
40 }
41 b.segment(module_kind, prev.as_bytes());
42}
43
44pub(crate) fn normalize_type_text(text: &str) -> Vec<u8> {
45 let bytes = text.as_bytes();
46 if !bytes
47 .iter()
48 .any(|b| matches!(*b, b' ' | b'\t' | b'\n' | b'\r'))
49 {
50 return bytes.to_vec();
51 }
52 let mut out = bytes.to_vec();
53 out.retain(|b| !matches!(*b, b' ' | b'\t' | b'\n' | b'\r'));
54 out
55}
56
57#[derive(Clone, Debug, Default)]
58pub(crate) struct CallableSlot {
59 pub name: Vec<u8>,
60 pub r#type: Vec<u8>,
61}
62
63pub(crate) fn callable_segment_slots(name: &[u8], slots: &[CallableSlot]) -> Vec<u8> {
64 let body_len: usize = slots
65 .iter()
66 .map(|s| s.name.len() + s.r#type.len() + 2)
67 .sum();
68 let mut full = Vec::with_capacity(name.len() + 2 + body_len);
69 full.extend_from_slice(name);
70 full.push(b'(');
71 for (i, slot) in slots.iter().enumerate() {
72 if i > 0 {
73 full.push(b',');
74 }
75 match (slot.name.as_slice(), slot.r#type.as_slice()) {
76 (b"", b"") => full.push(b'_'),
77 (name, b"") => full.extend_from_slice(name),
78 (b"", ty) => full.extend_from_slice(ty),
79 (name, ty) => {
80 full.extend_from_slice(name);
81 full.push(b':');
82 full.extend_from_slice(ty);
83 }
84 }
85 }
86 full.push(b')');
87 full
88}
89
90pub(crate) fn join_bytes_with_comma<T: AsRef<[u8]>>(parts: &[T]) -> Vec<u8> {
91 let body_len: usize = parts
92 .iter()
93 .map(|p| p.as_ref().len() + 1)
94 .sum::<usize>()
95 .saturating_sub(1);
96 let mut out = Vec::with_capacity(body_len);
97 for (i, p) in parts.iter().enumerate() {
98 if i > 0 {
99 out.push(b',');
100 }
101 out.extend_from_slice(p.as_ref());
102 }
103 out
104}
105
106pub(crate) fn extend_callable_slots(
107 parent: &Moniker,
108 kind: &[u8],
109 name: &[u8],
110 slots: &[CallableSlot],
111) -> Moniker {
112 extend_segment(parent, kind, &callable_segment_slots(name, slots))
113}
114
115pub(crate) fn slot_signature_bytes(slot: &CallableSlot) -> Vec<u8> {
116 match (slot.name.as_slice(), slot.r#type.as_slice()) {
117 (b"", b"") => b"_".to_vec(),
118 (name, b"") => name.to_vec(),
119 (b"", ty) => ty.to_vec(),
120 (name, ty) => {
121 let mut out = Vec::with_capacity(name.len() + 1 + ty.len());
122 out.extend_from_slice(name);
123 out.push(b':');
124 out.extend_from_slice(ty);
125 out
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn slots_segment_empty_args_emits_empty_parens() {
136 assert_eq!(callable_segment_slots(b"f", &[]), b"f()".to_vec());
137 }
138
139 #[test]
140 fn slots_segment_name_and_type_pairs() {
141 let slots = vec![
142 CallableSlot {
143 name: b"id".to_vec(),
144 r#type: b"int".to_vec(),
145 },
146 CallableSlot {
147 name: b"label".to_vec(),
148 r#type: b"String".to_vec(),
149 },
150 ];
151 assert_eq!(
152 callable_segment_slots(b"findById", &slots),
153 b"findById(id:int,label:String)".to_vec()
154 );
155 }
156
157 #[test]
158 fn slots_segment_name_only_when_type_absent() {
159 let slots = vec![
160 CallableSlot {
161 name: b"x".to_vec(),
162 r#type: Vec::new(),
163 },
164 CallableSlot {
165 name: b"y".to_vec(),
166 r#type: Vec::new(),
167 },
168 ];
169 assert_eq!(callable_segment_slots(b"f", &slots), b"f(x,y)".to_vec());
170 }
171
172 #[test]
173 fn slots_segment_type_only_when_name_absent() {
174 let slots = vec![
175 CallableSlot {
176 name: Vec::new(),
177 r#type: b"int".to_vec(),
178 },
179 CallableSlot {
180 name: Vec::new(),
181 r#type: b"String".to_vec(),
182 },
183 ];
184 assert_eq!(
185 callable_segment_slots(b"f", &slots),
186 b"f(int,String)".to_vec()
187 );
188 }
189
190 #[test]
191 fn slots_segment_underscore_when_both_absent() {
192 let slots = vec![
193 CallableSlot::default(),
194 CallableSlot::default(),
195 CallableSlot::default(),
196 ];
197 assert_eq!(callable_segment_slots(b"f", &slots), b"f(_,_,_)".to_vec());
198 }
199
200 #[test]
201 fn slots_segment_mixed_per_slot() {
202 let slots = vec![
203 CallableSlot {
204 name: b"id".to_vec(),
205 r#type: b"int".to_vec(),
206 },
207 CallableSlot {
208 name: Vec::new(),
209 r#type: b"String".to_vec(),
210 },
211 CallableSlot::default(),
212 ];
213 assert_eq!(
214 callable_segment_slots(b"f", &slots),
215 b"f(id:int,String,_)".to_vec()
216 );
217 }
218
219 #[test]
220 fn normalize_type_text_collapses_inner_whitespace() {
221 assert_eq!(
222 normalize_type_text("Map<String, Integer>"),
223 b"Map<String,Integer>".to_vec()
224 );
225 assert_eq!(
226 normalize_type_text("dict[str , int]"),
227 b"dict[str,int]".to_vec()
228 );
229 assert_eq!(
230 normalize_type_text("string | number"),
231 b"string|number".to_vec()
232 );
233 assert_eq!(
234 normalize_type_text("(x: number) => string"),
235 b"(x:number)=>string".to_vec()
236 );
237 }
238
239 #[test]
240 fn normalize_type_text_preserves_structural_punctuation() {
241 assert_eq!(
242 normalize_type_text("HashMap<String, u32>"),
243 b"HashMap<String,u32>".to_vec()
244 );
245 assert_eq!(normalize_type_text("\tFoo "), b"Foo".to_vec());
246 }
247}