use crate::error::{self, Token};
use colored::Colorize;
use std::{borrow::Cow, ffi::OsString, fmt::Write, fs};
use unicode_width::UnicodeWidthStr;
struct Number<'a> {
pos: (u128, &'a str),
size: (u128, &'a str),
}
struct Line<'a> {
filename: Cow<'a, str>,
line_num: usize,
line: &'a str,
}
pub fn parse_map(map_path: &OsString, device_name: &str) -> String {
let contents = fs::read_to_string(map_path.clone()).unwrap_or_else(|error| {
error::check_io_error(
error,
map_path.clone().into_string().unwrap_or_default(),
crate::error::FileType::MapFile,
)
});
parse_map_string(map_path, &contents, device_name)
}
fn create_error(contents: Number, line: &Line) -> String {
let Number {
pos: (pos, pos_string),
size: (size, size_string),
} = contents;
format!(
"{} {} error",
sector(pos, || report_error(
line,
line.line.find(pos_string).unwrap(),
pos_string,
error::POSITION_SECTOR_ERROR
)),
sector(size, || report_error(
line,
line.line.rfind(size_string).unwrap(),
size_string,
error::SIZE_SECTOR_ERROR
))
)
}
fn create_linear(contents: Number, device: &str, line: &Line) -> String {
let Number {
pos: (pos, pos_string),
size: (size, size_string),
} = contents;
let pos_sector = sector(pos, || {
report_error(
line,
line.line.find(pos_string).unwrap(),
pos_string,
error::POSITION_SECTOR_ERROR,
)
});
let size_sector = sector(size, || {
report_error(
line,
line.line.rfind(size_string).unwrap(),
size_string,
error::SIZE_SECTOR_ERROR,
)
});
format!("{pos_sector} {size_sector} linear {device} {pos_sector}")
}
pub fn parse_map_string(filename: &OsString, contents: &str, device_name: &str) -> String {
let mut output = String::new();
let mut file_line = contents
.lines()
.enumerate()
.map(|s| (s.0, s.1.trim()))
.filter(|s| !s.1.is_empty() && !s.1.contains('#'));
verify_status_line(&mut file_line, filename.to_string_lossy());
let mut prev_entry = 0;
#[allow(clippy::redundant_closure)]
for (line_number, line) in file_line {
let line = Line {
filename: filename.to_string_lossy(),
line_num: line_number,
line,
};
let mut map_line = line.line.split_ascii_whitespace();
let pos_string = map_line
.next()
.unwrap_or_else(|| {
report_error(&line, 0, line.line, error::NO_POSITION_ERROR);
})
.to_string();
let size_string = map_line
.next()
.unwrap_or_else(|| {
report_error(&line, 0, line.line, error::NO_SIZE_ERROR);
})
.to_string();
let status = map_line.next().unwrap_or_else(|| {
report_error(&line, 0, line.line, error::NO_STATUS_ERROR);
});
let status = status.parse::<char>().unwrap_or_else(|_| {
report_error(
&line,
line.line.rfind(status).unwrap(),
status,
error::UNKNOWN_MAP_STATUS_ERROR,
)
});
let pos_location = get_next(0, line.line, &pos_string);
let size_location = get_next(pos_location + pos_string.len(), line.line, &size_string);
let status_location = get_next(
size_location + size_string.len(),
line.line,
&status.to_string(),
);
let pos = convert_to_num(&pos_string, || {
report_error(
&line,
pos_location,
&pos_string,
&error::convert_error_string(Token::Pos),
)
});
let size = convert_to_num(&size_string, || {
report_error(
&line,
size_location,
&size_string,
&error::convert_error_string(Token::Size),
)
});
let number = Number {
pos: (pos, &pos_string),
size: (size, &size_string),
};
match status {
'+' => error::handle_string_write(writeln!(
output,
"{}",
create_linear(number, device_name, &line)
)),
'?' | '*' | '/' | '-' => {
error::handle_string_write(writeln!(output, "{}", create_error(number, &line)))
}
x => {
let x = &x.to_string();
report_error(&line, status_location, x, error::UNKNOWN_MAP_STATUS_ERROR);
}
}
if pos != prev_entry {
if prev_entry == 0 {
report_error(
&line,
line.line.find(&pos_string).unwrap(),
&pos_string,
error::START_NONZERO_ERROR,
);
} else {
report_error(
&line,
line.line.find(&pos_string).unwrap(),
&pos_string,
&error::CONTIGUOUS_ERROR
.replace("{pos}", &pos.to_string())
.replace("{size}", &prev_entry.to_string()),
);
}
} else {
prev_entry = pos + size;
}
}
output
}
fn sector(size: u128, error: impl FnOnce()) -> u128 {
if size % 512 != 0 {
error();
unreachable!()
} else {
size / 512
}
}
fn convert_to_num(num_string: &str, error: impl FnOnce()) -> u128 {
let mut num_string = num_string;
let radix = if num_string.starts_with("0x") {
num_string = num_string.trim_start_matches("0x");
16
} else if num_string.starts_with('0') {
8
} else {
10
};
u128::from_str_radix(num_string, radix).unwrap_or_else(|_| {
error();
unreachable!()
})
}
fn report_error(line: &Line, parse_start: usize, token: &str, message: &str) -> ! {
let Line {
filename,
line_num,
line,
} = line;
let seperator = "|".blue().bold();
let padding = " ".repeat(line_num.to_string().len());
eprintln!("{padding} {} {filename}", "-->".blue().bold());
eprintln!(" {padding} {seperator}");
eprintln!(" {} {seperator} {line}", line_num.to_string().blue().bold());
eprintln!(
" {padding} {seperator} {}{}",
" ".repeat(parse_start),
"^".repeat(UnicodeWidthStr::width(token)).red().bold()
);
error::print_error(message);
error::parse_error(false)
}
fn verify_status_line<'a, T>(lines: &mut T, filename: Cow<'a, str>)
where
T: Iterator<Item = (usize, &'a str)>,
{
let (line_number, line) = lines.next().unwrap_or_else(|| {
error::print_error(error::EMPTY_MAP_ERROR);
error::parse_error(false)
});
let mut contents = line.split_ascii_whitespace();
let line_content = Line {
filename,
line_num: line_number,
line,
};
let current_pos = contents
.next()
.unwrap_or_else(|| report_error(&line_content, 0, line, error::NO_CURRENT_POSITION_ERROR));
let current_pos_location = get_next(0, line, current_pos);
convert_to_num(current_pos, || {
report_error(
&line_content,
current_pos_location,
current_pos,
&error::convert_error_string(Token::CurrentPos),
)
});
let current_status = contents
.next()
.unwrap_or_else(|| report_error(&line_content, 0, line, error::NO_CURRENT_STATUS_ERROR));
let current_status_location = get_next(
current_pos_location + current_pos.len(),
line,
current_status,
);
let current_status = current_status.parse::<char>().unwrap_or_else(|_| {
report_error(
&line_content,
current_status_location,
current_status,
error::UNKNOWN_CURRENT_STATUS_ERROR,
)
});
let current_status_location = get_next(
current_pos_location + current_pos.len(),
line,
¤t_status.to_string(),
);
match current_status {
'?' | '*' | '/' | '-' | 'F' | 'G' | '+' => (),
x => report_error(
&line_content,
current_status_location,
&x.to_string(),
error::UNKNOWN_CURRENT_STATUS_ERROR,
),
}
let current_pass = contents.next();
match current_pass {
Some(x) => {
let x_location = get_next(current_status_location + 1, line, x);
let current_status = x.parse::<u8>().unwrap_or_else(|_| {
report_error(
&line_content,
x_location,
x,
error::UNKNOWN_CURRRENT_PHASE_ERROR,
)
});
if current_status < 1 {
report_error(
&line_content,
x_location,
x,
error::CURRENT_PHASE_LESS_THAN_ONE_ERROR,
);
}
}
None => (),
};
}
fn get_next(prev: usize, line: &str, next: &str) -> usize {
prev + line[prev..].find(next).unwrap()
}