diff --git a/client/main.py b/client/main.py index 7732871..7927be3 100644 --- a/client/main.py +++ b/client/main.py @@ -1,23 +1,30 @@ import requests import os +import time # Loop and get new api def main(): response = {} + id = -1 while True: try: response = api_get() - print(response) - match response["action_type"]: - case "output": - character = get_character(response["character"]) - output(character, response["content"]) - case "choice": - user_choice = choice(response["choices"]) - continue - case "end": - print("Exitting successfully") - os._exit(0) + if response["id"] != id: + id = response["id"] + print(response) + match response["action_type"]: + case "output": + character = get_character(response["character"]) + output(character, response["content"]) + case "choice": + user_choice = choice(response["choices"]) + time.sleep(0.5) + continue + case "end": + print("Exitting successfully") + os._exit(0) + else: + continue except: print("Server not up or cannot be reached") input() # Enter to go to next loop (testing) diff --git a/server/src/;a; b/server/src/;a; new file mode 100644 index 0000000..e224a14 --- /dev/null +++ b/server/src/;a; @@ -0,0 +1,184 @@ +use std::collections::HashMap; +use crate:: +{ + // Internal code + character, + api, + tokenise, + + // Libraries + mpsc::Receiver, + Arc, + Mutex, + info, + debug, + warn, + type_name, +}; + +mod strings; +mod character_parse; + +// Parse the tokens in a file +// Returns success or an error string +pub fn token_parse( + tokens: &Vec, + characters: &Arc>>, + data_to_send: &Arc>, + rx: &Receiver<(bool,usize,String)>, +) -> Result<(),String> +{ + info!("DSL parsing begun"); + let mut index: usize = 0; + if rx.recv().is_err() + { + warn!("Some issue with api"); + // TODO eh? + } + info!("Client has connected"); + // Run an infinite loop + 'parse_loop: loop + { + // Get the next token + let token = match tokens.get(index) { + Some(tokenise::Token::String(s)) => s.clone(), + Some(_) => return Err("Unexpected token".to_string()), + None => return Err("File unexpectedly reached termination point".to_string()), + }; + debug!("{index}: {token}"); + // The instructions are related to characters + if token.starts_with('@') + { + let character_name: String = token.chars().skip(1).collect(); + debug!("Doing something with a character: {character_name}"); + // The index is incremented to after the character's instructions + index = match character_parse::character_parse(index+1, tokens, character_name, characters, data_to_send) + { + Ok(increment) => increment, + Err((err,increment)) => + { + warn!("{err}"); + increment + }, + }; + } + // Miscelleneous instructions + else + { + match token.to_lowercase().as_str() + { + "end" => + { + info!("END command, exiting"); + return Ok(()) // quit successfully + }, + "choice" => + { + let mut choices: Vec = Vec::new(); + let mut choice_indeces: Vec = Vec::new(); + index += 1; + choices.push + (match tokens.get(index) { + Some(tokenise::Token::String(s)) => s.clone(), + Some(_) => return Err("Unexpected token".to_string()), + None => return Err("File unexpectedly reached termination point".to_string()), + }); + choice_indeces.push(index+1); + index += 2; + let next_token = match tokens.get(index) { + Some(tokenise::Token::String(s)) => s.clone(), + Some(_) => return Err("Unexpected token".to_string()), + None => return Err("File unexpectedly reached termination point".to_string()), + }; + while next_token == "or" + { + index += 1 + choices.push + (match tokens.get(index) { + Some(tokenise::Token::String(s)) => s.clone(), + Some(_) => return Err("Unexpected token".to_string()), + None => return Err("File unexpectedly reached termination point".to_string()), + }); + } + }, + /* + "choice" => + { + let (_,jump_points) = match choice_parse(index+1, tokens, data_to_send) + { + Ok((increment,jump_point)) => (increment,jump_point), + Err(error) => return Err(error), + }; + if rx.recv().is_err() { warn!("Error sending choices to client"); } + let (_, choice, _) = match rx.recv() + { + Ok((_,choice,_)) => (None::, choice, None::), + Err(err) => + { + warn!("Error receiving choice from client, defaulting to choice 0 {err}"); + (None::, 0, None::) + } + }; + index = jump_points[choice]; + info!("CHOICE command with {} choices",jump_points.len()); + debug!("{jump_points:?} {choice} {index}"); + continue 'parse_loop + }, + */ + "or" => + { + info!("OR command, jumping over"); + index += 2; + continue + }, + _ => + { + warn!("Invalid command: {token}"); + } + } + } + if rx.recv().is_err() + { + warn!("Some issue with api"); + } + } +} + +// Parse the options in a choice clause and returns the idexes of the code blocks +fn choice_parse +( + index: usize, + tokens: &[&str], + data_to_send: &Arc>, +) -> Result<(usize, Vec), String> +{ + let mut sum_index: usize = index; + let mut choices: Vec = Vec::new(); + let mut choice_indeces: Vec = Vec::new(); + // Ensure the index is valid (the index is not beyond the vector) + // Get the initial choice + let (choice_string, counter) = strings::extract_quoted(&tokens[sum_index..]) + .ok_or_else(|| "No choice string".to_string())?; + sum_index += counter; + choices.push(choice_string); + choice_indeces.push(sum_index+1); + sum_index += strings::closing_char(&tokens[sum_index..], '{','}') + .ok_or_else(|| "No closing brace".to_string())? + 1; + // Find all the alternate choices labelled with OR + // Fill out the choices vector with all the choice strings + while tokens[sum_index].to_lowercase() == "or" + { + let (choice_string, counter) = strings::extract_quoted(&tokens[sum_index+1..]) + .ok_or_else(|| "No choice string".to_string())?; + sum_index += counter; + choices.push(choice_string); + choice_indeces.push(sum_index+2); + sum_index += strings::closing_char(&tokens[sum_index..], '{','}') + .ok_or_else(|| "No closing brace".to_string())? + 1; + } + debug!("{choices:?}"); + // Send the choices to the Client via the API + api::modify_data(data_to_send, "choice".to_string(), String::new(), String::new(), choices); + // Return the choice indeces + Ok((sum_index + 1, choice_indeces)) +} diff --git a/server/src/api.rs b/server/src/api.rs index ee4d661..40398ec 100644 --- a/server/src/api.rs +++ b/server/src/api.rs @@ -20,6 +20,7 @@ use crate:: #[derive(Debug, Deserialize, Serialize, Clone)] pub struct DataToSend { + pub id: u32, pub action_type: String, pub content: String, pub character: String, @@ -53,7 +54,7 @@ pub async fn api_process // Perform this code on a GET request .map(|state: Arc>, tx_handle: Sender<(bool,usize,String)>| { - debug!("GET: {state:?}"); + //debug!("GET: {state:?}"); let reply = state.as_ref(); let _ = tx_handle.send((true,0,String::new())); warp::reply::json(&reply) // Send the reply data (data_to_send formatted as JSON) @@ -126,6 +127,7 @@ pub fn modify_data ) { let mut data = data_to_send.lock().unwrap_or_exit("Data to send Mutex was poisoned",2); // TODO eh? + data.id += 1; data.action_type = action_type; data.content = content; data.character = character_name; diff --git a/server/src/main.rs b/server/src/main.rs index 4122a57..6d93a06 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -45,7 +45,7 @@ async fn main() let (tx,rx) = mpsc::channel(); // Unzip zip archive to get data for story let file_name = args().nth(1) // Get filename from arguments - .unwrap_or_else + .unwrap_or_else // TODO unwrap or exit (|| { error!("No filename specified"); exit(10); @@ -79,6 +79,7 @@ async fn main() // Initialise the data strcut that will be sent out during API GET requests let data_to_send = Arc::new(Mutex::new(api::DataToSend { + id: 0, action_type: "begin".to_owned(), content: String::new(), character: String::new(), @@ -113,22 +114,15 @@ async fn main() error!("Unable to read story file to string: {err}"); exit(14); }); - // Tokenise story file - /* - let tokens: Vec<&str> = file_contents - .split_whitespace() - .collect(); - if ! tokens.contains(&"END") - { - warn!("No END statement, story may exit unexpectedly"); - } - */ let space_seperated: Vec<&str> = file_contents .split_whitespace() .collect(); + if ! space_seperated.contains(&"END") + { + warn!("No END statement, story may exit unexpectedly"); + } let (tokens, labels,_) = tokenise::tokenise(&space_seperated, 0).unwrap(); debug!("{tokens:?}"); -/* let data_clone2 = Arc::clone(&data_to_send); let characters_clone2 = Arc::clone(&characters); // Run the parsing process for the DSL @@ -138,6 +132,7 @@ async fn main() Ok(()) => { api::modify_data(&data_to_send, "end".to_string(), String::new(), String::new(), vec![]); + // TODO fix quitting instantly let _ = rx.recv(); // Wait for the client to respond info!("Program exited successfully"); exit(0); @@ -148,5 +143,4 @@ async fn main() exit(1); }, } -*/ } diff --git a/server/src/parsing.rs b/server/src/parsing.rs index 0c4e7dc..fdf14aa 100644 --- a/server/src/parsing.rs +++ b/server/src/parsing.rs @@ -4,6 +4,7 @@ use crate:: // Internal code character, api, + tokenise, // Libraries mpsc::Receiver, @@ -14,13 +15,12 @@ use crate:: warn, }; -mod strings; mod character_parse; // Parse the tokens in a file // Returns success or an error string pub fn token_parse( - tokens: &Vec<&str>, + tokens: &Vec, characters: &Arc>>, data_to_send: &Arc>, rx: &Receiver<(bool,usize,String)>, @@ -31,16 +31,14 @@ pub fn token_parse( if rx.recv().is_err() { warn!("Some issue with api"); - // TOD eh? + // TODO eh? } info!("Client has connected"); // Run an infinite loop 'parse_loop: loop { // Get the next token - let token = tokens - .get(index) - .ok_or_else(|| "File unexpectedly reached termination point".to_string())?; + let token = tokenise::get_string_token(tokens, index)?; debug!("{index}: {token}"); // The instructions are related to characters if token.starts_with('@') @@ -70,39 +68,48 @@ pub fn token_parse( }, "choice" => { - let (_,jump_points) = match choice_parse(index+1, tokens, data_to_send) + debug!("Doing CHOICE"); + let mut next_token = token; + let mut choices: Vec = Vec::new(); + let mut choice_indeces: Vec = Vec::new(); + while next_token == "or" || next_token == "choice" { - Ok((increment,jump_point)) => (increment,jump_point), - Err(error) => return Err(error), - }; + index += 1; + choices.push + ( + tokenise::get_string_token(tokens, index)? + ); + choice_indeces.push(index+1); + index += 2; + next_token = match tokenise::get_string_token(tokens, index) + { + Ok(string) => string, + Err(_) => break, + } + } + debug!("{choices:?}"); + debug!("{choice_indeces:?}"); + api::modify_data(data_to_send, "choice".to_string(), String::new(), String::new(), choices); if rx.recv().is_err() { warn!("Error sending choices to client"); } - let (_, choice, _) = match rx.recv() + let choice = match rx.recv() { - Ok((_,choice,_)) => (None::, choice, None::), + Ok((_,choice,_)) => choice_indeces[choice], Err(err) => { warn!("Error receiving choice from client, defaulting to choice 0 {err}"); - (None::, 0, None::) + 0 } }; - index = jump_points[choice]; - info!("CHOICE command with {} choices",jump_points.len()); - debug!("{jump_points:?} {choice} {index}"); + let object = tokenise::get_object_token(tokens, choice)?; // TODO make more efficient + debug!("{object:?}"); + // Don't propogate exit error + let _ = token_parse(object, characters, data_to_send, rx); continue 'parse_loop }, "or" => { info!("OR command, jumping over"); - index += match strings::closing_char(&tokens[index..], '{','}') - { - Some(index) => index, - None => return Err(String::from("No closing brace")), - }; - continue - }, - "}" => - { - index += 1; + index += 2; continue }, _ => @@ -117,42 +124,3 @@ pub fn token_parse( } } } - -// Parse the options in a choice clause and returns the idexes of the code blocks -fn choice_parse -( - index: usize, - tokens: &[&str], - data_to_send: &Arc>, -) -> Result<(usize, Vec), String> -{ - let mut sum_index: usize = index; - let mut choices: Vec = Vec::new(); - let mut choice_indeces: Vec = Vec::new(); - // Ensure the index is valid (the index is not beyond the vector) - // Get the initial choice - let (choice_string, counter) = strings::extract_quoted(&tokens[sum_index..]) - .ok_or_else(|| "No choice string".to_string())?; - sum_index += counter; - choices.push(choice_string); - choice_indeces.push(sum_index+1); - sum_index += strings::closing_char(&tokens[sum_index..], '{','}') - .ok_or_else(|| "No closing brace".to_string())? + 1; - // Find all the alternate choices labelled with OR - // Fill out the choices vector with all the choice strings - while tokens[sum_index].to_lowercase() == "or" - { - let (choice_string, counter) = strings::extract_quoted(&tokens[sum_index+1..]) - .ok_or_else(|| "No choice string".to_string())?; - sum_index += counter; - choices.push(choice_string); - choice_indeces.push(sum_index+2); - sum_index += strings::closing_char(&tokens[sum_index..], '{','}') - .ok_or_else(|| "No closing brace".to_string())? + 1; - } - debug!("{choices:?}"); - // Send the choices to the Client via the API - api::modify_data(data_to_send, "choice".to_string(), String::new(), String::new(), choices); - // Return the choice indeces - Ok((sum_index + 1, choice_indeces)) -} diff --git a/server/src/parsing/character_parse.rs b/server/src/parsing/character_parse.rs index 11d144f..143b785 100644 --- a/server/src/parsing/character_parse.rs +++ b/server/src/parsing/character_parse.rs @@ -1,9 +1,9 @@ -use super::strings; use crate:: { // Internal code character, api, + tokenise, UnwrapOrExit, //Libs Mutex, @@ -20,7 +20,7 @@ use crate:: pub fn character_parse ( index: usize, - tokens: &Vec<&str>, + tokens: &Vec, character_name: String, characters: &Arc>>, data_to_send: &Arc>, @@ -28,9 +28,11 @@ pub fn character_parse { let mut sum_index: usize = index; // Ensure the index is valid (the index is not beyond the vector) - let token = tokens - .get(sum_index) - .ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?; + let token = match tokens.get(sum_index) { + Some(tokenise::Token::String(s)) => s.clone(), + Some(_) => return Err(("Unexpected token".to_string(), sum_index + 1)), + None => return Err(("File unexpectedly reached termination point".to_string(), sum_index)), + }; match token.to_lowercase().as_str() { // The character is saying something, so grab the text and pass it @@ -38,35 +40,26 @@ pub fn character_parse "says" => { info!("SAYS command with character {character_name}"); - match strings::extract_quoted(&tokens[sum_index+1..]) - { - Some((output_string, counter)) => - { - debug!("{output_string}"); - sum_index += counter; - api::modify_data(data_to_send, "output".to_string(), output_string, character_name, vec![]); - }, - None => return Err(("Unable to read output string".to_string(), sum_index)), - } + sum_index += 1; + let output = tokenise::get_string_token(tokens, sum_index) + .map_err(|err| (err, index))?; + debug!("Saying {output}"); + api::modify_data(data_to_send, "output".to_string(), output.to_string(), character_name, vec![]); }, // Change the property of the selected character eg @tim CHANGE name "Bill Buffins" // will change the character with ID tim to "Bill Buffins"; a character's ID cannot change "change" => { sum_index += 1; - let feature = tokens - .get(sum_index) - .ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?; - let output_string: String; - (output_string, sum_index) = match strings::extract_quoted(&tokens[sum_index+1..]) - { - Some((string,counter)) => (string,sum_index+counter), - None => return Err(("Unable to parse property to change character".to_string(),sum_index)), - }; + let feature = tokenise::get_string_token(tokens, sum_index) + .map_err(|err| (err, index))?; + sum_index += 1; + let string = tokenise::get_string_token(tokens, sum_index) + .map_err(|err| (err, index))?; info!("CHANGE command with character {character_name} feature {feature}"); let mut characters = characters.lock().unwrap_or_exit("Character Mutex was poisoned",3); if let Some(character) = characters.get_mut(&character_name) - && character.set_field(feature, &output_string) + && character.set_field(&feature, &string) .is_err() { warn!("Feature {feature} does not exist") } drop(characters); api::modify_data(data_to_send, "change".to_string(), String::new(), character_name, vec![]); @@ -76,9 +69,8 @@ pub fn character_parse "to"|"animate" => { sum_index += 1; - let content = tokens - .get(sum_index) - .ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?; + let content = tokenise::get_string_token(tokens, sum_index) + .map_err(|err| (err, index))?; api::modify_data(data_to_send, token.to_lowercase(), content.to_string(), character_name, vec![]); }, // Catch all condition, if the instruction is unrecognised as a diff --git a/server/src/parsing/strings.rs b/server/src/parsing/strings.rs deleted file mode 100644 index b9ed60f..0000000 --- a/server/src/parsing/strings.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub fn closing_char(parts: &[&str], open: char, close: char) --> Option -{ - let mut indentation: usize = 0; - let mut flag = false; // flag to mark you've passed open - for (index, part) in parts.iter().enumerate() - { - if part.contains(open) - { - indentation += 1; - flag = true; - } - if part.contains(close) { indentation -= 1; } - if indentation == 0 && flag { return Some(index); } - } - None -} -pub fn extract_quoted(parts: &[&str]) --> Option<(String, usize)> -{ - let mut vec_string = Vec::new(); - let mut counter: usize = 0; - for part in parts - { - counter += 1; - vec_string.push(*part); - // End of the string - if part.ends_with('\"') // TODO allow for backslashes and ' - { - let final_string: String = vec_string.join(" "); - let final_string: String = final_string - .chars() - .skip(1) - .take( - final_string.chars() - .count() - 2) - .collect(); - return Some((final_string, counter)); - } - } - None -} diff --git a/server/src/tokenise.rs b/server/src/tokenise.rs index e34d189..5cc74ea 100644 --- a/server/src/tokenise.rs +++ b/server/src/tokenise.rs @@ -1,10 +1,9 @@ use crate:: { - debug, exit, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Token { Null, @@ -12,6 +11,29 @@ pub enum Token Object(Vec), } +pub fn get_string_token(tokens: &Vec, index: usize) +-> Result +{ + match tokens.get(index) { + Some(Token::String(s)) => return Ok(s.clone()), + Some(_) => return Err("Unexpected token".to_string()), + None => return Err("File unexpectedly reached termination point".to_string()), + } +} + +pub fn get_object_token(tokens: &Vec, index: usize) +-> Result<&Vec, String> +{ + match tokens.get(index) { + Some(Token::Object(v)) => return Ok(v), + Some(_) => return Err("Unexpected token".to_string()), + None => return Err("File unexpectedly reached termination point".to_string()), + } +} + +// Tokenise the story to Vec +// It can contain sub-objects (using recursive calls) +// TODO check for END pub fn tokenise(space_seperated: &Vec<&str>, mut index: usize) -> Result<(Vec,Vec<(String,usize)>,usize),String> { @@ -20,8 +42,7 @@ pub fn tokenise(space_seperated: &Vec<&str>, mut index: usize) while index < space_seperated.len() { let mut item = space_seperated[index].to_string(); - let mut object: Vec = Vec::new(); - debug!("{item}"); + let mut object: Vec; if item.starts_with("\"") { (index, item) = match tokenise_string(&space_seperated, index) diff --git a/stories/story.ha b/stories/story.ha index 47a99ad..677b1be 100644 --- a/stories/story.ha +++ b/stories/story.ha @@ -4,8 +4,21 @@ choice "choice numero uno" { @tim animate wave @tim says "super sad" + choice "choice numero uno" { + @tim animate wave + @tim says "super sad" + } + or "choice numero duo" { + @tim says "super unsad" + } + or "choice numero tres" { + @tim says "hola mi amigos" + } } or "choice numero duo" { @tim says "super unsad" } +or "choice numero tres" { + @tim says "hola mi amigos" +} END diff --git a/stories/test.zip b/stories/test.zip index 045234a..37dd0d4 100644 Binary files a/stories/test.zip and b/stories/test.zip differ