Skip to main content

code_moniker_core/core/
shape.rs

1#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
2pub enum Shape {
3	Namespace,
4	Type,
5	Callable,
6	Value,
7	Annotation,
8}
9
10impl Shape {
11	pub fn as_bytes(self) -> &'static [u8] {
12		match self {
13			Shape::Namespace => b"namespace",
14			Shape::Type => b"type",
15			Shape::Callable => b"callable",
16			Shape::Value => b"value",
17			Shape::Annotation => b"annotation",
18		}
19	}
20
21	pub fn as_str(self) -> &'static str {
22		std::str::from_utf8(self.as_bytes()).unwrap()
23	}
24}
25
26const SHAPE_TABLE: &[(&[u8], Shape, bool)] = &[
27	(b"module", Shape::Namespace, true),
28	(b"namespace", Shape::Namespace, true),
29	(b"schema", Shape::Namespace, true),
30	(b"impl", Shape::Namespace, true),
31	(b"class", Shape::Type, true),
32	(b"struct", Shape::Type, true),
33	(b"interface", Shape::Type, true),
34	(b"trait", Shape::Type, true),
35	(b"enum", Shape::Type, true),
36	(b"record", Shape::Type, true),
37	(b"annotation_type", Shape::Type, true),
38	(b"table", Shape::Type, true),
39	(b"type", Shape::Type, false),
40	(b"view", Shape::Type, false),
41	(b"delegate", Shape::Type, false),
42	(b"function", Shape::Callable, true),
43	(b"method", Shape::Callable, true),
44	(b"constructor", Shape::Callable, true),
45	(b"fn", Shape::Callable, true),
46	(b"func", Shape::Callable, true),
47	(b"procedure", Shape::Callable, true),
48	(b"async_function", Shape::Callable, true),
49	(b"field", Shape::Value, false),
50	(b"property", Shape::Value, false),
51	(b"event", Shape::Value, false),
52	(b"enum_constant", Shape::Value, false),
53	(b"const", Shape::Value, false),
54	(b"static", Shape::Value, false),
55	(b"var", Shape::Value, false),
56	(b"param", Shape::Value, false),
57	(b"local", Shape::Value, false),
58	(b"comment", Shape::Annotation, false),
59];
60
61pub fn shape_of(kind: &[u8]) -> Option<Shape> {
62	SHAPE_TABLE
63		.iter()
64		.find(|(k, _, _)| *k == kind)
65		.map(|(_, s, _)| *s)
66}
67
68pub fn opens_scope(kind: &[u8]) -> bool {
69	SHAPE_TABLE
70		.iter()
71		.find(|(k, _, _)| *k == kind)
72		.is_some_and(|(_, _, opens)| *opens)
73}
74
75pub fn known_kinds() -> impl Iterator<Item = &'static [u8]> {
76	SHAPE_TABLE.iter().map(|(k, _, _)| *k)
77}
78
79#[cfg(test)]
80mod tests {
81	use super::*;
82
83	#[test]
84	fn shape_table_has_no_duplicate_kind() {
85		let mut seen = std::collections::HashSet::new();
86		for (k, _, _) in SHAPE_TABLE {
87			assert!(seen.insert(*k), "duplicate kind in SHAPE_TABLE: {k:?}");
88		}
89	}
90
91	#[test]
92	fn unknown_kind_has_no_shape() {
93		assert!(shape_of(b"definitely_not_a_kind").is_none());
94		assert!(!opens_scope(b"definitely_not_a_kind"));
95	}
96
97	#[test]
98	fn internal_kinds_are_classified() {
99		assert_eq!(shape_of(b"module"), Some(Shape::Namespace));
100		assert_eq!(shape_of(b"comment"), Some(Shape::Annotation));
101		assert_eq!(shape_of(b"local"), Some(Shape::Value));
102		assert_eq!(shape_of(b"param"), Some(Shape::Value));
103	}
104
105	#[test]
106	fn comment_is_the_only_annotation() {
107		let annotations: Vec<_> = SHAPE_TABLE
108			.iter()
109			.filter(|(_, s, _)| *s == Shape::Annotation)
110			.map(|(k, _, _)| *k)
111			.collect();
112		assert_eq!(annotations, vec![b"comment".as_slice()]);
113	}
114
115	#[test]
116	fn annotation_never_opens_scope() {
117		for (_, shape, opens) in SHAPE_TABLE {
118			if *shape == Shape::Annotation {
119				assert!(!opens, "annotation kind must not open a scope");
120			}
121		}
122	}
123
124	#[test]
125	fn values_never_open_scope() {
126		for (k, shape, opens) in SHAPE_TABLE {
127			if *shape == Shape::Value {
128				assert!(!opens, "value kind {k:?} must not open a scope");
129			}
130		}
131	}
132
133	#[test]
134	fn callables_always_open_scope() {
135		for (k, shape, opens) in SHAPE_TABLE {
136			if *shape == Shape::Callable {
137				assert!(*opens, "callable kind {k:?} must open a scope");
138			}
139		}
140	}
141
142	#[test]
143	fn namespaces_always_open_scope() {
144		for (k, shape, opens) in SHAPE_TABLE {
145			if *shape == Shape::Namespace {
146				assert!(*opens, "namespace kind {k:?} must open a scope");
147			}
148		}
149	}
150
151	#[test]
152	fn type_containers_open_scope_aliases_do_not() {
153		let containers: &[&[u8]] = &[
154			b"class",
155			b"struct",
156			b"interface",
157			b"trait",
158			b"enum",
159			b"record",
160			b"annotation_type",
161			b"table",
162		];
163		let aliases: &[&[u8]] = &[b"type", b"view", b"delegate"];
164		for k in containers {
165			assert!(opens_scope(k), "type container {k:?} must open a scope");
166		}
167		for k in aliases {
168			assert!(!opens_scope(k), "type alias {k:?} must not open a scope");
169		}
170	}
171
172	#[test]
173	fn shape_str_round_trip_is_lowercase_word() {
174		for shape in [
175			Shape::Namespace,
176			Shape::Type,
177			Shape::Callable,
178			Shape::Value,
179			Shape::Annotation,
180		] {
181			let s = shape.as_str();
182			assert!(s.chars().all(|c| c.is_ascii_lowercase()));
183			assert!(!s.is_empty());
184		}
185	}
186}