use crate::{
Action, Config, Coordinate, Error, Frame, Marker, Range, Result, Value, WhileyTestFile,
};
pub struct Parser<'a> {
index: usize,
lines: Vec<&'a str>,
}
impl<'a> Parser<'a> {
pub fn new(input: &'a str) -> Self {
let lines = input.lines().collect();
Parser { index: 0, lines }
}
pub fn parse(&mut self) -> Result<WhileyTestFile<'a>> {
let config = self.parse_config()?;
let frames = self.parse_frames()?;
Ok(WhileyTestFile { config, frames })
}
pub fn eof(&self) -> bool {
self.index >= self.lines.len()
}
pub fn peek(&self) -> &'a str {
assert!(!self.eof());
self.lines[self.index]
}
pub fn next(&mut self) -> &'a str {
assert!(!self.eof());
let line = self.lines[self.index];
self.index += 1;
line
}
fn parse_config(&mut self) -> Result<Config<'a>> {
let mut config = Config::new();
while !self.eof() && !self.peek().starts_with("===") {
let line = self.next().trim();
if !line.is_empty() {
let (k, v) = parse_kvp_line(line)?;
config.insert(k, v);
}
}
Ok(config)
}
fn parse_frames(&mut self) -> Result<Vec<Frame<'a>>> {
let mut frames = Vec::new();
while !self.eof() && self.peek().starts_with("===") {
frames.push(self.parse_frame()?);
}
Ok(frames)
}
fn parse_frame(&mut self) -> Result<Frame<'a>> {
let _l = self.next(); let mut actions = Vec::new();
while !self.eof() && is_action_prefix(self.peek()) {
actions.push(self.parse_action()?);
}
let mut markers = Vec::new();
if !self.eof() && is_marker_prefix(self.peek()) {
self.next(); while !self.eof() && !is_prefix(self.peek()) {
markers.push(self.parse_marker()?);
}
}
Ok(Frame { actions, markers })
}
fn parse_action(&mut self) -> Result<Action<'a>> {
let line = self.next().trim();
let split: Vec<&str> = line.split(' ').collect();
let (filename, range) = match split.len() {
2 => (split[1], None),
3 => (split[1], Some(parse_range(split[2])?)),
_ => {
return Err(Error::InvalidAction);
}
};
let mut lines = Vec::new();
while !self.eof() && !is_prefix(self.peek()) {
lines.push(self.next());
}
let act = if split[0] == ">>>" {
match range {
Some(r) => Action::INSERT(filename, r, lines),
None => Action::CREATE(filename, lines),
}
} else {
Action::REMOVE(filename)
};
Ok(act)
}
fn parse_marker(&mut self) -> Result<Marker<'a>> {
let line = self.next().trim();
let split: Vec<&str> = line.split(' ').collect();
if split.len() == 3 {
let errno = parse_error_code(split[0])?;
let filename = split[1];
let location = parse_coordinate(split[2])?;
Ok(Marker {
errno,
filename,
location,
})
} else {
Err(Error::InvalidMarker)
}
}
}
fn parse_kvp_line(line: &str) -> Result<(&str, Value)> {
let bits: Vec<&str> = line.split('=').collect();
if bits.len() != 2 {
Err(Error::InvalidConfigOption)
} else {
let key = bits[0].trim();
let value = parse_value(bits[1].trim())?;
Ok((key, value))
}
}
fn parse_value(input: &str) -> Result<Value> {
let c = input.chars().next();
match c {
Some('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-') => {
parse_int_value(input)
}
Some('"') => parse_string_value(input),
_ => parse_bool_value(input),
}
}
fn parse_int_value(input: &str) -> Result<Value> {
match input.parse::<i64>() {
Ok(i) => Ok(Value::Int(i)),
_ => Err(Error::InvalidIntValue),
}
}
fn parse_string_value(input: &str) -> Result<Value> {
let n = input.len() - 1;
if n > 0 && &input[n..] == "\"" {
let content = &input[1..n];
if !content.contains('"') {
return Ok(Value::String(content));
}
}
Err(Error::InvalidStringValue)
}
fn parse_bool_value(input: &str) -> Result<Value> {
if input == "true" {
Ok(Value::Bool(true))
} else if input == "false" {
Ok(Value::Bool(false))
} else {
Err(Error::InvalidConfigValue)
}
}
fn parse_coordinate(input: &str) -> Result<Coordinate> {
let split: Vec<&str> = input.split(',').collect();
if split.len() == 2 {
let line = parse_coordinate_index(split[0])?;
let range = parse_range(split[1])?;
Ok(Coordinate(line, range))
} else {
Err(Error::InvalidCoordinate)
}
}
fn parse_coordinate_index(input: &str) -> Result<usize> {
match input.parse::<usize>() {
Ok(i) => Ok(i),
_ => Err(Error::InvalidCoordinate),
}
}
fn parse_range(input: &str) -> Result<Range> {
let split: Vec<&str> = input.split(':').collect();
match split.len() {
1 => {
let i = parse_range_index(split[0])?;
Ok(Range(i, i))
}
2 => {
let i = parse_range_index(split[0])?;
let j = parse_range_index(split[1])?;
Ok(Range(i, j))
}
_ => Err(Error::InvalidRange),
}
}
fn parse_range_index(input: &str) -> Result<usize> {
match input.parse::<usize>() {
Ok(i) => Ok(i),
_ => Err(Error::InvalidRange),
}
}
fn parse_error_code(mut input: &str) -> Result<u16> {
input = &input[1..];
match input.parse::<u16>() {
Ok(i) => Ok(i),
_ => Err(Error::InvalidErrorCode),
}
}
fn is_prefix(line: &str) -> bool {
is_frame_prefix(line) || is_action_prefix(line) || is_marker_prefix(line)
}
fn is_frame_prefix(line: &str) -> bool {
line.starts_with("===")
}
fn is_action_prefix(line: &str) -> bool {
line.starts_with(">>>") || line.starts_with("<<<")
}
fn is_marker_prefix(line: &str) -> bool {
line.starts_with("---")
}