links_notation/syntax.rs
1//! How the references of a parsed document were written.
2//!
3//! The links [`parse_lino_to_links`](crate::parse_lino_to_links) returns hold
4//! what each reference means: `1` and `'1'` are both the reference `1`. The
5//! links [`parse_lino_to_syntax_links`](crate::parse_lino_to_syntax_links)
6//! returns hold a [`SyntaxReference`] instead, which keeps the meaning and also
7//! the text the reference was written as, so a tool can tell the two apart
8//! while comparing, hashing and formatting them exactly as before.
9
10use crate::quotes::{QUOTES, read_reference};
11use std::fmt;
12use std::hash::{Hash, Hasher};
13
14/// A reference read from a document, together with the text it was written as.
15///
16/// Equality, hashing, ordering and [`Display`](fmt::Display) use the
17/// [`value`](Self::value) only, so `1` and `'1'` are equal references and a
18/// `LiNo<SyntaxReference>` formats exactly like the `LiNo<String>` with the
19/// same values. [`raw`](Self::raw) and [`quote`](Self::quote) say how the
20/// reference was written.
21///
22/// # Examples
23/// ```
24/// use links_notation::{LiNo, SyntaxReference, parse_lino_to_syntax_links};
25///
26/// let links = parse_lino_to_syntax_links("(t 1 '1')").unwrap();
27/// let LiNo::Link { values, .. } = &links[0] else { unreachable!() };
28/// let (LiNo::Ref(bare), LiNo::Ref(quoted)) = (&values[1], &values[2]) else {
29/// unreachable!()
30/// };
31/// assert_eq!(bare, quoted);
32/// assert_eq!(bare.raw.as_deref(), Some("1"));
33/// assert_eq!(quoted.raw.as_deref(), Some("'1'"));
34/// assert_eq!((bare.quote(), quoted.quote()), (None, Some('\'')));
35/// assert_eq!(quoted.to_string(), "1");
36/// assert_eq!(SyntaxReference::from("x").raw, None);
37/// ```
38#[derive(Debug, Clone, Default)]
39pub struct SyntaxReference {
40 /// What the reference means: its delimiters and escapes resolved and a
41 /// versioned literal decoded. This is the ID `parse_lino_to_links` reads.
42 pub value: String,
43 /// The reference exactly as the document wrote it: quotes, doubled quote
44 /// runs and `~1{...}` literal syntax included, and the line breaks of a
45 /// multi-line quoted reference. Taken from the document as the caller
46 /// passed it, comments included. `None` for a reference built in code.
47 pub raw: Option<String>,
48}
49
50impl SyntaxReference {
51 /// A reference that was not read from a document, so it has no raw text.
52 pub fn new(value: impl Into<String>) -> Self {
53 Self {
54 value: value.into(),
55 raw: None,
56 }
57 }
58
59 /// A reference with the text it was written as.
60 pub fn with_raw(value: impl Into<String>, raw: impl Into<String>) -> Self {
61 Self {
62 value: value.into(),
63 raw: Some(raw.into()),
64 }
65 }
66
67 /// What the reference means, the same as [`value`](Self::value).
68 pub fn as_str(&self) -> &str {
69 &self.value
70 }
71
72 /// The delimiter the reference was written between: `'`, `"` or `` ` ``
73 /// when it was quoted, and `None` when it was bare, a versioned literal, or
74 /// not read from a document. See [`reference_quote`].
75 pub fn quote(&self) -> Option<char> {
76 self.raw.as_deref().and_then(reference_quote)
77 }
78}
79
80impl PartialEq for SyntaxReference {
81 fn eq(&self, other: &Self) -> bool {
82 self.value == other.value
83 }
84}
85
86impl Eq for SyntaxReference {}
87
88impl Hash for SyntaxReference {
89 fn hash<H: Hasher>(&self, state: &mut H) {
90 self.value.hash(state);
91 }
92}
93
94impl PartialOrd for SyntaxReference {
95 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
96 Some(self.cmp(other))
97 }
98}
99
100impl Ord for SyntaxReference {
101 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
102 self.value.cmp(&other.value)
103 }
104}
105
106impl PartialEq<str> for SyntaxReference {
107 fn eq(&self, other: &str) -> bool {
108 self.value == other
109 }
110}
111
112impl PartialEq<&str> for SyntaxReference {
113 fn eq(&self, other: &&str) -> bool {
114 self.value == *other
115 }
116}
117
118impl PartialEq<String> for SyntaxReference {
119 fn eq(&self, other: &String) -> bool {
120 &self.value == other
121 }
122}
123
124/// Writes the [`value`](SyntaxReference::value), unescaped, the way a `String`
125/// ID is written: a `LiNo` quotes it where the notation needs quotes, so a
126/// `LiNo<SyntaxReference>` prints exactly what the `LiNo<String>` prints.
127impl fmt::Display for SyntaxReference {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 f.write_str(&self.value)
130 }
131}
132
133impl From<String> for SyntaxReference {
134 fn from(value: String) -> Self {
135 Self::new(value)
136 }
137}
138
139impl From<&str> for SyntaxReference {
140 fn from(value: &str) -> Self {
141 Self::new(value)
142 }
143}
144
145impl From<SyntaxReference> for String {
146 fn from(reference: SyntaxReference) -> Self {
147 reference.value
148 }
149}
150
151/// The delimiter a reference was written between, read from the reference as
152/// it was written: `'`, `"` or `` ` `` for one complete quoted reference
153/// (however many delimiters open it), and `None` for a bare reference, a
154/// versioned literal, or text that holds more than one reference.
155///
156/// # Examples
157/// ```
158/// use links_notation::reference_quote;
159///
160/// assert_eq!(reference_quote("'1'"), Some('\''));
161/// assert_eq!(reference_quote("\"\"a b\"\""), Some('"'));
162/// assert_eq!(reference_quote("`x`"), Some('`'));
163/// assert_eq!(reference_quote("1"), None);
164/// assert_eq!(reference_quote("~1{31}"), None);
165/// // A bare reference that starts with a quote.
166/// assert_eq!(reference_quote("'unclosed"), None);
167/// assert_eq!(reference_quote("'a' b"), None);
168/// assert_eq!(reference_quote(""), None);
169/// ```
170pub fn reference_quote(raw: &str) -> Option<char> {
171 let first = *raw.as_bytes().first()?;
172 if !QUOTES.contains(&first) {
173 return None;
174 }
175 let reading = read_reference(raw)?;
176 (reading.length == raw.len()).then_some(first as char)
177}