use std::{fs::File, io::Error, io::ErrorKind}; use sipp::{ decoder::{ByteStreamCharDecoder, Utf8Decoder}, parser::Parser, }; fn main() -> Result<(), Error> { // Open the file which contains the Acornsoft Logo commands sequence. let file = File::open("examples/Acornsoft_Logo_command_sequence.txt")?; // We don't need to check the initial byte sequence for anything, so just wrap the file // in a decoder directly. The file was saved using UTF-8 encoding. let decoder = Utf8Decoder::wrap(file); // Now wrap the decoder in a Parser so that we can extract data from it. let mut parser = Parser::wrap(decoder); let mut turtle = Turtle::new(); println!("Initial state of turtle:\n{:?}", turtle); // Keep parsing until no content remains. while let Some(c) = parser.peek()? { if c == '\n' { // End of current line, so skip past the newline character. parser.read()?; continue; } if c == '#' { // This line is just a comment, so skip to the end of the line. parser.skip_while(|c| c != '\n')?; continue; } // Any line not blank nor starting with a '#' we expect to be a command. This (very // much) simplified application only recognises four commands: // DRAW // FORWARD X // RIGHT D // LEFT D // where X is a number of pixels, and D is a number of degrees. // Because the four commands all start with a different character, this makes it much // easier to parse, as we can use this `peek` to see which command to expect. match c { 'D' => parser.require_str("DRAW")?, 'F' => parse_forward_command(&mut parser, &mut turtle)?, 'R' => parse_right_command(&mut parser, &mut turtle)?, 'L' => parse_left_command(&mut parser, &mut turtle)?, _ => { return Err(Error::new(ErrorKind::InvalidData, "Unrecognised command!")); } } // println!("Turtle status: {:?}", turtle); } println!("Final state of turtle:\n{:?}", turtle); Ok(()) } fn require_whitespace(parser: &mut Parser, File>) -> Result<(), Error> { // Now skip past any whitespace, and check whether or not any whitespace was found. let found_space = parser.skip_while(|c| c == ' ')?; if !found_space { return Err(Error::new( std::io::ErrorKind::InvalidData, "Whitespace required after instruction!", )); } Ok(()) } fn require_numeric_value( parser: &mut Parser, File>, command_name: &'static str, ) -> Result { // Read up to (but not including) the newline character or the end of input (whichever comes // first). Note that the newline character will not be removed from the input stream. if let Some(variable) = parser.read_up_to('\n')? { if let Ok(degrees) = variable.parse::() { Ok(degrees) } else { Err(Error::new( std::io::ErrorKind::InvalidData, format!("Invalid number for {} command!", command_name), )) } } else { Err(Error::new( std::io::ErrorKind::InvalidData, format!("Number must follow {} command!", command_name), )) } } fn parse_forward_command( parser: &mut Parser, File>, turtle: &mut Turtle, ) -> Result<(), Error> { // Require that the input sequence contains "FORWARD" and not something else beginning // with 'F'. parser.require_str("FORWARD")?; require_whitespace(parser)?; let move_amount = require_numeric_value(parser, "FORWARD")?; println!("Turning turtle forward by {} pixels.", move_amount); turtle.forward(move_amount); Ok(()) } fn parse_right_command( parser: &mut Parser, File>, turtle: &mut Turtle, ) -> Result<(), Error> { parser.require_str("RIGHT")?; require_whitespace(parser)?; let degrees = require_numeric_value(parser, "RIGHT")?; println!("Turning turtle right by {} degrees.", degrees); turtle.right(degrees); Ok(()) } fn parse_left_command( parser: &mut Parser, File>, turtle: &mut Turtle, ) -> Result<(), Error> { parser.require_str("LEFT")?; require_whitespace(parser)?; let degrees = require_numeric_value(parser, "LEFT")?; println!("Turning turtle left by {} degrees.", degrees); turtle.left(degrees); Ok(()) } #[derive(Debug)] struct Turtle { heading: u16, x_pos: f64, y_pos: f64, } impl Turtle { fn new() -> Turtle { Turtle { heading: 0, x_pos: 0.0, y_pos: 0.0, } } fn forward(&mut self, move_amount: u16) { let alpha = (self.heading as f64) * std::f64::consts::TAU / 360.0; self.x_pos += (move_amount as f64) * alpha.sin(); self.y_pos += (move_amount as f64) * alpha.cos(); } fn right(&mut self, degrees: u16) { self.heading += degrees; self.heading %= 360; } fn left(&mut self, degrees: u16) { self.heading -= degrees; self.heading %= 360; } }