pub mod ast;
pub mod concrete_test;
use ast::Statement;
use pest::iterators::{Pair, Pairs};
#[allow(unused)]
use pest::Parser;
use crate::result::{diagnostic::Location, CompilerResult};
use ast::{SensorBound, SensorBoundNumeric, SensorConstraint};
#[derive(Parser)]
#[grammar = "./test_descriptor/tdf.pest"]
struct TDFParser;
pub fn parse_tdf(file_id: usize, text: String) -> CompilerResult<ast::Test> {
let mut res = CompilerResult::new("Parse Test Descriptor Format");
match TDFParser::parse(Rule::TEST, &text) {
Ok(_parse_tree) => {
res.set(test_from_tree(file_id, _parse_tree.peek().unwrap()));
}
Err(pest_error) => {
res.error(Box::new(pest_error));
}
}
res
}
fn test_from_tree(file_id: usize, node: Pair<Rule>) -> CompilerResult<ast::Test> {
let mut res = CompilerResult::new("Generating ast");
let mut aborts = Vec::new();
let mut test_body = Option::None;
for node in node.into_inner() {
match node.as_rule() {
Rule::ABORT_SEQUENCE => {
if let Some(abort) = res.require(abort_sequence_from_tree(file_id, node)) {
aborts.push(abort);
}
}
Rule::TEST_BODY => {
if let Some(test_body_temp) = res.require(test_body_from_tree(file_id, node)) {
test_body = Some(test_body_temp);
}
}
Rule::EOI => {}
_ => unreachable!(),
}
}
if aborts.len() > 15 {
res.error("More than 15 aborts defined in test");
}
if let Some(test_body) = test_body {
res.set_value(ast::Test { test_body, aborts });
}
res
}
fn abort_sequence_from_tree(file_id: usize, node: Pair<Rule>) -> CompilerResult<ast::Abort> {
let mut res = CompilerResult::new("Create abort from statements");
let mut pairs = node.into_inner();
let abort_header = pairs.next().unwrap();
let abort_name = abort_header.as_str();
if let Some(abort_statements) = res.require(statements_from_tree(file_id, pairs)) {
res.require(no_user_aborts(&abort_statements));
res.set_value(ast::Abort {
name: String::from(abort_name),
statements: abort_statements,
});
}
res
}
fn no_user_aborts(abort_statements: &[Statement]) -> CompilerResult<()> {
let mut res = CompilerResult::status_only("Check abort only references HARD_ABORT.");
for statement in abort_statements {
if let ast::Statement::Sensor(sensor_statement) = statement {
if sensor_statement.abort != ast::Abort::HARD_ABORT {
res.error((
sensor_statement.metadata,
"Can't reference user defined abort within abort sequence!",
));
}
}
}
res
}
fn timing_from_tree(file_id: usize, timing_iter: Pair<Rule>) -> CompilerResult<u128> {
let mut res = CompilerResult::new("Create timestamp");
let metadata = Location::from_pest(file_id, timing_iter.as_span());
let mut timing_iter = timing_iter.into_inner();
let mut timing = ast::Timing {
minutes: None,
seconds: None,
milliseconds: None,
};
let mut found_units = ["", "", ""];
for i in 0..3 {
if let Some(number) = timing_iter.next() {
if let Some(unit_opt) = timing_iter.next() {
let value = Some(number.as_str().parse().unwrap());
let unit = unit_opt.as_str();
if found_units.iter().position(|&u| u == unit).is_some() {
res.error((metadata, "Illegal timestamp. No duplicate units allowed."));
return res;
}
found_units[i] = unit;
match unit {
"min" => timing.minutes = value,
"s" => timing.seconds = value,
"ms" => timing.milliseconds = value,
_ => {
res.error((metadata, "Unrecognized time unit"));
return res;
}
}
} else {
res.error((metadata, "Failed to parse timestamp"));
return res;
}
} else {
break;
}
}
if timing.can_be_simplified() {
res.warning((metadata, "Timestamp can be simplified"));
}
res.with_value(timing.to_ms())
}
fn test_body_from_tree(file_id: usize, node: Pair<Rule>) -> CompilerResult<ast::TestBody> {
let mut res = CompilerResult::new("Create test body from statements");
let pairs = node.into_inner();
if let Some(statements) = res.require(statements_from_tree(file_id, pairs)) {
res.set_value(ast::TestBody { statements });
}
res
}
fn statements_from_tree(
file_id: usize,
iterator: Pairs<Rule>,
) -> CompilerResult<Vec<ast::Statement>> {
let mut res = CompilerResult::new("Parse test statements into vector of statements");
res.set_value(Vec::new());
res.collect(iterator.map(|node| statement_from_tree(file_id, node)));
res
}
fn statement_from_tree(file_id: usize, node: Pair<Rule>) -> CompilerResult<ast::Statement> {
let mut res = CompilerResult::new("Parse test statement");
let mut node_iter = node.into_inner();
let statement = node_iter.next().unwrap();
match statement.as_rule() {
Rule::SECTION_STATEMENT => {
if let Some(statement) = res.require(section_statement_from_tree(file_id, statement)) {
res.set_value(ast::Statement::Section(statement));
}
}
Rule::SENSOR_STATEMENT => {
if let Some(statement) = res.require(sensor_statement_from_tree(file_id, statement)) {
res.set_value(ast::Statement::Sensor(statement));
}
}
Rule::RELAY_STATEMENT => {
if let Some(statement) = res.require(relay_statement_from_tree(file_id, statement)) {
res.set_value(ast::Statement::Relay(statement));
}
}
_ => unreachable!(),
}
res
}
fn section_statement_from_tree(
file_id: usize,
section_statement: Pair<Rule>,
) -> CompilerResult<ast::SectionStatement> {
let mut res = CompilerResult::new("Parse section statement");
let metadata = Location::from_pest(file_id, section_statement.as_span());
let mut section_statement_pairs = section_statement.into_inner();
let name = section_statement_pairs.next().unwrap();
let timing = section_statement_pairs.next().unwrap();
let time = res.require(section_timing_from_statement(file_id, timing));
let statements = res.require(statements_from_tree(file_id, section_statement_pairs));
if !(time.is_some() && statements.is_some()) {
res.error((metadata, "Something went wrong! (TODO)"));
return res;
}
let ast_section_statement = ast::SectionStatement {
name: name.as_str().into(),
time: time.unwrap(),
statements: statements.unwrap(),
metadata,
};
res.require(ast_section_statement.check_statements_within_bounds());
res.with_value(ast_section_statement)
}
fn section_timing_from_statement(
file_id: usize,
section_timing: Pair<Rule>,
) -> CompilerResult<ast::SectionTime> {
let mut res = CompilerResult::new("Parse section timing");
let metadata = Location::from_pest(file_id, section_timing.as_span());
let mut timing_iter = section_timing.into_inner();
let start_opt;
let duration_opt;
if let Some(time) = res.require(timing_from_tree(file_id, timing_iter.next().unwrap())) {
start_opt = time;
} else {
res.error((metadata, "Failed to parse section start time"));
return res;
}
if let Some(time) = res.require(timing_from_tree(file_id, timing_iter.next().unwrap())) {
duration_opt = time;
} else {
res.error((metadata, "Failed to parse section duration time"));
return res;
}
let start = start_opt;
let duration = duration_opt;
res.with_value(ast::SectionTime { start, duration })
}
fn sensor_statement_from_tree(
file_id: usize,
sensor_statement: Pair<Rule>,
) -> CompilerResult<ast::SensorStatement> {
let mut res = CompilerResult::new("Parse sensor statement");
let metadata = Location::from_pest(file_id, sensor_statement.as_span());
let mut sensor_statement_pairs = sensor_statement.into_inner();
let timing = sensor_statement_pairs.next().unwrap();
let constraint_list = sensor_statement_pairs.next().unwrap();
let abort_id = sensor_statement_pairs.next().unwrap();
let abort_name = res
.require(abort_name_from_statement(file_id, abort_id))
.unwrap();
let relative_time = res.require(sensor_timing_from_statement(file_id, timing));
let sensor_constraints = res.require(sensor_constraint_list_from_statement(
file_id,
constraint_list,
&abort_name,
));
if !(relative_time.is_some() && sensor_constraints.is_some()) {
return res;
}
let ast_sensor_statement = ast::SensorStatement {
time: relative_time.unwrap(),
constraints: sensor_constraints.unwrap(),
abort: abort_name,
metadata,
};
res.with_value(ast_sensor_statement)
}
fn abort_name_from_statement(_file_id: usize, abort_name: Pair<Rule>) -> CompilerResult<String> {
let res = CompilerResult::new("Parse abort name");
res.with_value(String::from(abort_name.as_str()))
}
fn sensor_timing_from_statement(
file_id: usize,
sensor_timing: Pair<Rule>,
) -> CompilerResult<ast::SensorTime> {
let mut res = CompilerResult::new("Parse sensor timing");
let mut timing_iter = sensor_timing.into_inner();
let mut first = None;
let mut second = None;
if let Some(time) = res.require(timing_from_tree(file_id, timing_iter.next().unwrap())) {
first = Some(time);
}
if let Some(end_iter) = timing_iter.next() {
if let Some(time) = res.require(timing_from_tree(file_id, end_iter)) {
second = Some(time);
}
}
if let Some(first) = first {
if let Some(second) = second {
res.set_value(ast::SensorTime::Interval {
start: first,
end: second,
});
} else {
res.set_value(ast::SensorTime::Instant { time: first });
}
}
res
}
fn sensor_constraint_list_from_statement(
file_id: usize,
sensor_constraint_list: Pair<Rule>,
abort_name: &str,
) -> CompilerResult<Vec<ast::SensorConstraint>> {
let mut res = CompilerResult::new("Parse sensor constraint list");
let constraint_list_iter = sensor_constraint_list.into_inner();
res.set_value(Vec::new());
res.collect(
constraint_list_iter
.map(|child| sensor_constraint_from_statement(file_id, child, abort_name)),
);
res
}
fn sensor_constraint_from_statement(
file_id: usize,
sensor_constraint: Pair<Rule>,
abort_name: &str,
) -> CompilerResult<ast::SensorConstraint> {
let mut res = CompilerResult::new("Parse sensor constraint");
let metadata = Location::from_pest(file_id, sensor_constraint.as_span());
let mut constraint_iter = sensor_constraint.into_inner();
let sensor_constraint_type = constraint_iter.next().unwrap();
match sensor_constraint_type.as_rule() {
Rule::SENSOR_CONSTRAINT_NUMERIC => {
let mut inner_constraint_iter = sensor_constraint_type.into_inner();
let left_bound = inner_constraint_iter.next().unwrap();
let sensor_id = inner_constraint_iter.next().unwrap().as_str();
let right_bound = inner_constraint_iter.next().unwrap();
let sensor_bound = check!(
res,
numeric_bound_from_constraint(file_id, left_bound, right_bound)
);
res.set_value(SensorConstraint {
id: String::from(sensor_id),
sensor_bound: SensorBound::Numeric(sensor_bound),
abort: abort_name.to_string(),
metadata,
});
}
Rule::SENSOR_CONSTRAINT_BOOLEAN => {
let mut inner_constraint_iter = sensor_constraint_type.into_inner();
let sensor_id = inner_constraint_iter.next().unwrap().as_str();
let sensor_constraint_bool = inner_constraint_iter.next().unwrap().as_str();
let bool_value = match sensor_constraint_bool {
"true" => true,
"false" => false,
_ => unreachable!(),
};
res.set_value(SensorConstraint {
id: String::from(sensor_id),
sensor_bound: SensorBound::Boolean(bool_value),
abort: abort_name.to_string(),
metadata,
});
}
_ => unreachable!(),
}
res
}
fn numeric_bound_from_constraint(
file_id: usize,
left_bound: Pair<Rule>,
right_bound: Pair<Rule>,
) -> CompilerResult<SensorBoundNumeric> {
let mut res = CompilerResult::new("Parse sensor bound");
let left_metadata = Location::from_pest(file_id, left_bound.as_span());
let right_metadata = Location::from_pest(file_id, right_bound.as_span());
let mut left_iter = left_bound.into_inner();
let mut right_iter = right_bound.into_inner();
let left_value_str = left_iter.next().unwrap().as_str();
let right_value_str = right_iter.next().unwrap().as_str();
let left_value = check!(
res,
left_value_str.parse::<f64>(),
(left_metadata, "Invalid sensor bound provided!")
);
let right_value = check!(
res,
right_value_str.parse::<f64>(),
(right_metadata, "Invalid sensor bound provided!")
);
let left_unit = left_iter.next().unwrap().as_str();
let right_unit = right_iter.next().unwrap().as_str();
if left_unit != right_unit {
res.error((left_metadata, "See next message"));
res.error((
right_metadata,
"Type mismatch for left and right sensor bound",
));
}
res.with_value(SensorBoundNumeric {
left: left_value,
right: right_value,
unit: String::from(left_unit),
})
}
fn relay_statement_from_tree(
file_id: usize,
relay_statement: Pair<Rule>,
) -> CompilerResult<ast::RelayStatement> {
let mut res = CompilerResult::new("Parse relay statement");
let metadata = Location::from_pest(file_id, relay_statement.as_span());
let mut relay_statement = relay_statement.into_inner();
let timing = relay_statement.next().unwrap();
let relay_op_pair = relay_statement.next().unwrap();
let relay_id = relay_statement.next().unwrap().as_str();
let relay_op = relay_op_from_statement(file_id, relay_op_pair);
let relative_time = res.require(relay_timing_from_statement(file_id, timing));
let sensor_statement = ast::RelayStatement {
time: relative_time.unwrap(),
id: String::from(relay_id),
op: res.require(relay_op).unwrap(),
metadata,
};
res.with_value(sensor_statement)
}
fn relay_timing_from_statement(file_id: usize, relay_timing: Pair<Rule>) -> CompilerResult<u128> {
let mut res = CompilerResult::new("Parse relay timing");
let metadata = Location::from_pest(file_id, relay_timing.as_span());
let mut timing_iter = relay_timing.into_inner();
if let Some(time) = res.require(timing_from_tree(file_id, timing_iter.next().unwrap())) {
res.set_value(time);
} else {
res.error((metadata, "Failed to parse relay timing!"));
}
res
}
fn relay_op_from_statement(file_id: usize, relay_op: Pair<Rule>) -> CompilerResult<ast::RelayOp> {
let mut res = CompilerResult::new("Parse relay op");
let metadata = Location::from_pest(file_id, relay_op.as_span());
let op = relay_op.as_str();
match op {
"set" => {
return res.with_value(ast::RelayOp::Set);
}
"unset" => {
return res.with_value(ast::RelayOp::Unset);
}
_ => {
res.error((metadata, "No valid relay op given"));
}
}
res
}
#[cfg(test)]
mod test {
use super::*;
use crate::pest::Parser;
use ast::*;
#[test]
fn parse_timing_syntax() {
fn parse_timing(timing: &str) -> Option<u128> {
let timing_parsed = TDFParser::parse(Rule::TIMING, timing);
let timing_final = timing_from_tree(0, timing_parsed.unwrap().peek().unwrap());
timing_final.to_option()
}
let mut res = parse_timing("1min-30s-15ms");
assert_eq!(Some(60000 * 1 + 30 * 1000 + 15), res);
res = parse_timing("30s-15ms");
assert_eq!(Some(30 * 1000 + 15), res);
res = parse_timing("30min-15ms");
assert_eq!(Some(30 * 60000 + 15), res);
res = parse_timing("30min-15s");
assert_eq!(Some(30 * 60000 + 15 * 1000), res);
res = parse_timing("15min");
assert_eq!(Some(15 * 60000), res);
res = parse_timing("15s");
assert_eq!(Some(15 * 1000), res);
res = parse_timing("15ms");
assert_eq!(Some(15), res);
res = parse_timing("15ms-15ms-15ms");
assert_eq!(None, res);
res = parse_timing("15s-15s");
assert_eq!(None, res);
}
#[test]
fn parse_relay_statement() {
let relay_test = "at 1ms set relay1";
let relay_parsed = TDFParser::parse(Rule::RELAY_STATEMENT, relay_test);
let relay_final = relay_statement_from_tree(0, relay_parsed.unwrap().peek().unwrap());
let relay_expected = RelayStatement {
time: 1,
id: "relay1".to_string(),
op: RelayOp::Set,
metadata: Location::from_raw(0, 0, 17),
};
assert_eq!(Some(relay_expected), relay_final.to_option());
}
#[test]
fn parse_sensor_statement() {
let sensor_test = "at 100ms require { 10c < sensor1 < 20c } else pressure_lost";
let sensor_parsed = TDFParser::parse(Rule::SENSOR_STATEMENT, sensor_test);
let sensor_final = sensor_statement_from_tree(0, sensor_parsed.unwrap().peek().unwrap());
let sensor_expected = SensorStatement {
time: ast::SensorTime::Instant { time: 100 },
constraints: vec![SensorConstraint {
id: "sensor1".to_string(),
sensor_bound: SensorBound::Numeric(SensorBoundNumeric {
left: 10.0,
right: 20.0,
unit: "c".to_string(),
}),
abort: "pressure_lost".to_string(),
metadata: Location::from_raw(0, 19, 38),
}],
abort: "pressure_lost".to_string(),
metadata: Location::from_raw(0, 0, 59),
};
assert_eq!(Some(sensor_expected), sensor_final.to_option());
}
#[test]
fn parse_empty_tdf() {
let create_test = "
test {
}
"
.to_string();
let parsed_test = parse_tdf(0, create_test);
let final_test = parsed_test.to_option();
let check_test = ast::Test {
test_body: TestBody { statements: vec![] },
aborts: vec![],
};
assert_eq!(final_test, Some(check_test));
}
#[test]
fn parse_commented_tdf() {
let create_test = "
test {
#Hello World
#}
#at 1ms set relay1
#at 100ms require { 10c < sensor1 < 20c } else pressure_lost
#from 150ms to 200ms require {
#20c < sensor1 < 30c,
#sensor1 < 20c or sensor1 > 30c
#} else pressure_lost
}
"
.to_string();
let parsed_test = parse_tdf(0, create_test);
let final_test = parsed_test.to_option();
let check_test = ast::Test {
test_body: TestBody { statements: vec![] },
aborts: vec![],
};
assert_eq!(final_test, Some(check_test));
}
#[test]
fn parse_abort_tdf() {
let create_abort = "abort pressure_lost
{
from 150ms to 200ms require {
20c < sensor1 < 30c
} else HARD_ABORT
}";
let parsed_abort = TDFParser::parse(Rule::ABORT_SEQUENCE, create_abort);
let final_abort = abort_sequence_from_tree(0, parsed_abort.unwrap().peek().unwrap());
let sensor_contraint = vec![SensorConstraint {
id: "sensor1".to_string(),
sensor_bound: SensorBound::Numeric(SensorBoundNumeric {
left: 20.0,
right: 30.0,
unit: "c".to_string(),
}),
abort: "HARD_ABORT".to_string(),
metadata: Location::from_raw(0, 89, 108),
}];
let sensor_statements = vec![Statement::Sensor(SensorStatement {
time: SensorTime::Interval {
start: 150,
end: 200,
},
constraints: sensor_contraint,
abort: "HARD_ABORT".to_string(),
metadata: Location::from_raw(0, 43, 138),
})];
let check_abort = ast::Abort {
name: "pressure_lost".to_string(),
statements: sensor_statements,
};
assert_eq!(final_abort.to_option(), Some(check_abort))
}
#[test]
fn parse_section_tdf() {
let create_test = "test {
section new at 52ms for 50ms {
at 5ms unset relay1
}
}"
.to_string();
let parsed_test = parse_tdf(0, create_test);
let final_test = parsed_test.to_option();
let relay_vec = Statement::Section(SectionStatement {
name: String::from("new"),
time: SectionTime {
start: 52,
duration: 50,
},
statements: vec![Statement::Relay(RelayStatement {
time: 5,
id: String::from("relay1"),
op: RelayOp::Unset,
metadata: Location::from_raw(0, 66, 85),
})],
metadata: Location::from_raw(0, 19, 99),
});
let check_test = ast::Test {
test_body: TestBody {
statements: vec![relay_vec],
},
aborts: vec![],
};
assert_eq!(final_test, Some(check_test));
}
}