use std::fs::File;
use std::io::*;
use crate::compiling::{Metadata, Token};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Position {
Pos(usize, usize),
EOF
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PositionInfo {
pub path: Option<String>,
pub position: Position,
pub start: usize,
pub len: usize,
pub data: Option<String>
}
impl PositionInfo {
pub fn new(meta: &impl Metadata, position: Position, start: usize, len: usize) -> Self {
let info = PositionInfo {
position,
path: meta.get_path(),
start,
len,
data: None
};
info.updated_pos(meta)
}
pub fn at_eof(meta: &impl Metadata) -> Self {
let info = PositionInfo {
path: meta.get_path(),
position: Position::EOF,
start: 0,
len: 0,
data: None
};
info.updated_pos(meta)
}
pub fn at_pos(path: Option<String>, (row, col): (usize, usize), start: usize, len: usize) -> Self {
PositionInfo {
path,
position: Position::Pos(row, col),
start,
len,
data: None
}
}
fn updated_pos(mut self, meta: &impl Metadata) -> Self {
let (row, col) = self.get_pos_by_file_or_code(meta.get_code());
self.position = Position::Pos(row, col);
self
}
pub fn get_path(&self) -> String {
self.path.clone().unwrap_or_else(|| "[unknown]".to_string())
}
pub fn from_metadata(meta: &impl Metadata) -> Self {
Self::from_token(meta, meta.get_current_token())
}
pub fn from_token(meta: &impl Metadata, token_opt: Option<Token>) -> Self {
match token_opt {
Some(token) => PositionInfo::at_pos(meta.get_path(), token.pos, token.start, token.word.chars().count()),
None => PositionInfo::at_eof(meta)
}
}
pub fn from_between_tokens(meta: &impl Metadata, begin: Option<Token>, end: Option<Token>) -> Self {
if let Some(begin) = begin {
let (row, col) = begin.pos;
let end_pos = end.map_or(usize::max_value(), |tok| tok.start);
let len = end_pos - begin.start;
PositionInfo::at_pos(meta.get_path(), (row, col), begin.start, len)
} else {
PositionInfo::from_metadata(meta)
}
}
pub fn from_between_positions(meta: &impl Metadata, begin: PositionInfo, end: PositionInfo) -> Self {
let start_index = begin.start;
let end_index = end.start + end.len;
let len = end_index - start_index;
if let Position::Pos(row, col) = begin.position {
PositionInfo::at_pos(meta.get_path(), (row, col), start_index, len)
} else {
PositionInfo::at_eof(meta)
}
}
pub fn data<T: AsRef<str>>(mut self, data: T) -> Self {
self.data = Some(data.as_ref().to_string());
self
}
pub fn get_pos_by_file_or_code(&self, code: Option<&String>) -> (usize, usize) {
match self.position {
Position::Pos(row, col) => (row, col),
Position::EOF => {
if let Some(code) = code {
self.get_pos_by_code(code)
}
else if let Some(path) = &self.path {
match self.get_pos_by_file(path) {
Ok((row, col)) => (row, col),
Err(_) => (0, 0)
}
}
else {
(0, 0)
}
}
}
}
pub fn get_pos_by_file(&self, path: impl AsRef<str>) -> std::io::Result<(usize, usize)> {
let mut code = String::new();
let mut file = File::open(path.as_ref())?;
file.read_to_string(&mut code)?;
Ok(self.get_pos_by_code(&code))
}
pub fn get_pos_by_code(&self, code: impl AsRef<str>) -> (usize, usize) {
let code = code.as_ref();
match self.position {
Position::Pos(row, col) => {
(row, col)
}
Position::EOF => {
if let Some((row, line)) = code.lines().enumerate().last() {
(row + 1, line.len() + 1)
} else {
(0, 0)
}
}
}
}
}
#[cfg(test)]
mod test {
use crate::prelude::DefaultMetadata;
use super::*;
#[test]
fn test_position_info() {
let pos = PositionInfo::at_pos(Some("test".to_string()), (1, 1), 0, 1);
assert_eq!(pos.get_path(), "test");
assert_eq!(pos.get_pos_by_code("test"), (1, 1));
}
#[test]
fn test_position_info_between_tokens() {
let begin = Token { word: "begin".to_string(), pos: (1, 1), start: 0 };
let to = Token { word: "to".to_string(), pos: (1, 7), start: 6 };
let end = Token { word: "end".to_string(), pos: (1, 10), start: 9 };
let mut meta = DefaultMetadata::new(vec![begin.clone(), to.clone(), end.clone()], None, Some("begin to end".to_string()));
let pos = PositionInfo::from_between_tokens(&mut meta, Some(begin.clone()), Some(end.clone()));
assert_eq!(pos.len, end.start - begin.start);
}
#[test]
fn test_position_info_between_positions() {
let begin = PositionInfo::at_pos(Some("test".to_string()), (1, 1), 0, 5);
let end = PositionInfo::at_pos(Some("test".to_string()), (1, 10), 9, 3);
let mut meta = DefaultMetadata::new(vec![], None, Some("begin to end".to_string()));
let pos = PositionInfo::from_between_positions(&mut meta, begin.clone(), end.clone());
assert_eq!(pos.start, 0);
assert_eq!(pos.len, 12);
assert_eq!(pos.get_pos_by_code("begin to end"), (1, 1));
}
}