use std::fs;
use std::io::{BufReader, Read};
use regex::Regex;
use crate::tests::{TestCase, TestSuite, TestCaseSettings, TestSuiteSettings};
use crate::utils::TextFormat;
mod error;
mod token;
mod string_util;
mod char_util;
mod lexer;
mod finite_state_machine;
mod options;
use self::lexer::Lexer;
use self::token::{Token, TokenType};
use self::error::Error;
use self::finite_state_machine::FiniteStateMachine;
use self::options::{set_test_option, set_group_option};
pub fn parse_file(file: &mut fs::File) -> Result<Vec<TestSuite>, Error> {
parse_file_with_default_settings(file, Default::default())
}
pub fn parse_file_with_default_settings(file: &mut fs::File, default_test_settings: TestCaseSettings) -> Result<Vec<TestSuite>, Error> {
let mut reader = BufReader::new(file);
let mut content = String::new();
if let Err(_) = reader.read_to_string(&mut content) {
return Err(Error::ReadFileError);
}
let mut lexer = Lexer::new(content);
let tokens = lexer.get_tokens();
analyse_tokens(tokens, default_test_settings)
}
fn analyse_tokens(tokens: Vec<Token>, default_test_settings: TestCaseSettings) -> Result<Vec<TestSuite>, Error> {
let mut lines: Vec<Vec<Token>> = Vec::new();
let mut line: Vec<Token> = Vec::new();
for token in tokens {
if token.token_type == TokenType::Illegal {
return Err(Error::IllegalToken(token.value, token.line, token.column));
}
if token.token_type == TokenType::Newline {
if line.len() > 0 {
lines.push(line);
line = Vec::new();
}
continue;
}
line.push(token);
}
let mut test_suites: Vec<TestSuite> = Vec::new();
let group_state_machine = FiniteStateMachine::new(1, vec!(4), |state, token| -> u32 {
match state {
1 if token.token_type == TokenType::LeftGroupParenthesis => 2,
2 if token.token_type == TokenType::Identifier => 3,
2 if token.token_type == TokenType::ContentSeparator => 5,
3 if token.token_type == TokenType::RightGroupParenthesis => 4,
3 if token.token_type == TokenType::ContentSeparator => 5,
5 if token.token_type == TokenType::Identifier => 6,
6 if token.token_type == TokenType::OptionSeparator => 7,
7 if token.token_type == TokenType::Identifier => 3,
_ => 0
}
});
let test_state_machine = FiniteStateMachine::new(1, vec!(9), |state, token| -> u32 {
match state {
1 if token.token_type == TokenType::LeftTestParenthesis => 2,
1 if token.token_type == TokenType::FormatSpecifier => 5,
1 if token.token_type == TokenType::Content => 5,
2 if token.token_type == TokenType::Identifier => 3,
2 if token.token_type == TokenType::ContentSeparator => 10,
3 if token.token_type == TokenType::RightTestParenthesis => 4,
3 if token.token_type == TokenType::ContentSeparator => 10,
4 if token.token_type == TokenType::FormatSpecifier => 5,
4 if token.token_type == TokenType::Content => 6,
5 if token.token_type == TokenType::Content => 6,
6 if token.token_type == TokenType::DirectionSeparator => 7,
7 if token.token_type == TokenType::FormatSpecifier => 8,
7 if token.token_type == TokenType::Content => 9,
8 if token.token_type == TokenType::Content => 9,
10 if token.token_type == TokenType::Identifier => 11,
11 if token.token_type == TokenType::OptionSeparator => 12,
11 if token.token_type == TokenType::ContentSeparator => 10,
11 if token.token_type == TokenType::RightTestParenthesis => 4,
12 if token.token_type == TokenType::Identifier => 3,
_ => 0
}
});
for line in lines {
let first_token: &Token = line.first().unwrap();
if first_token.token_type == TokenType::LeftGroupParenthesis {
match analyse_test_group(&line, &group_state_machine, default_test_settings.clone()) {
Ok(test_suite) => test_suites.push(test_suite),
Err(err) => return Err(err)
};
continue;
}
if first_token.token_type == TokenType::LeftTestParenthesis || first_token.token_type == TokenType::FormatSpecifier || first_token.token_type == TokenType::Content {
match analyse_test(&line, &test_state_machine) {
Ok(test) => {
if test_suites.is_empty() {
test_suites.push(TestSuite::new(String::new()));
}
let test_suite: &mut TestSuite = test_suites.last_mut().unwrap();
test_suite.push(test);
}
Err(err) => return Err(err)
};
continue;
}
return Err(Error::InvalidLineStart(first_token.line, first_token.column));
}
Ok(test_suites)
}
fn analyse_test_group(tokens: &Vec<Token>, state_machine: &FiniteStateMachine, default_test_settings: TestCaseSettings) -> Result<TestSuite, Error> {
let result = state_machine.run(&tokens);
if let Err((state, token)) = result {
return match state {
2 => Err(Error::MissingGroupIdentifier(token.line, token.column)),
3 => Err(Error::MissingClosingParenthesis("]".to_string(), token.line, token.column)),
5 => Err(Error::MissingOptionIdentifier(token.line, token.column)),
6 => Err(Error::MissingOptionSeparator(token.line, token.column)),
7 => Err(Error::MissingOptionValue(token.line, token.column)),
_ => Err(Error::UnknownError(token.line, token.column))
};
}
let mut index = 1;
let name = if tokens[1].token_type == TokenType::Identifier {
index += 1;
tokens[1].value.clone()
} else {
String::new()
};
let mut settings = TestSuiteSettings::default();
let mut test_settings = default_test_settings;
analyse_group_options(&tokens[index..], &mut settings, &mut test_settings)?;
let test_suite = TestSuite::new_with_settings(name, settings, test_settings);
Ok(test_suite)
}
fn analyse_test(tokens: &Vec<Token>, state_machine: &FiniteStateMachine) -> Result<TestCase, Error> {
let result = state_machine.run(&tokens);
if let Err((state, token)) = result {
return match state {
2 => Err(Error::MissingTestIdentifier(token.line, token.column)),
3 => Err(Error::MissingClosingParenthesis(")".to_string(), token.line, token.column)),
4 | 5 => Err(Error::MissingContent("input".to_string(), token.line, token.column)),
6 => Err(Error::MissingDirectionSeparator(token.line, token.column)),
7 | 8 => Err(Error::MissingContent("output".to_string(), token.line, token.column)),
10 => Err(Error::MissingOptionIdentifier(token.line, token.column)),
11 => Err(Error::MissingOptionSeparator(token.line, token.column)),
12 => Err(Error::MissingOptionValue(token.line, token.column)),
_ => Err(Error::UnknownError(token.line, token.column))
};
}
let mut name = String::new();
let input: String;
let output: String;
let mut settings = TestCaseSettings::default();
let mut input_format: Option<TextFormat> = None;
let mut output_format: Option<TextFormat> = None;
let mut index = 0;
if tokens[index].token_type == TokenType::LeftTestParenthesis {
if tokens[index + 1].token_type == TokenType::Identifier {
name = tokens[1].value.clone();
index += 1;
}
index += 1;
index += analyse_test_options(&tokens[index..], &mut settings)?;
}
if tokens[index].token_type == TokenType::FormatSpecifier {
input_format = Some(get_text_format(&tokens[index])?);
index += 1;
}
input = tokens[index].value.clone();
index += 2;
if tokens[index].token_type == TokenType::FormatSpecifier {
output_format = Some(get_text_format(&tokens[index])?);
index += 1;
}
output = tokens[index].value.clone();
if let Err(_) = Regex::new(&output) {
return Err(Error::InvalidOutputContent(output, tokens[index].line, tokens[index].column));
}
let mut test = TestCase::new(name, input, output);
test.settings = settings;
if let Some(format) = input_format {
test.input_format = format;
}
if let Some(format) = output_format {
test.output_format = format;
}
Ok(test)
}
fn analyse_test_options(tokens: &[Token], settings: &mut TestCaseSettings) -> Result<usize, Error> {
let mut index = 0;
while tokens[index].token_type == TokenType::ContentSeparator {
let mut option_length = 1;
while tokens[index + option_length].token_type != TokenType::ContentSeparator && tokens[index + option_length].token_type != TokenType::RightTestParenthesis {
option_length += 1;
}
let offset = set_test_option(&tokens[index + 1 .. index + option_length], settings)?;
index += 2 + offset;
}
Ok(index + 1)
}
fn analyse_group_options(tokens: &[Token], settings: &mut TestSuiteSettings, test_settings: &mut TestCaseSettings) -> Result<usize, Error> {
let mut index = 0;
while tokens[index].token_type == TokenType::ContentSeparator {
let mut option_length = 1;
while tokens[index + option_length].token_type != TokenType::ContentSeparator && tokens[index + option_length].token_type != TokenType::RightGroupParenthesis {
option_length += 1;
}
let offset = match set_test_option(&tokens[index + 1 .. index + option_length], test_settings) {
Ok(offset) => offset,
Err(err) => {
match err {
Error::UnknownTestOption(_, _, _) => set_group_option(&tokens[index + 1 .. index + option_length], settings)?,
_ => return Err(err)
}
},
};
index += 2 + offset;
}
Ok(index + 1)
}
fn get_text_format(token: &Token) -> Result<TextFormat, Error> {
match token.value.as_str() {
"b" => Ok(TextFormat::Binary),
"o" => Ok(TextFormat::Octal),
"d" => Ok(TextFormat::Decimal),
"h" => Ok(TextFormat::Hex),
_ => Err(Error::UnknownError(token.line, token.column))
}
}