backhopper_core/model/
normalised.rs1use std::fmt;
8use std::sync::Arc;
9
10use serde::{Deserialize, Serialize};
11
12use crate::errors::NameError;
13
14const EMPTY_BODY: &str = "%empty%";
15const BARE_QUESTION_MARK: &str = "%bare_question_mark%";
16
17fn normalise(text: &str) -> String {
21 let mut out = String::with_capacity(text.len());
22 let mut chars = text.chars();
23 let mut in_string = false;
24 let mut in_atom = false;
25 while let Some(c) = chars.next() {
26 if in_string || in_atom {
27 push_literal_char(&mut out, c);
28 match c {
29 '\\' => {
30 if let Some(next) = chars.next() {
31 push_literal_char(&mut out, next);
32 }
33 }
34 '"' if in_string => in_string = false,
35 '\'' if in_atom => in_atom = false,
36 _ => {}
37 }
38 continue;
39 }
40 match c {
41 '%' => {
42 for next in chars.by_ref() {
43 if next == '\n' {
44 break;
45 }
46 }
47 if !out.ends_with(' ') {
48 out.push(' ');
49 }
50 }
51 '"' => {
52 in_string = true;
53 out.push(c);
54 }
55 '\'' => {
56 in_atom = true;
57 out.push(c);
58 }
59 '$' => {
60 out.push(c);
61 if let Some(next) = chars.next() {
62 push_literal_char(&mut out, next);
63 }
64 }
65 c if c.is_whitespace() => {
66 if !out.ends_with(' ') {
67 out.push(' ');
68 }
69 }
70 c => out.push(c),
71 }
72 }
73 out.trim().to_owned()
74}
75
76fn push_literal_char(out: &mut String, c: char) {
77 match c {
78 '\n' => out.push_str("\\n"),
79 '\r' => out.push_str("\\r"),
80 c => out.push(c),
81 }
82}
83
84fn not_normalised(kind: &'static str, text: &str) -> NameError {
85 NameError::Invalid {
86 kind,
87 value: text.to_owned(),
88 reason: "not in normalised form",
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
96#[serde(try_from = "String", into = "String")]
97pub struct NormalisedMacroBody(Arc<str>);
98
99impl NormalisedMacroBody {
100 #[must_use]
101 pub fn from_source(source: &str) -> Self {
102 let text = normalise(source);
103 let text = match text.as_str() {
104 "" => EMPTY_BODY,
105 "?" => BARE_QUESTION_MARK,
106 other => other,
107 };
108 Self(Arc::from(text))
109 }
110
111 pub fn from_stored(text: &str) -> Result<Self, NameError> {
112 let body = Self::from_source(text);
113 if body.as_str() == text {
114 Ok(body)
115 } else {
116 Err(not_normalised("macro body", text))
117 }
118 }
119
120 #[must_use]
121 pub fn as_str(&self) -> &str {
122 &self.0
123 }
124
125 #[must_use]
128 pub fn is_string_literal(&self) -> bool {
129 let mut chars = self.0.chars();
130 let mut in_string = false;
131 let mut seen = false;
132 while let Some(c) = chars.next() {
133 match (in_string, c) {
134 (true, '\\') => {
135 chars.next();
136 }
137 (true, '"') => in_string = false,
138 (true, _) => {}
139 (false, '"') => {
140 in_string = true;
141 seen = true;
142 }
143 (false, c) if c.is_whitespace() => {}
144 (false, _) => return false,
145 }
146 }
147 seen && !in_string
148 }
149}
150
151impl fmt::Display for NormalisedMacroBody {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 f.write_str(self.as_str())
154 }
155}
156
157impl TryFrom<String> for NormalisedMacroBody {
158 type Error = NameError;
159
160 fn try_from(text: String) -> Result<Self, NameError> {
161 Self::from_stored(&text)
162 }
163}
164
165impl From<NormalisedMacroBody> for String {
166 fn from(body: NormalisedMacroBody) -> Self {
167 body.as_str().to_owned()
168 }
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
174#[serde(try_from = "String", into = "String")]
175pub struct NormalisedDefault(Arc<str>);
176
177impl NormalisedDefault {
178 #[must_use]
179 pub fn from_source(source: &str) -> Option<Self> {
180 let text = normalise(source);
181 (!text.is_empty()).then(|| Self(Arc::from(text)))
182 }
183
184 pub fn from_stored(text: &str) -> Result<Self, NameError> {
185 Self::from_source(text)
186 .filter(|default| default.as_str() == text)
187 .ok_or_else(|| not_normalised("record field default", text))
188 }
189
190 #[must_use]
191 pub fn as_str(&self) -> &str {
192 &self.0
193 }
194}
195
196impl fmt::Display for NormalisedDefault {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 f.write_str(self.as_str())
199 }
200}
201
202impl TryFrom<String> for NormalisedDefault {
203 type Error = NameError;
204
205 fn try_from(text: String) -> Result<Self, NameError> {
206 Self::from_stored(&text)
207 }
208}
209
210impl From<NormalisedDefault> for String {
211 fn from(default: NormalisedDefault) -> Self {
212 default.as_str().to_owned()
213 }
214}