lutra_compiler/pr/
path.rs1#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
4pub struct Path {
5 path: Vec<String>,
6}
7
8impl Path {
9 pub fn new<S: ToString, I: IntoIterator<Item = S>>(path: I) -> Self {
11 Path {
12 path: path.into_iter().map(|x| x.to_string()).collect(),
13 }
14 }
15 pub fn empty() -> Self {
16 Path { path: vec![] }
17 }
18
19 pub fn from_name<S: ToString>(name: S) -> Self {
20 Path {
21 path: vec![name.to_string()],
22 }
23 }
24
25 pub fn first(&self) -> &str {
26 self.path.first().unwrap()
27 }
28
29 pub fn last(&self) -> &str {
30 self.path.last().unwrap()
31 }
32
33 pub fn parent(&self) -> &[String] {
34 &self.path[0..(self.len() - 1)]
35 }
36
37 pub fn as_steps(&self) -> &[String] {
38 &self.path
39 }
40
41 pub fn len(&self) -> usize {
42 self.path.len()
43 }
44
45 pub fn is_empty(&self) -> bool {
46 self.path.is_empty()
47 }
48
49 pub fn prepend(self, mut prefix: Path) -> Path {
50 prefix.path.extend(self.path);
51 prefix
52 }
53
54 pub fn extend(&mut self, suffix: Path) {
55 self.path.extend(suffix);
56 }
57
58 pub fn push(&mut self, name: String) {
59 self.path.push(name);
60 }
61
62 pub fn pop(&mut self) -> Option<String> {
63 self.path.pop()
64 }
65
66 pub fn pop_first(&mut self) -> Option<String> {
67 if self.is_empty() {
68 return None;
69 }
70 let remaining = self.path.split_off(1);
71 let first = std::mem::replace(&mut self.path, remaining);
72 Some(first.into_iter().next().unwrap())
73 }
74
75 pub fn with_name<S: ToString>(mut self, name: S) -> Self {
76 *self.path.last_mut().unwrap() = name.to_string();
77 self
78 }
79
80 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &String> {
81 self.path.iter()
82 }
83
84 pub fn starts_with(&self, prefix: &Path) -> bool {
85 if prefix.len() > self.len() {
86 return false;
87 }
88 prefix
89 .iter()
90 .zip(self.iter())
91 .all(|(prefix_component, self_component)| prefix_component == self_component)
92 }
93
94 pub fn starts_with_path<S: AsRef<str>>(&self, prefix: &[S]) -> bool {
95 if prefix.len() > self.len() {
97 return false;
98 }
99 prefix
100 .iter()
101 .zip(self.iter())
102 .all(|(prefix_component, self_component)| prefix_component.as_ref() == self_component)
103 }
104
105 pub fn starts_with_part(&self, prefix: &str) -> bool {
106 self.starts_with_path(&[prefix])
107 }
108}
109
110impl std::fmt::Debug for Path {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.write_str(&display_path(self))
113 }
114}
115
116impl std::fmt::Display for Path {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 f.write_str(&display_path(self))
119 }
120}
121
122impl IntoIterator for Path {
123 type Item = String;
124 type IntoIter = std::vec::IntoIter<std::string::String>;
125
126 fn into_iter(self) -> Self::IntoIter {
127 self.path.into_iter()
128 }
129}
130
131impl std::ops::Add<Path> for Path {
132 type Output = Path;
133
134 fn add(mut self, rhs: Path) -> Self::Output {
135 self.path.extend(rhs.path);
136 self
137 }
138}
139
140fn display_path(ident: &Path) -> String {
141 let path = &ident.path[..];
142
143 let mut r = String::new();
144 for (index, part) in path.iter().enumerate() {
145 if index > 0 {
146 r += "::";
147 }
148 r += display_ident(part).as_ref();
149 }
150 r
151}
152
153pub fn display_ident(s: &str) -> std::borrow::Cow<'_, str> {
154 fn forbidden_start(c: char) -> bool {
155 !matches!(c, 'A'..='Z' | 'a'..='z' | '_')
156 }
157 fn forbidden_subsequent(c: char) -> bool {
158 !matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '_')
159 }
160 let needs_escape = s.is_empty()
161 || s.starts_with(forbidden_start)
162 || (s.len() > 1 && s.chars().skip(1).any(forbidden_subsequent));
163
164 if needs_escape {
165 format!("`{s}`").into()
166 } else {
167 s.into()
168 }
169}
170
171#[test]
172#[cfg(test)]
173fn test_display_ident() {
174 assert_eq!(display_ident("Key"), "Key");
175 assert_eq!(display_ident("Key No"), "`Key No`");
176 assert_eq!(display_ident("#"), "`#`");
177}