new paser + generate random instructions

This commit is contained in:
Volodymyr Patuta 2020-11-05 00:06:48 +01:00
parent f424f66650
commit 3f9e231ebf
3 changed files with 71 additions and 72 deletions

View File

@ -9,3 +9,5 @@ edition = "2018"
[dependencies]
clap = "2.33.3"
rand = "0.7.3"
pest = "2.0"
pest_derive = "2.0"

3
src/conf.pest Normal file
View File

@ -0,0 +1,3 @@
world = { ASCII_DIGIT* ~ " " ~ ASCII_DIGIT* }
robot_init = { ASCII_DIGIT* ~ " " ~ ASCII_DIGIT* ~ " " ~ ("S" | "N" | "W" | "E") }
robot_instructions = { ASCII_ALPHA_UPPER+ }

View File

@ -13,7 +13,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
extern crate pest;
#[macro_use]
extern crate pest_derive;
use clap::{App, Arg};
use pest::Parser;
use rand::Rng;
use std::collections::HashMap;
use std::fs;
use std::io;
@ -21,6 +27,10 @@ use std::io;
mod robot;
mod world;
#[derive(Parser)]
#[grammar = "conf.pest"]
pub struct ConfParser;
/// Check if the robot is in the map.
fn check_map(r: &robot::Robot, w: &world::World) -> Result<(), String> {
if r.p.x < 0 || r.p.y < 0 || r.p.x > w.x || r.p.y > w.y {
@ -48,119 +58,96 @@ fn create_hash_map(pool: &Vec<robot::Robot>, hash: &mut HashMap<robot::Position,
}
}
fn gen_random_instructions() -> String {
let mut rng = rand::thread_rng();
let n = rng.gen_range(5, 10);
let mut instructions = String::with_capacity(n);
const CHARSET: &[u8] = b"LRF";
for _ in 0..n {
let l = rng.gen_range(0, CHARSET.len());
instructions.push(CHARSET[l] as char);
}
instructions
}
/// Parse the config file, generate the world and robot pool.
fn parse_config(conf: String, pool: &mut Vec<robot::Robot>) -> Result<world::World, String> {
let mut lines = conf.lines();
// The first line of the config file should be the World.
let raw_line: &str = match lines.next() {
Some(raw) => raw,
None => {
return Err(String::from(
"Could not read the first line of the config file !",
))
}
let mut lines: Vec<&str> = conf.split('\n').collect();
let raw_world = match ConfParser::parse(Rule::world, lines.remove(0)) {
Ok(s) => s.as_str(),
Err(e) => return Err(format!("{}", e)),
};
let mut tokens = raw_line.split_whitespace();
let token1 = match tokens.next() {
Some(raw) => raw,
None => {
return Err(String::from(
"Could not read the first token of the first line !",
))
}
};
let token2 = match tokens.next() {
Some(raw) => raw,
None => {
return Err(String::from(
"Could not read the second token of the first line !",
))
}
};
let x: i32 = match token1.parse::<i32>() {
let mut w: Vec<i32> = Vec::with_capacity(2);
for n in raw_world.split_whitespace() {
let v: i32 = match n.parse::<i32>() {
Ok(x) => x,
Err(_) => {
return Err(String::from(
"Could not convert token one from the first string to i32",
))
}
Err(_) => return Err(String::from("World config is broken.")),
};
let y: i32 = match token2.parse::<i32>() {
Ok(x) => x,
Err(_) => {
return Err(String::from(
"Could not convert token two from the first string to i32",
))
w.push(v);
}
};
let w = world::World { x, y };
let world = world::World { x: w[0], y: w[1] };
lines.remove(0);
let mut r_id: u32 = 0;
loop {
r_id += 1;
// This line should be empty.
let empty_line = match lines.next() {
None => break,
Some(x) => x,
};
if !empty_line.is_empty() {
return Err(String::from("This line should be empty !"));
if lines.len() == 0 {
break;
}
let raw_setup = match lines.next() {
None => return Err(String::from("This line should be the config !")),
Some(raw) => raw,
let raw_setup = match ConfParser::parse(Rule::robot_init, lines.remove(0)) {
Ok(s) => s.as_str(),
Err(e) => return Err(format!("{}", e)),
};
if raw_setup.is_empty() {
return Err(String::from("This line should not be empty !"));
}
let raw_inst = match lines.next() {
None => return Err(String::from("This line should be the instruction !")),
Some(raw) => raw,
let rand_instructions = gen_random_instructions();
let l = lines.remove(0);
let instructions = match ConfParser::parse(Rule::robot_instructions, l) {
Ok(s) => s.as_str(),
Err(_) => rand_instructions.as_str(),
};
if raw_inst.is_empty() {
return Err(String::from("This line should not be empty !"));
}
// Parse the setup line of the robot.
let mut setup = raw_setup.split_whitespace();
let pos_x = match setup.next() {
Some(raw) => raw,
None => {
return Err(String::from(
"Could not read the first token of the setup line !",
))
}
Some(raw) => raw,
};
let pos_y = match setup.next() {
Some(raw) => raw,
None => {
return Err(String::from(
"Could not read the second token of the setup line !",
))
}
Some(raw) => raw,
};
let orientation = match setup.next() {
Some(raw) => raw,
None => {
return Err(String::from(
"Could not read the third token of the setup line !",
))
}
Some(raw) => raw,
};
// Convert values of the setup line
let r_x = match pos_x.parse::<i32>() {
Ok(raw) => raw,
Err(_) => {
return Err(String::from(
"Could not convert the first token of the setup ligne to i32 !",
))
}
Ok(raw) => raw,
};
let r_y = match pos_y.parse::<i32>() {
Ok(raw) => raw,
Err(_) => {
return Err(String::from(
"Could not convert the second token of the setup ligne to i32 !",
))
}
Ok(raw) => raw,
};
let r_o = match orientation {
"N" => robot::Orientation::N,
@ -173,9 +160,8 @@ fn parse_config(conf: String, pool: &mut Vec<robot::Robot>) -> Result<world::Wor
))
}
};
// Convert instructions line.
let inst: Vec<char> = raw_inst.chars().rev().collect();
let inst: Vec<char> = instructions.chars().rev().collect();
if !robot::is_instructions(&inst) {
return Err(String::from("Invalid instructions !"));
}
@ -183,13 +169,21 @@ fn parse_config(conf: String, pool: &mut Vec<robot::Robot>) -> Result<world::Wor
let r = robot::Robot::new(r_id, r_o, robot::Position { x: r_x, y: r_y }, inst);
// Load robot inside the pool.
match check_map(&r, &w) {
match check_map(&r, &world) {
Ok(()) => pool.push(r),
Err(err) => return Err(err),
}
if lines.len() == 0 {
break;
}
if l.len() == 0 {
continue;
}
lines.remove(0);
}
Ok(w)
Ok(world)
}
/// Retrieve the content of a file and return it as a string.