Skip to main content

code_moniker_core/lang/
callable.rs

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}