yatima_core/
position.rs

1use crate::{
2  ipld_error::IpldError,
3  parse::span::Span,
4};
5
6use sp_cid::Cid;
7use sp_ipld::Ipld;
8
9use sp_std::{
10  borrow::ToOwned,
11  convert::TryInto,
12  fmt,
13};
14
15use alloc::string::String;
16
17/// Source code position of an expression in a file
18#[derive(PartialEq, Clone, Copy, Debug)]
19pub struct Position {
20  pub input: Cid,
21  pub from_offset: u64,
22  pub from_line: u64,
23  pub from_column: u64,
24  pub upto_offset: u64,
25  pub upto_line: u64,
26  pub upto_column: u64,
27}
28
29/// Wrapper optional type for Position
30#[derive(PartialEq, Clone, Copy)]
31pub enum Pos {
32  None,
33  Some(Position),
34}
35impl fmt::Debug for Pos {
36  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "") }
37}
38
39impl Position {
40  /// Use the range information in a Position to pretty-print that range within
41  /// a string
42  pub fn range(self, input: String) -> String {
43    let mut res = String::new();
44    let gutter = format!("{}", self.upto_line).len();
45    let pad = format!("{: >gutter$}", self.from_line, gutter = gutter).len()
46      + 3
47      + self.from_column as usize;
48    res.push_str(&format!("{}▼\n", " ".to_owned().repeat(pad)));
49    for (line_number, line) in input.lines().enumerate() {
50      if ((line_number as u64 + 1) >= self.from_line)
51        && ((line_number as u64 + 1) <= self.upto_line)
52      {
53        res.push_str(&format!(
54          "{: >gutter$} | {}\n",
55          line_number + 1,
56          line,
57          gutter = gutter
58        ));
59      }
60    }
61    let pad = format!("{: >gutter$}", self.upto_line, gutter = gutter).len()
62      + 3
63      + self.upto_column as usize;
64    res.push_str(&format!("{}▲", " ".to_owned().repeat(pad)));
65    res
66  }
67
68  /// Converts a position into an IPLD object
69  pub fn to_ipld(self) -> Ipld {
70    Ipld::List(vec![
71      Ipld::Link(self.input),
72      Ipld::Integer(self.from_offset as i128),
73      Ipld::Integer(self.from_line as i128),
74      Ipld::Integer(self.from_column as i128),
75      Ipld::Integer(self.upto_offset as i128),
76      Ipld::Integer(self.upto_line as i128),
77      Ipld::Integer(self.upto_column as i128),
78    ])
79  }
80
81  /// Construct a position from the difference of two Spans
82  pub fn from_upto(input: Cid, from: Span, upto: Span) -> Self {
83    Self {
84      input,
85      from_offset: (from.location_offset() as u64),
86      from_line: from.location_line() as u64,
87      from_column: from.get_utf8_column() as u64,
88      upto_offset: (upto.location_offset() as u64),
89      upto_line: upto.location_line() as u64,
90      upto_column: upto.get_utf8_column() as u64,
91    }
92  }
93
94  /// Converts an IPLD object into a position
95  pub fn from_ipld(ipld: &Ipld) -> Result<Self, IpldError> {
96    match ipld {
97      Ipld::List(xs) => match xs.as_slice() {
98        #[rustfmt::skip]
99        [Ipld::Link(cid),
100         Ipld::Integer(from_offset),
101         Ipld::Integer(from_line),
102         Ipld::Integer(from_column),
103         Ipld::Integer(upto_offset),
104         Ipld::Integer(upto_line),
105         Ipld::Integer(upto_column),
106        ] => {
107          let from_offset: u64 =
108            (*from_offset).try_into().map_err(IpldError::U64)?;
109          let from_line: u64 =
110            (*from_line).try_into().map_err(IpldError::U64)?;
111          let from_column: u64 =
112            (*from_column).try_into().map_err(IpldError::U64)?;
113          let upto_offset: u64 =
114            (*upto_offset).try_into().map_err(IpldError::U64)?;
115          let upto_line: u64 =
116            (*upto_line).try_into().map_err(IpldError::U64)?;
117          let upto_column: u64 =
118            (*upto_column).try_into().map_err(IpldError::U64)?;
119          Ok(Position {
120            input: *cid,
121            from_offset,
122            from_line,
123            from_column,
124            upto_offset,
125            upto_line,
126            upto_column})
127        }
128        xs => Err(IpldError::Position(Ipld::List(xs.to_owned()))),
129      },
130      xs => Err(IpldError::Position(xs.to_owned())),
131    }
132  }
133}
134
135impl Pos {
136  /// Converts a pos into an IPLD object
137  pub fn to_ipld(self) -> Ipld {
138    match self {
139      Self::None => Ipld::Null,
140      Self::Some(x) => Position::to_ipld(x),
141    }
142  }
143
144  /// Converts an IPLD object into a pos
145  pub fn from_ipld(ipld: &Ipld) -> Result<Self, IpldError> {
146    match ipld {
147      Ipld::Null => Ok(Self::None),
148      xs => {
149        let pos = Position::from_ipld(&xs)?;
150        Ok(Self::Some(pos))
151      }
152    }
153  }
154
155  pub fn from_upto(input: Cid, from: Span, upto: Span) -> Self {
156    Pos::Some(Position::from_upto(input, from, upto))
157  }
158}
159
160impl fmt::Display for Position {
161  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162    write!(
163      f,
164      "{}@{}:{}-{}:{}",
165      self.input,
166      self.from_line,
167      self.from_column,
168      self.upto_line,
169      self.upto_column
170    )
171  }
172}
173
174#[cfg(test)]
175pub mod tests {
176  use super::*;
177  use quickcheck::{
178    Arbitrary,
179    Gen,
180  };
181
182  use crate::tests::arbitrary_cid;
183
184  impl Arbitrary for Position {
185    fn arbitrary(g: &mut Gen) -> Self {
186      Position {
187        input: arbitrary_cid(g),
188        from_offset: Arbitrary::arbitrary(g),
189        from_line: Arbitrary::arbitrary(g),
190        from_column: Arbitrary::arbitrary(g),
191        upto_offset: Arbitrary::arbitrary(g),
192        upto_line: Arbitrary::arbitrary(g),
193        upto_column: Arbitrary::arbitrary(g),
194      }
195    }
196  }
197
198  impl Arbitrary for Pos {
199    fn arbitrary(g: &mut Gen) -> Self {
200      let x: bool = Arbitrary::arbitrary(g);
201      if x { Self::None } else { Self::Some(Arbitrary::arbitrary(g)) }
202    }
203  }
204
205  #[quickcheck]
206  fn position_ipld(x: Position) -> bool {
207    match Position::from_ipld(&x.to_ipld()) {
208      Ok(y) => x == y,
209      _ => false,
210    }
211  }
212
213  #[quickcheck]
214  fn pos_ipld(x: Pos) -> bool {
215    match Pos::from_ipld(&x.to_ipld()) {
216      Ok(y) => x == y,
217      _ => false,
218    }
219  }
220}