lady_deirdre/lexis/position.rs
1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Lady Deirdre", a compiler front-end foundation //
3// technology. //
4// //
5// This work is proprietary software with source-available code. //
6// //
7// To copy, use, distribute, or contribute to this work, you must agree to //
8// the terms of the General License Agreement: //
9// //
10// https://github.com/Eliah-Lakhin/lady-deirdre/blob/master/EULA.md //
11// //
12// The agreement grants a Basic Commercial License, allowing you to use //
13// this work in non-commercial and limited commercial products with a total //
14// gross revenue cap. To remove this commercial limit for one of your //
15// products, you must acquire a Full Commercial License. //
16// //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions. //
19// Contributions are governed by the "Contributions" section of the General //
20// License Agreement. //
21// //
22// Copying the work in parts is strictly forbidden, except as permitted //
23// under the General License Agreement. //
24// //
25// If you do not or cannot agree to the terms of this Agreement, //
26// do not use this work. //
27// //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid. //
30// //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин). //
32// All rights reserved. //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{
36 cmp::Ordering,
37 fmt::{Display, Formatter},
38 ops::AddAssign,
39};
40
41use crate::lexis::{Site, SourceCode, ToSite};
42
43/// An index of the line in the source code text.
44///
45/// Line numeration starts from 1, such that 1 denotes the first line,
46/// 2 denotes the second line, and so on.
47///
48/// Line 0 also denotes the first line.
49///
50/// If this number exceed the total number of lines, the value interpreted
51/// as the source code text end.
52pub type Line = usize;
53
54/// An index of the character of the line in the source code text.
55///
56/// Column numeration starts from 1, such that 1 denotes the first char,
57/// 2 denotes the second char, and so on.
58///
59/// Column 0 also denotes the first char.
60///
61/// If this number exceed the total number of characters of the line, the value
62/// interpreted as the end of the line.
63///
64/// Note that the line delimiters (`\n` and `\r` chars) are parts of the line
65/// tail.
66pub type Column = usize;
67
68/// A line-column index of the Unicode character within the source code text.
69///
70/// The line and the column indices are 1-based, and the Position object
71/// is always [valid](ToSite::is_valid_site) index for any source code.
72///
73/// For details, see [Line] and [Column] specifications.
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub struct Position {
76 /// A line number. This value is 1-based.
77 pub line: Line,
78
79 /// A number of the character in the line. This value is 1-based.
80 pub column: Column,
81}
82
83impl Ord for Position {
84 #[inline]
85 fn cmp(&self, other: &Self) -> Ordering {
86 if self.line < other.line {
87 return Ordering::Less;
88 }
89
90 if self.line > other.line {
91 return Ordering::Greater;
92 }
93
94 self.column.cmp(&other.column)
95 }
96}
97
98impl PartialOrd for Position {
99 #[inline(always)]
100 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
101 Some(self.cmp(other))
102 }
103}
104
105impl Default for Position {
106 #[inline(always)]
107 fn default() -> Self {
108 Self { line: 1, column: 1 }
109 }
110}
111
112impl Display for Position {
113 #[inline(always)]
114 fn fmt(&self, formatter: &mut Formatter) -> std::fmt::Result {
115 formatter.write_fmt(format_args!("{}:{}", self.line, self.column))
116 }
117}
118
119impl<I: Iterator<Item = char>> AddAssign<I> for Position {
120 #[inline]
121 fn add_assign(&mut self, rhs: I) {
122 for ch in rhs {
123 match ch {
124 '\n' => {
125 self.line += 1;
126 self.column = 1;
127 }
128
129 _ => {
130 self.column += 1;
131 }
132 }
133 }
134 }
135}
136
137unsafe impl ToSite for Position {
138 fn to_site(&self, code: &impl SourceCode) -> Option<Site> {
139 let span = code.lines().line_span(self.line);
140
141 Some(
142 self.column
143 .checked_sub(1)
144 .unwrap_or_default()
145 .checked_add(span.start)
146 .unwrap_or(span.end)
147 .min(span.end),
148 )
149 }
150
151 #[inline(always)]
152 fn is_valid_site(&self, _code: &impl SourceCode) -> bool {
153 true
154 }
155}
156
157impl Position {
158 /// A constructor of the Position object.
159 #[inline(always)]
160 pub fn new(line: Line, column: Column) -> Self {
161 Self { line, column }
162 }
163}