diff --git a/Cargo.toml b/Cargo.toml
index a264f32..858be56 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,3 +9,5 @@ edition = "2018"
[dependencies]
clap = "2.33.3"
rand = "0.7.3"
+pest = "2.0"
+pest_derive = "2.0"
diff --git a/src/conf.pest b/src/conf.pest
new file mode 100644
index 0000000..67df1e6
--- /dev/null
+++ b/src/conf.pest
@@ -0,0 +1,3 @@
+world = { ASCII_DIGIT* ~ " " ~ ASCII_DIGIT* }
+robot_init = { ASCII_DIGIT* ~ " " ~ ASCII_DIGIT* ~ " " ~ ("S" | "N" | "W" | "E") }
+robot_instructions = { ASCII_ALPHA_UPPER+ }
diff --git a/src/main.rs b/src/main.rs
index 22feaca..1568944 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,7 +13,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+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, hash: &mut HashMap 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) -> Result {
- 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::() {
- Ok(x) => x,
- Err(_) => {
- return Err(String::from(
- "Could not convert token one from the first string to i32",
- ))
- }
- };
- let y: i32 = match token2.parse::() {
- Ok(x) => x,
- Err(_) => {
- return Err(String::from(
- "Could not convert token two from the first string to i32",
- ))
- }
- };
- let w = world::World { x, y };
+ let mut w: Vec = Vec::with_capacity(2);
+ for n in raw_world.split_whitespace() {
+ let v: i32 = match n.parse::() {
+ Ok(x) => x,
+ Err(_) => return Err(String::from("World config is broken.")),
+ };
+ w.push(v);
+ }
+ 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::() {
+ 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::() {
+ 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) -> Result = raw_inst.chars().rev().collect();
+ let inst: Vec = 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) -> Result 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.