formally_support/location.rs
1//
2// ::formally - the open-source formal methods toolchain
3//
4// Copyright (c) 2025 Nicola Gigante
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23//
24
25use std::{
26 borrow::Cow,
27 error::Error,
28 fmt::{self, Display, Formatter},
29 hash::Hash,
30 ops::Deref,
31 ops::Sub,
32 path::{Path, PathBuf},
33 sync::Arc,
34};
35
36use crate::*;
37
38use derive_more::Display;
39
40/// Wrap any type with location information.
41///
42/// [Loc] is used to wrap a type to give it location information. It is a simple struct adjoining
43/// any given type to an `Option<Span>` and implementing [Located] and [Locatable]. This is meant to
44/// be done to types that are not under control and cannot be made to implement the traits directly
45/// because of the orphan instance rules, or because giving them a [Span] all the time does not make
46/// sense. Otherwise, deriving [Located] and [Locatable] directly using the associated derive
47/// macros is preferred.
48///
49/// It is often controverial to decide whether a [Located] type should compare equal accounting for
50/// its span or not. For this reason, [Loc] intentionally does not implement [PartialEq] nor [Eq].
51#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
52pub struct Loc<T> {
53 pub value: T,
54 pub span: Option<Span>,
55}
56
57impl<T> Loc<T> {
58 /// Creates a new [Loc] from a value without any [Span] information.
59 pub const fn new(value: T) -> Loc<T> {
60 Loc { span: None, value }
61 }
62
63 /// Transforms the inner value by applying the given function
64 pub fn map<F, R>(self, f: F) -> Loc<R>
65 where
66 F: FnOnce(T) -> R,
67 {
68 Loc {
69 value: f(self.value),
70 span: self.span,
71 }
72 }
73}
74
75impl<T> Located for Loc<T> {
76 fn span(&self) -> Option<Span> {
77 self.span.clone()
78 }
79}
80
81impl<T> Locatable for Loc<T> {
82 type Located = Loc<T>;
83
84 fn over(self, span: impl Into<Option<Span>>) -> Loc<T> {
85 Loc {
86 span: span.into(),
87 ..self
88 }
89 }
90}
91
92impl<T: Display> Display for Loc<T> {
93 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
94 Display::fmt(&self.value, f)
95 }
96}
97
98impl<T: Error> Error for Loc<T> {}
99
100impl<T> From<T> for Loc<T> {
101 fn from(value: T) -> Self {
102 Loc::new(value)
103 }
104}
105
106impl<T> Deref for Loc<T> {
107 type Target = T;
108
109 fn deref(&self) -> &T {
110 &self.value
111 }
112}
113
114/// Tracks the origin of a set of source locations
115///
116/// This type is used as a component of a [Span] to tell the origin of a source file
117/// (e.g. an actual file, or stdin, or a buffer, etc.). This is mainly useful to give show error
118/// messages to the user.
119#[derive(Debug, Clone, Default, Hash, PartialEq, Eq)]
120pub enum Origin {
121 /// The origin of the source locations is unknown
122 #[default]
123 Unknown,
124 /// The origin of the source locations is an in-memory buffer or string
125 Buffer,
126 /// The origin of the source locations is the standard input stream
127 StdIn,
128 /// The origin of the source locations is a file at the given path
129 Path(Arc<PathBuf>),
130}
131
132impl Display for Origin {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 Origin::Unknown => f.write_str("<unknown>"),
136 Origin::StdIn => f.write_str("<stdin>"),
137 Origin::Buffer => f.write_str("<buffer>"),
138 Origin::Path(path) => Display::fmt(&path.display(), f),
139 }
140 }
141}
142
143impl<T: AsRef<Path>> From<T> for Origin {
144 fn from(value: T) -> Self {
145 Origin::Path(Arc::new(PathBuf::from(value.as_ref())))
146 }
147}
148
149/// Represent a position in a source file.
150///
151/// [Location] represents the position of a single character or token in a source file, given by its
152/// `position` which is the actual index in the corresponding buffer, and a pair of `line` and
153/// `column` information.
154///
155/// Line and column information start from zero, but the [Display] instance of [Location] and [Span]
156/// show line numbers to the user as starting from one.
157#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
158pub struct Location {
159 /// The index of this location in the corresponding buffer
160 pub position: usize,
161 /// The line number corresponding to this location
162 pub line: usize,
163 /// The column number corresponding to this location
164 pub column: usize,
165}
166
167impl Display for Location {
168 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
169 write!(f, "{}:{}", self.line + 1, self.column)
170 }
171}
172
173impl Sub for Location {
174 type Output = usize;
175
176 fn sub(self, rhs: Self) -> usize {
177 self.position - rhs.position
178 }
179}
180
181/// Track an interval between two locations in a source file.
182///
183/// [Span] tracks an interval in a source file, represented either as a pair of `begin`/`end`
184/// [Location] objects in the [Span::Span] variant, or as the [Span::Whole] variant that represents
185/// the entire source file under consideration.
186///
187/// All the components in `formally` try hard to preserve locations and spans across the board, in
188/// order to give users informative error messages even for errors originating quite far from the
189/// parsers.
190///
191/// This means that any type that can be created even indirectly by parsing something from the user
192/// should probably contain a [Span] and implement at least the [Located] trait, and maybe also
193/// [Locatable].
194#[derive(Debug, Clone, Hash, PartialEq, Eq)]
195pub enum Span {
196 /// The [Span] represents the whole source file at the given [Origin].
197 Whole(Origin),
198 /// The [Span] represents a built-in or primitive entity such as functions defined by SMT
199 /// theories
200 Builtin,
201 /// The [Span] represents the interval between `begin` (included) and `end` (excluded) at the
202 /// given [Origin].
203 Span {
204 /// The origin of the span.
205 origin: Origin,
206 /// The starting position of the interval (included).
207 begin: Location,
208 /// The end of the interval (excluded).
209 end: Location,
210 },
211}
212
213impl Default for Span {
214 /// The default [Span] is `Span::Whole(Origin::Unknown)`
215 fn default() -> Self {
216 Span::Whole(Origin::default())
217 }
218}
219
220impl Span {
221 /// Creates a default [Span], i.e. `Span::Whole(Origin::Unknown)`
222 pub fn new() -> Span {
223 Span::default()
224 }
225}
226
227impl Display for Span {
228 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
229 match self {
230 Span::Whole(origin) => Display::fmt(origin, f),
231 Span::Builtin => write!(f, "builtin"),
232 Span::Span { origin, begin, end } => {
233 if end.column > begin.column + 1 {
234 write!(
235 f,
236 "{}:[{}:{}, {}:{})",
237 origin,
238 begin.line + 1,
239 begin.column,
240 end.line + 1,
241 end.column
242 )
243 } else {
244 write!(f, "{}:{}:{}", origin, begin.line + 1, begin.column)
245 }
246 }
247 }
248 }
249}
250
251/// Types that track a [Span].
252///
253/// [Located] is a small trait to get a [Span] from types that track span information. The [Span] is
254/// returned in an [Option] because even types that track spans very precisely may not have such
255/// information available in the first place, probably when constructed by hand instead of being
256/// parsed.
257///
258/// [Located] can be automatically derived (see the corresponding proc-macro for details).
259pub trait Located {
260 /// Gets the [Span] tracked by the type.
261 fn span(&self) -> Option<Span>;
262}
263
264impl Located for Option<Span> {
265 fn span(&self) -> Option<Span> {
266 self.clone()
267 }
268}
269
270/// Add or set a [Span] to a type.
271///
272/// [Locatable] is for [Located] types that support changing their tracked [Span], or for types that
273/// can be turned into [Located] types. The [Locatable::over] method is a builder method used to
274/// create a new object with a different [Span], and the `Located` associated type tell the type of
275/// the result.
276///
277/// Example:
278/// ```text
279/// let term = term!(* x y).over(span);
280/// ```
281///
282/// [Locatable] can be automatically derived (see the corresponding proc-macro for details).
283pub trait Locatable: Sized {
284 type Located: Located;
285
286 /// Turns `self` into an object of type `Self::Located` tracking the new span.
287 fn over(self, span: impl Into<Option<Span>>) -> Self::Located;
288}
289
290impl Locatable for Option<Span> {
291 type Located = Option<Span>;
292
293 fn over(self, span: impl Into<Option<Span>>) -> Self::Located {
294 span.into()
295 }
296}
297
298/// A string associated with its source span.
299///
300/// [Identifier] just holds a `Cow<'a, str>` together with a [Span]. It is used in `formally`
301/// whenever an identifier coming from an origin source code has to be stored or passed as argument.
302/// Using [Cow] allows to avoid unnecessary clones when identifiers are passed directly as arguments
303/// from string slices.
304#[derive(Debug, Clone, Hash, PartialEq, Eq, Display, Located, Locatable)]
305#[display("{name}")]
306pub struct Identifier<'a> {
307 name: Cow<'a, str>,
308 span: Option<Span>,
309}
310
311impl<'a> Identifier<'a> {
312 pub const fn new(name: &'a str) -> Self {
313 Identifier {
314 name: Cow::Borrowed(name),
315 span: None,
316 }
317 }
318
319 /// Return a reference to the stored name.
320 pub fn name(&self) -> &str {
321 &self.name
322 }
323
324 /// Turn the identifier into the inner `Cow<'a, str>`
325 pub fn into_inner(self) -> Cow<'a, str> {
326 self.name
327 }
328
329 /// Turn the identifier into its inner string.
330 pub fn into_string(self) -> String {
331 self.name.into_owned()
332 }
333
334 pub fn into_owned(self) -> Identifier<'static> {
335 match self.name {
336 Cow::Borrowed(name) => Identifier {
337 name: Cow::Owned(name.to_string()),
338 span: self.span,
339 },
340 Cow::Owned(name) => Identifier {
341 name: Cow::Owned(name),
342 span: self.span,
343 },
344 }
345 }
346}
347
348impl Deref for Identifier<'_> {
349 type Target = str;
350
351 fn deref(&self) -> &str {
352 &self.name
353 }
354}
355
356impl From<String> for Identifier<'_> {
357 fn from(name: String) -> Self {
358 Identifier {
359 name: Cow::from(name),
360 span: None,
361 }
362 }
363}
364
365impl<'a> From<&'a str> for Identifier<'a> {
366 fn from(name: &'a str) -> Self {
367 Identifier::new(name)
368 }
369}
370
371impl<'a> From<Cow<'a, str>> for Identifier<'a> {
372 fn from(name: Cow<'a, str>) -> Self {
373 Identifier { name, span: None }
374 }
375}