Skip to main content

boa_ast/
position.rs

1use std::{
2    cmp::Ordering,
3    fmt::{self, Debug},
4    num::NonZeroU32,
5};
6
7/// A position in the ECMAScript source code.
8///
9/// Stores both the column number and the line number.
10///
11/// ## Similar Implementations
12/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216)
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct Position {
16    /// Line number.
17    line_number: NonZeroU32,
18    /// Column number.
19    column_number: NonZeroU32,
20}
21
22impl Default for Position {
23    /// Creates a new [`Position`] with line and column set to `1`.
24    #[inline]
25    fn default() -> Self {
26        Self::new(1, 1)
27    }
28}
29
30impl Position {
31    /// Creates a new `Position` from Non-Zero values.
32    ///
33    /// # Panics
34    ///
35    /// Will panic if the line number or column number is zero.
36    #[inline]
37    #[track_caller]
38    #[must_use]
39    pub const fn new(line_number: u32, column_number: u32) -> Self {
40        Self {
41            line_number: NonZeroU32::new(line_number).expect("line number cannot be 0"),
42            column_number: NonZeroU32::new(column_number).expect("column number cannot be 0"),
43        }
44    }
45
46    /// Gets the line number of the position.
47    #[inline]
48    #[must_use]
49    pub const fn line_number(self) -> u32 {
50        self.line_number.get()
51    }
52
53    /// Gets the column number of the position.
54    #[inline]
55    #[must_use]
56    pub const fn column_number(self) -> u32 {
57        self.column_number.get()
58    }
59}
60
61impl fmt::Display for Position {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}:{}", self.line_number, self.column_number)
64    }
65}
66
67impl From<PositionGroup> for Position {
68    #[inline]
69    fn from(value: PositionGroup) -> Self {
70        value.pos
71    }
72}
73
74impl From<(NonZeroU32, NonZeroU32)> for Position {
75    #[inline]
76    fn from(value: (NonZeroU32, NonZeroU32)) -> Self {
77        Position {
78            line_number: value.0,
79            column_number: value.1,
80        }
81    }
82}
83
84impl From<(u32, u32)> for Position {
85    #[inline]
86    #[track_caller]
87    fn from(value: (u32, u32)) -> Self {
88        Position::new(value.0, value.1)
89    }
90}
91
92#[cfg(feature = "arbitrary")]
93impl<'a> arbitrary::Arbitrary<'a> for Span {
94    fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
95        Ok(Span::new(Position::new(1, 1), Position::new(1, 1)))
96    }
97}
98
99/// Linear position in the ECMAScript source code.
100///
101/// Stores linear position in the source code.
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
104#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
105pub struct LinearPosition {
106    pos: usize,
107}
108
109impl LinearPosition {
110    /// Creates a new `LinearPosition`.
111    #[inline]
112    #[must_use]
113    pub const fn new(pos: usize) -> Self {
114        Self { pos }
115    }
116    /// Gets the linear position.
117    #[inline]
118    #[must_use]
119    pub const fn pos(self) -> usize {
120        self.pos
121    }
122}
123impl fmt::Display for LinearPosition {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{}", self.pos())
126    }
127}
128
129/// A span in the ECMAScript source code.
130///
131/// Stores a start position and an end position.
132///
133/// Note that spans are of the form [start, end) i.e. that the start position is inclusive
134/// and the end position is exclusive.
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[derive(Clone, Copy, PartialEq, Eq, Hash)]
137pub struct Span {
138    start: Position,
139    end: Position,
140}
141
142impl Debug for Span {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(
145            f,
146            "Span(({}, {}), ({}, {}))",
147            self.start.line_number,
148            self.start.column_number,
149            self.end.line_number,
150            self.end.column_number,
151        )
152    }
153}
154
155impl Span {
156    /// Creates a new `Span`.
157    ///
158    /// # Panics
159    ///
160    /// Panics if the start position is bigger than the end position.
161    #[inline]
162    #[track_caller]
163    #[must_use]
164    pub fn new<T, U>(start: T, end: U) -> Self
165    where
166        T: Into<Position>,
167        U: Into<Position>,
168    {
169        let start = start.into();
170        let end = end.into();
171
172        assert!(start <= end, "a span cannot start after its end");
173
174        Self { start, end }
175    }
176
177    /// Gets the starting position of the span.
178    #[inline]
179    #[must_use]
180    pub const fn start(self) -> Position {
181        self.start
182    }
183
184    /// Gets the final position of the span.
185    #[inline]
186    #[must_use]
187    pub const fn end(self) -> Position {
188        self.end
189    }
190
191    /// Checks if this span inclusively contains another span or position.
192    pub fn contains<S>(self, other: S) -> bool
193    where
194        S: Into<Self>,
195    {
196        let other = other.into();
197        self.start <= other.start && self.end >= other.end
198    }
199}
200
201impl From<Position> for Span {
202    fn from(pos: Position) -> Self {
203        Self {
204            start: pos,
205            end: pos,
206        }
207    }
208}
209
210impl Spanned for Span {
211    #[inline]
212    fn span(&self) -> Span {
213        *self
214    }
215}
216
217impl<T: Spanned> Spanned for &T {
218    #[inline]
219    fn span(&self) -> Span {
220        T::span(*self)
221    }
222}
223
224impl PartialOrd for Span {
225    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
226        if self == other {
227            Some(Ordering::Equal)
228        } else if self.end < other.start {
229            Some(Ordering::Less)
230        } else if self.start > other.end {
231            Some(Ordering::Greater)
232        } else {
233            None
234        }
235    }
236}
237
238impl fmt::Display for Span {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        write!(f, "[{}..{}]", self.start, self.end)
241    }
242}
243
244/// An element of the AST or any type that can be located in the source with a Span.
245pub trait Spanned {
246    /// Returns a span from the current type.
247    #[must_use]
248    fn span(&self) -> Span;
249}
250
251/// A linear span in the ECMAScript source code.
252///
253/// Stores a linear start position and a linear end position.
254///
255/// Note that linear spans are of the form [start, end) i.e. that the
256/// start position is inclusive and the end position is exclusive.
257#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
258#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
259pub struct LinearSpan {
260    start: LinearPosition,
261    end: LinearPosition,
262}
263impl LinearSpan {
264    /// Creates a new `LinearSpan`.
265    ///
266    /// # Panics
267    ///
268    /// Panics if the start position is bigger than the end position.
269    #[inline]
270    #[track_caller]
271    #[must_use]
272    pub const fn new(start: LinearPosition, end: LinearPosition) -> Self {
273        assert!(
274            start.pos <= end.pos,
275            "a linear span cannot start after its end"
276        );
277
278        Self { start, end }
279    }
280
281    /// Test if the span is empty.
282    #[inline]
283    #[must_use]
284    pub fn is_empty(self) -> bool {
285        self.start == self.end
286    }
287
288    /// Gets the starting position of the span.
289    #[inline]
290    #[must_use]
291    pub const fn start(self) -> LinearPosition {
292        self.start
293    }
294
295    /// Gets the final position of the span.
296    #[inline]
297    #[must_use]
298    pub const fn end(self) -> LinearPosition {
299        self.end
300    }
301
302    /// Checks if this span inclusively contains another span or position.
303    pub fn contains<S>(self, other: S) -> bool
304    where
305        S: Into<Self>,
306    {
307        let other = other.into();
308        self.start <= other.start && self.end >= other.end
309    }
310
311    /// Gets the starting position of the span.
312    #[inline]
313    #[must_use]
314    pub fn union(self, other: impl Into<Self>) -> Self {
315        let other: Self = other.into();
316        Self {
317            start: LinearPosition::new(self.start.pos.min(other.start.pos)),
318            end: LinearPosition::new(self.end.pos.max(other.end.pos)),
319        }
320    }
321}
322#[cfg(feature = "arbitrary")]
323impl<'a> arbitrary::Arbitrary<'a> for LinearSpan {
324    fn arbitrary(_: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
325        let zero_pos = LinearPosition::new(0);
326        Ok(Self::new(zero_pos, zero_pos))
327    }
328}
329
330impl From<LinearPosition> for LinearSpan {
331    fn from(pos: LinearPosition) -> Self {
332        Self {
333            start: pos,
334            end: pos,
335        }
336    }
337}
338
339impl PartialOrd for LinearSpan {
340    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
341        if self == other {
342            Some(Ordering::Equal)
343        } else if self.end < other.start {
344            Some(Ordering::Less)
345        } else if self.start > other.end {
346            Some(Ordering::Greater)
347        } else {
348            None
349        }
350    }
351}
352
353/// Stores a `LinearSpan` but `PartialEq`, `Eq` always return true.
354#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
355#[derive(Debug, Clone, Copy)]
356pub struct LinearSpanIgnoreEq(pub LinearSpan);
357impl PartialEq for LinearSpanIgnoreEq {
358    fn eq(&self, _: &Self) -> bool {
359        true
360    }
361}
362impl From<LinearSpan> for LinearSpanIgnoreEq {
363    fn from(value: LinearSpan) -> Self {
364        Self(value)
365    }
366}
367#[cfg(feature = "arbitrary")]
368impl<'a> arbitrary::Arbitrary<'a> for LinearSpanIgnoreEq {
369    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
370        Ok(Self(LinearSpan::arbitrary(u)?))
371    }
372}
373
374#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376/// A position group of `LinearPosition` and `Position` related to the same position in the ECMAScript source code.
377pub struct PositionGroup {
378    pos: Position,
379    linear_pos: LinearPosition,
380}
381impl PositionGroup {
382    /// Creates a new `PositionGroup`.
383    #[inline]
384    #[must_use]
385    pub const fn new(pos: Position, linear_pos: LinearPosition) -> Self {
386        Self { pos, linear_pos }
387    }
388    /// Get the `Position`.
389    #[inline]
390    #[must_use]
391    pub fn position(&self) -> Position {
392        self.pos
393    }
394    /// Get the `LinearPosition`.
395    #[inline]
396    #[must_use]
397    pub fn linear_position(&self) -> LinearPosition {
398        self.linear_pos
399    }
400
401    /// Gets the line number of the position.
402    #[inline]
403    #[must_use]
404    pub const fn line_number(&self) -> u32 {
405        self.pos.line_number()
406    }
407
408    /// Gets the column number of the position.
409    #[inline]
410    #[must_use]
411    pub const fn column_number(&self) -> u32 {
412        self.pos.column_number()
413    }
414}
415
416#[cfg(test)]
417mod tests {
418    #![allow(clippy::similar_names)]
419    #![allow(unused_must_use)]
420    use super::{LinearPosition, LinearSpan, Position, Span};
421
422    /// Checks that we cannot create a position with 0 as the column.
423    #[test]
424    #[should_panic(expected = "column number cannot be 0")]
425    fn invalid_position_column() {
426        Position::new(10, 0);
427    }
428
429    /// Checks that we cannot create a position with 0 as the line.
430    #[test]
431    #[should_panic(expected = "line number cannot be 0")]
432    fn invalid_position_line() {
433        Position::new(0, 10);
434    }
435
436    /// Checks that the `PartialEq` implementation of `Position` is consistent.
437    #[test]
438    fn position_equality() {
439        assert_eq!(Position::new(10, 50), Position::new(10, 50));
440        assert_ne!(Position::new(10, 50), Position::new(10, 51));
441        assert_ne!(Position::new(10, 50), Position::new(11, 50));
442        assert_ne!(Position::new(10, 50), Position::new(11, 51));
443    }
444
445    /// Checks that the `PartialEq` implementation of `LinearPosition` is consistent.
446    #[test]
447    fn linear_position_equality() {
448        assert_eq!(LinearPosition::new(1050), LinearPosition::new(1050));
449        assert_ne!(LinearPosition::new(1050), LinearPosition::new(1051));
450    }
451
452    /// Checks that the `PartialOrd` implementation of `Position` is consistent.
453    #[test]
454    fn position_order() {
455        assert!(Position::new(10, 50) < Position::new(10, 51));
456        assert!(Position::new(9, 50) < Position::new(10, 50));
457        assert!(Position::new(10, 50) < Position::new(11, 51));
458        assert!(Position::new(10, 50) < Position::new(11, 49));
459
460        assert!(Position::new(10, 51) > Position::new(10, 50));
461        assert!(Position::new(10, 50) > Position::new(9, 50));
462        assert!(Position::new(11, 51) > Position::new(10, 50));
463        assert!(Position::new(11, 49) > Position::new(10, 50));
464    }
465
466    /// Checks that the `PartialOrd` implementation of `LinearPosition` is consistent.
467    #[test]
468    fn linear_position_order() {
469        assert!(LinearPosition::new(1050) < LinearPosition::new(1051));
470        assert!(LinearPosition::new(1149) > LinearPosition::new(1050));
471    }
472
473    /// Checks that the position getters actually retrieve correct values.
474    #[test]
475    fn position_getters() {
476        let pos = Position::new(10, 50);
477        assert_eq!(pos.line_number(), 10);
478        assert_eq!(pos.column_number(), 50);
479    }
480
481    /// Checks that the string representation of a position is correct.
482    #[test]
483    fn position_to_string() {
484        let pos = Position::new(10, 50);
485
486        assert_eq!("10:50", pos.to_string());
487        assert_eq!("10:50", pos.to_string());
488    }
489
490    /// Checks that we cannot create an invalid span.
491    #[test]
492    #[should_panic(expected = "a span cannot start after its end")]
493    fn invalid_span() {
494        let a = Position::new(10, 30);
495        let b = Position::new(10, 50);
496        Span::new(b, a);
497    }
498
499    /// Checks that we cannot create an invalid linear span.
500    #[test]
501    #[should_panic(expected = "a linear span cannot start after its end")]
502    fn invalid_linear_span() {
503        let a = LinearPosition::new(1030);
504        let b = LinearPosition::new(1050);
505        LinearSpan::new(b, a);
506    }
507
508    /// Checks that we can create valid spans.
509    #[test]
510    fn span_creation() {
511        let a = Position::new(10, 30);
512        let b = Position::new(10, 50);
513
514        Span::new(a, b);
515        Span::new(a, a);
516        Span::from(a);
517    }
518
519    /// Checks that we can create valid linear spans.
520    #[test]
521    fn linear_span_creation() {
522        let a = LinearPosition::new(1030);
523        let b = LinearPosition::new(1050);
524
525        LinearSpan::new(a, b);
526        let span_aa = LinearSpan::new(a, a);
527        assert_eq!(LinearSpan::from(a), span_aa);
528    }
529
530    /// Checks that the `PartialEq` implementation of `Span` is consistent.
531    #[test]
532    fn span_equality() {
533        let a = Position::new(10, 50);
534        let b = Position::new(10, 52);
535        let c = Position::new(11, 20);
536
537        let span_ab = Span::new(a, b);
538        let span_ab_2 = Span::new(a, b);
539        let span_ac = Span::new(a, c);
540        let span_bc = Span::new(b, c);
541
542        assert_eq!(span_ab, span_ab_2);
543        assert_ne!(span_ab, span_ac);
544        assert_ne!(span_ab, span_bc);
545        assert_ne!(span_bc, span_ac);
546
547        let span_a = Span::from(a);
548        let span_aa = Span::new(a, a);
549
550        assert_eq!(span_a, span_aa);
551    }
552
553    /// Checks that the `PartialEq` implementation of `LinearSpan` is consistent.
554    #[test]
555    fn linear_span_equality() {
556        let a = LinearPosition::new(1030);
557        let b = LinearPosition::new(1050);
558        let c = LinearPosition::new(1150);
559
560        let span_ab = LinearSpan::new(a, b);
561        let span_ab_2 = LinearSpan::new(a, b);
562        let span_ac = LinearSpan::new(a, c);
563        let span_bc = LinearSpan::new(b, c);
564
565        assert_eq!(span_ab, span_ab_2);
566        assert_ne!(span_ab, span_ac);
567        assert_ne!(span_ab, span_bc);
568        assert_ne!(span_bc, span_ac);
569    }
570
571    /// Checks that the getters retrieve the correct value.
572    #[test]
573    fn span_getters() {
574        let a = Position::new(10, 50);
575        let b = Position::new(10, 52);
576
577        let span = Span::new(a, b);
578
579        assert_eq!(span.start(), a);
580        assert_eq!(span.end(), b);
581    }
582
583    /// Checks that the `Span::contains()` method works properly.
584    #[test]
585    fn span_contains() {
586        let a = Position::new(10, 50);
587        let b = Position::new(10, 52);
588        let c = Position::new(11, 20);
589        let d = Position::new(12, 5);
590
591        let span_ac = Span::new(a, c);
592        assert!(span_ac.contains(b));
593
594        let span_ab = Span::new(a, b);
595        let span_cd = Span::new(c, d);
596
597        assert!(!span_ab.contains(span_cd));
598        assert!(span_ab.contains(b));
599
600        let span_ad = Span::new(a, d);
601        let span_bc = Span::new(b, c);
602
603        assert!(span_ad.contains(span_bc));
604        assert!(!span_bc.contains(span_ad));
605
606        let span_ac = Span::new(a, c);
607        let span_bd = Span::new(b, d);
608
609        assert!(!span_ac.contains(span_bd));
610        assert!(!span_bd.contains(span_ac));
611    }
612
613    /// Checks that the `LinearSpan::contains()` method works properly.
614    #[test]
615    fn linear_span_contains() {
616        let a = LinearPosition::new(1050);
617        let b = LinearPosition::new(1080);
618        let c = LinearPosition::new(1120);
619        let d = LinearPosition::new(1125);
620
621        let span_ac = LinearSpan::new(a, c);
622        assert!(span_ac.contains(b));
623
624        let span_ab = LinearSpan::new(a, b);
625        let span_cd = LinearSpan::new(c, d);
626
627        assert!(!span_ab.contains(span_cd));
628        assert!(span_ab.contains(b));
629
630        let span_ad = LinearSpan::new(a, d);
631        let span_bc = LinearSpan::new(b, c);
632
633        assert!(span_ad.contains(span_bc));
634        assert!(!span_bc.contains(span_ad));
635
636        let span_ac = LinearSpan::new(a, c);
637        let span_bd = LinearSpan::new(b, d);
638
639        assert!(!span_ac.contains(span_bd));
640        assert!(!span_bd.contains(span_ac));
641    }
642
643    /// Checks that the string representation of a span is correct.
644    #[test]
645    fn span_to_string() {
646        let a = Position::new(10, 50);
647        let b = Position::new(11, 20);
648        let span = Span::new(a, b);
649
650        assert_eq!("[10:50..11:20]", span.to_string());
651        assert_eq!("[10:50..11:20]", span.to_string());
652    }
653
654    /// Checks that the ordering of spans is correct.
655    #[test]
656    fn span_ordering() {
657        let a = Position::new(10, 50);
658        let b = Position::new(10, 52);
659        let c = Position::new(11, 20);
660        let d = Position::new(12, 5);
661
662        let span_ab = Span::new(a, b);
663        let span_cd = Span::new(c, d);
664
665        assert!(span_ab < span_cd);
666        assert!(span_cd > span_ab);
667    }
668
669    /// Checks that the ordering of linear spans is correct.
670    #[test]
671    fn linear_span_ordering() {
672        let a = LinearPosition::new(1050);
673        let b = LinearPosition::new(1052);
674        let c = LinearPosition::new(1120);
675        let d = LinearPosition::new(1125);
676
677        let span_ab = LinearSpan::new(a, b);
678        let span_cd = LinearSpan::new(c, d);
679
680        let span_ac = LinearSpan::new(a, c);
681        let span_bd = LinearSpan::new(b, d);
682
683        assert!(span_ab < span_cd);
684        assert!(span_cd > span_ab);
685        assert_eq!(span_bd.partial_cmp(&span_ac), None);
686        assert_eq!(span_ac.partial_cmp(&span_bd), None);
687    }
688
689    /// Checks that the ordering of linear spans is correct.
690    #[test]
691    fn linear_union() {
692        let a = LinearPosition::new(1050);
693        let b = LinearPosition::new(1052);
694        let c = LinearPosition::new(1120);
695        let d = LinearPosition::new(1125);
696
697        let span_ab = LinearSpan::new(a, b);
698        let span_ad = LinearSpan::new(a, d);
699        let span_bc = LinearSpan::new(b, c);
700        let span_cd = LinearSpan::new(c, d);
701        let span_ac = LinearSpan::new(a, c);
702        let span_bd = LinearSpan::new(b, d);
703
704        assert_eq!(span_bd.union(a), span_ad);
705        assert_eq!(span_ab.union(a), span_ab);
706        assert_eq!(span_bd.union(span_ac), span_ad);
707        assert_eq!(span_ac.union(span_bd), span_ad);
708        assert_eq!(span_ac.union(span_bd), span_ad);
709        assert_eq!(span_ac.union(b), span_ac);
710        assert_eq!(span_bc.union(span_ab), span_ac);
711        assert_eq!(span_ab.union(span_bc), span_ac);
712        assert_eq!(span_ac.union(span_ab), span_ac);
713        assert_eq!(span_cd.union(a), span_ad);
714        assert_eq!(span_cd.union(span_bc), span_bd);
715    }
716}
717
718// TODO: union Span & LinearSpan into `SpanBase<T>` and then:
719//       * Span = SpanBase<Position>;
720//       * LinearSpan = SpanBase<LinearPosition>;
721//       ?