1use std::error::Error;
33use std::fmt;
34use std::fmt::Write as _;
35use std::sync::Arc;
36
37use links_notation::{LiNo, ParserConfig, parse_lino_to_links_with_config};
38
39use crate::link_flags::LinkFlags;
40use crate::link_network::{Link, LinkId, LinkMetadata, LinkNetwork, LinkType};
41use crate::source::{ByteRange, Point, SourceSpan};
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum LinoSerializationError {
46 Parse(String),
48 Structure(String),
50}
51
52impl fmt::Display for LinoSerializationError {
53 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Self::Parse(message) => write!(formatter, "links-notation parse error: {message}"),
56 Self::Structure(message) => {
57 write!(formatter, "serialization structure error: {message}")
58 }
59 }
60 }
61}
62
63impl Error for LinoSerializationError {}
64
65impl LinkNetwork {
66 #[must_use]
72 pub fn to_lino(&self) -> String {
73 let registered: std::collections::BTreeSet<u64> =
74 self.terms.values().map(|id| id.0).collect();
75 let mut output = String::new();
76 for link in self.links.values() {
77 encode_link(link, registered.contains(&link.id.0), &mut output);
78 output.push('\n');
79 }
80 output
81 }
82
83 pub fn from_lino(text: &str) -> Result<Self, LinoSerializationError> {
90 let statements = parse_lino_to_links_with_config(text, &ParserConfig::without_comments())
91 .map_err(|error| LinoSerializationError::Parse(error.to_string()))?;
92 let mut network = Self::new();
93 for statement in &statements {
94 let LiNo::Link {
95 id: Some(id),
96 values,
97 } = statement
98 else {
99 return Err(LinoSerializationError::Structure(
100 "top-level statement must be an identified link".to_string(),
101 ));
102 };
103 let link_id = LinkId(parse_u64(id)?);
104 let mut references = Vec::new();
105 let mut meta_values: Option<&Vec<LiNo<String>>> = None;
106 for value in values {
107 match value {
108 LiNo::Ref(reference) => references.push(LinkId(parse_u64(reference)?)),
109 LiNo::Link {
110 id: Some(key),
111 values: fields,
112 } if key == "meta" => meta_values = Some(fields),
113 LiNo::Link { .. } => {
114 return Err(LinoSerializationError::Structure(
115 "statement values must be references or a meta sublink".to_string(),
116 ));
117 }
118 }
119 }
120 let meta_values = meta_values.ok_or_else(|| {
121 LinoSerializationError::Structure(
122 "statement is missing its meta sublink".to_string(),
123 )
124 })?;
125 let (metadata, registered) = decode_meta(meta_values)?;
126 if registered && let Some(term) = metadata.term() {
127 network.terms.insert(Arc::from(term), link_id);
128 }
129 network.next_id = network.next_id.max(link_id.0 + 1);
130 network.links.insert(
131 link_id,
132 Arc::new(Link {
133 id: link_id,
134 references: Arc::from(references),
135 metadata,
136 }),
137 );
138 }
139 Ok(network)
140 }
141}
142
143fn encode_link(link: &Link, registered: bool, output: &mut String) {
145 write!(output, "({}:", link.id.0).expect("writing to a String never fails");
146 for reference in link.references.iter() {
147 write!(output, " {}", reference.0).expect("writing to a String never fails");
148 }
149 output.push_str(" (meta:");
150 let metadata = &link.metadata;
151 if let Some(link_type) = metadata.link_type() {
152 write!(output, " (t: {link_type})").expect("writing to a String never fails");
153 }
154 write!(output, " (n: {})", u8::from(metadata.is_named()))
155 .expect("writing to a String never fails");
156 if let Some(term) = metadata.term() {
157 write!(output, " (term: {})", percent_encode(term))
158 .expect("writing to a String never fails");
159 }
160 if let Some(definition) = metadata.definition() {
161 write!(output, " (def: {})", percent_encode(definition))
162 .expect("writing to a String never fails");
163 }
164 if let Some(language) = metadata.language() {
165 write!(output, " (lang: {})", percent_encode(language))
166 .expect("writing to a String never fails");
167 }
168 if let Some(span) = metadata.span() {
169 let byte_range = span.byte_range();
170 let start = span.start_point();
171 let end = span.end_point();
172 write!(
173 output,
174 " (span: {} {} {} {} {} {})",
175 byte_range.start(),
176 byte_range.end(),
177 start.row(),
178 start.column(),
179 end.row(),
180 end.column(),
181 )
182 .expect("writing to a String never fails");
183 }
184 let bits = flag_bits(metadata.flags());
185 if bits != 0 {
186 write!(output, " (flags: {bits})").expect("writing to a String never fails");
187 }
188 if registered {
189 output.push_str(" (reg: 1)");
190 }
191 output.push_str("))");
192}
193
194fn decode_meta(fields: &[LiNo<String>]) -> Result<(LinkMetadata, bool), LinoSerializationError> {
196 let mut metadata = LinkMetadata::new();
197 let mut registered = false;
198 let mut flag_bits = 0u8;
199 for field in fields {
200 let LiNo::Link {
201 id: Some(key),
202 values,
203 } = field
204 else {
205 return Err(LinoSerializationError::Structure(
206 "meta field must be an identified link".to_string(),
207 ));
208 };
209 match key.as_str() {
210 "t" => metadata = metadata.with_link_type(parse_link_type(single_ref(values)?)?),
211 "n" => metadata = metadata.with_named(single_ref(values)? == "1"),
212 "term" => metadata = metadata.with_term(percent_decode(single_ref(values)?)?),
213 "def" => metadata = metadata.with_definition(percent_decode(single_ref(values)?)?),
214 "lang" => metadata = metadata.with_language(percent_decode(single_ref(values)?)?),
215 "span" => metadata = metadata.with_span(parse_span(values)?),
216 "flags" => flag_bits = parse_u8(single_ref(values)?)?,
217 "reg" => registered = true,
218 other => {
219 return Err(LinoSerializationError::Structure(format!(
220 "unknown meta field `{other}`"
221 )));
222 }
223 }
224 }
225 if flag_bits != 0 {
226 let mut flags = LinkFlags::clean();
227 if flag_bits & 0b0001 != 0 {
228 flags = flags.with_error();
229 }
230 if flag_bits & 0b0010 != 0 {
231 flags = flags.with_containing_error();
232 }
233 if flag_bits & 0b0100 != 0 {
234 flags = flags.with_missing();
235 }
236 if flag_bits & 0b1000 != 0 {
237 flags = flags.with_extra();
238 }
239 metadata = metadata.with_flags(flags);
240 }
241 Ok((metadata, registered))
242}
243
244fn flag_bits(flags: LinkFlags) -> u8 {
246 u8::from(flags.is_error())
247 | (u8::from(flags.has_error()) << 1)
248 | (u8::from(flags.is_missing()) << 2)
249 | (u8::from(flags.is_extra()) << 3)
250}
251
252fn parse_span(values: &[LiNo<String>]) -> Result<SourceSpan, LinoSerializationError> {
254 if values.len() != 6 {
255 return Err(LinoSerializationError::Structure(
256 "span field requires six numbers".to_string(),
257 ));
258 }
259 let mut numbers = [0usize; 6];
260 for (slot, value) in numbers.iter_mut().zip(values) {
261 let LiNo::Ref(reference) = value else {
262 return Err(LinoSerializationError::Structure(
263 "span field values must be numbers".to_string(),
264 ));
265 };
266 *slot = reference.parse().map_err(|_| {
267 LinoSerializationError::Structure(format!("invalid span number `{reference}`"))
268 })?;
269 }
270 Ok(SourceSpan::new(
271 ByteRange::new(numbers[0], numbers[1]),
272 Point::new(numbers[2], numbers[3]),
273 Point::new(numbers[4], numbers[5]),
274 ))
275}
276
277fn single_ref(values: &[LiNo<String>]) -> Result<&str, LinoSerializationError> {
279 match values {
280 [LiNo::Ref(reference)] => Ok(reference),
281 _ => Err(LinoSerializationError::Structure(
282 "meta field must hold exactly one reference".to_string(),
283 )),
284 }
285}
286
287fn parse_link_type(token: &str) -> Result<LinkType, LinoSerializationError> {
289 Ok(match token {
290 "link" => LinkType::Link,
291 "reference" => LinkType::Reference,
292 "relation" => LinkType::Relation,
293 "language" => LinkType::Language,
294 "grammar" => LinkType::Grammar,
295 "type" => LinkType::Type,
296 "concept" => LinkType::Concept,
297 "syntax" => LinkType::Syntax,
298 "field" => LinkType::Field,
299 "trivia" => LinkType::Trivia,
300 "token" => LinkType::Token,
301 "document" => LinkType::Document,
302 "semantic" => LinkType::Semantic,
303 "region" => LinkType::Region,
304 "object" => LinkType::Object,
305 other => {
306 return Err(LinoSerializationError::Structure(format!(
307 "unknown link type `{other}`"
308 )));
309 }
310 })
311}
312
313fn parse_u64(value: &str) -> Result<u64, LinoSerializationError> {
314 value
315 .parse()
316 .map_err(|_| LinoSerializationError::Structure(format!("invalid link id `{value}`")))
317}
318
319fn parse_u8(value: &str) -> Result<u8, LinoSerializationError> {
320 value
321 .parse()
322 .map_err(|_| LinoSerializationError::Structure(format!("invalid flags value `{value}`")))
323}
324
325fn percent_encode(value: &str) -> String {
330 if value.is_empty() {
331 return "%".to_string();
332 }
333 let mut encoded = String::with_capacity(value.len());
334 for &byte in value.as_bytes() {
335 if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.') {
336 encoded.push(byte as char);
337 } else {
338 write!(encoded, "%{byte:02X}").expect("writing to a String never fails");
339 }
340 }
341 encoded
342}
343
344fn percent_decode(value: &str) -> Result<String, LinoSerializationError> {
346 if value == "%" {
347 return Ok(String::new());
348 }
349 let bytes = value.as_bytes();
350 let mut decoded = Vec::with_capacity(bytes.len());
351 let mut index = 0;
352 while index < bytes.len() {
353 if bytes[index] == b'%' {
354 if index + 2 >= bytes.len() {
355 return Err(LinoSerializationError::Structure(
356 "truncated percent escape".to_string(),
357 ));
358 }
359 let high = hex_value(bytes[index + 1])?;
360 let low = hex_value(bytes[index + 2])?;
361 decoded.push((high << 4) | low);
362 index += 3;
363 } else {
364 decoded.push(bytes[index]);
365 index += 1;
366 }
367 }
368 String::from_utf8(decoded).map_err(|_| {
369 LinoSerializationError::Structure("percent escape is not valid UTF-8".to_string())
370 })
371}
372
373fn hex_value(byte: u8) -> Result<u8, LinoSerializationError> {
374 match byte {
375 b'0'..=b'9' => Ok(byte - b'0'),
376 b'a'..=b'f' => Ok(byte - b'a' + 10),
377 b'A'..=b'F' => Ok(byte - b'A' + 10),
378 _ => Err(LinoSerializationError::Structure(
379 "invalid percent escape digit".to_string(),
380 )),
381 }
382}