diff --git a/server/src/character.rs b/server/src/character.rs index b4a4a70..f9f08a6 100644 --- a/server/src/character.rs +++ b/server/src/character.rs @@ -17,7 +17,9 @@ pub struct Character { name: String, gender: String, - eye_color: String, + eye_color: Colour, + hair_color: Colour, + skin_color: Colour, pronoun_subject: String, pronoun_object: String, pronoun_deppos: String, @@ -28,7 +30,6 @@ pub struct Character torso_shape: String, arm_shape: String, leg_shape: String, - hair_color: String, // TODO RGB enum clothing: Clothing, } #[derive(Debug,Deserialize,Serialize,Clone,Default)] @@ -39,6 +40,15 @@ pub struct Clothing bottom: String, shoes: String, } +#[derive(Debug,Deserialize,Serialize,Clone,Default)] +#[serde(default)] +pub struct Colour +{ + red: u8, + green: u8, + blue: u8, +} + impl Character { // Big ass ugly match case pub fn set_field(&mut self, field: &str, value: &str) -> Result<(), String> { diff --git a/server/src/main.rs b/server/src/main.rs index daccc71..818e375 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -106,18 +106,18 @@ async fn main() let space_seperated: Vec<&str> = file_contents .split_whitespace() .collect(); - if ! space_seperated.contains(&"END") + if ! space_seperated.contains(&"END") // TODO remove { warn!("No END statement, story may exit unexpectedly"); } let (tokens, labels,_) = tokenise::tokenise(&space_seperated, 0) .unwrap_or_exit("Unable to tokenise data", 15); - debug!("{tokens:?}"); + debug!("{tokens:?}\n{labels:?}"); let data_clone2 = Arc::clone(&data_to_send); let characters_clone2 = Arc::clone(&characters); // Run the parsing process for the DSL info!("DSL parsing begun"); - match parsing::token_parse(&tokens, &characters_clone2, &data_clone2, &rx) + match parsing::token_parse(&tokens, &labels, &characters_clone2, &data_clone2, &rx) { // Exit with error or success Ok(()) => diff --git a/server/src/parsing.rs b/server/src/parsing.rs index ad11fe5..ffe1a3f 100644 --- a/server/src/parsing.rs +++ b/server/src/parsing.rs @@ -21,6 +21,7 @@ mod character_parse; // Returns success or an error string pub fn token_parse( tokens: &[tokenise::Token], + labels: &HashMap, characters: &Arc>>, data_to_send: &Arc>, rx: &Receiver<(bool,usize,String)>, @@ -36,85 +37,126 @@ pub fn token_parse( // Run an infinite loop 'parse_loop: loop { + debug!("Reading {index}"); // Get the next token - let token = tokenise::get_string_token(tokens, index)?; + let token: String = match tokens.get(index) + { + Some(tokenise::Token::Keyword(s)) => s.to_string(), + // Ignore closing braces and jump over opening brace blocks + Some(tokenise::Token::Bracket((bracket,new_index))) => + { + if bracket == &tokenise::Bracket::Closing + { + index += 1; + } + else + { + warn!("Unexpected brace block, jumping over..."); + index = new_index + 1; + } + continue 'parse_loop + }, + // Handle a character + Some(tokenise::Token::Character(character_name)) => + { + index = match character_parse::character_parse(index+1,tokens,character_name.to_string(),characters,data_to_send) + { + Ok(increment) => increment, + Err((err,increment)) => + { + warn!("{err}"); + increment + }, + }; + continue 'parse_loop + } + Some(_) => + { + warn!("Unexpected token"); + index += 1; + continue 'parse_loop + }, + None => return Err("File unexpectedly reached termination point".to_string()), + }; debug!("{index}: {token}"); // The instructions are related to characters - if token.starts_with('@') + match token.to_lowercase().as_str() { - 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) + "end" => { - Ok(increment) => increment, - Err((err,increment)) => - { - warn!("{err}"); - increment - }, - }; - } - // Miscelleneous instructions - else - { - match token.to_lowercase().as_str() + info!("END command, exiting"); + return Ok(()) // quit successfully + }, + "choice" => { - "end" => + let mut next_token: String = token; + let mut choices: Vec = Vec::new(); + let mut choice_indeces: Vec = Vec::new(); + while next_token == "or" || next_token == "choice" { - info!("END command, exiting"); - return Ok(()) // quit successfully - }, - "choice" => - { - 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" + index += 1; + choices.push + ( + tokenise::get_string_token(tokens, index)? + ); + index += 1; + choice_indeces.push(index+1); + index = match tokenise::get_closing_index(tokens,index) { - 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() - { - Ok((_,choice,_)) => choice_indeces[choice], - Err(err) => - { - warn!("Error receiving choice from client, defaulting to choice 0 {err}"); - 0 - } + Ok(new_index) => new_index + 1, + Err(_) => break, }; - 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 += 2; - continue - }, - _ => - { - warn!("Invalid command: {token}"); + next_token = match tokenise::get_keyword_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() + { + Ok((_,choice,_)) => choice_indeces[choice], + Err(err) => + { + warn!("Error receiving choice from client, defaulting to choice 0 {err}"); + 0 + } + }; + index = choice; + continue 'parse_loop + }, + "or" => + { + info!("OR command, jumping over"); + index += 2; + let new_index = tokenise::get_closing_index(tokens, index)?; + index = new_index; + continue 'parse_loop + }, + // Jump to a particular index based on a label eg GOTO character_check + "goto" => + { + index += 1; + let label = tokenise::get_keyword_token(tokens, index)?; + index = match labels.get(&label) + { + Some(label_index) => *label_index, + None => + { + warn!("Label {label} does not exist"); + index + } + }; + debug!("Jumping to {index}"); + continue 'parse_loop + } + _ => + { + warn!("Invalid command: {token}"); + index += 1; } } if rx.recv().is_err() diff --git a/server/src/parsing/character_parse.rs b/server/src/parsing/character_parse.rs index b07d5cd..e80142a 100644 --- a/server/src/parsing/character_parse.rs +++ b/server/src/parsing/character_parse.rs @@ -28,12 +28,9 @@ pub fn character_parse { let mut sum_index: usize = index; // Ensure the index is valid (the index is not beyond the vector) - 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() + let keyword = tokenise::get_keyword_token(tokens, sum_index) + .map_err(|err| (err, sum_index))?; + match keyword.to_lowercase().as_str() { // The character is saying something, so grab the text and pass it // to the client @@ -51,7 +48,7 @@ pub fn character_parse "change" => { sum_index += 1; - let feature = tokenise::get_string_token(tokens, sum_index) + let feature = tokenise::get_keyword_token(tokens, sum_index) .map_err(|err| (err, index))?; sum_index += 1; let string = tokenise::get_string_token(tokens, sum_index) @@ -69,13 +66,13 @@ pub fn character_parse "to"|"animate" => { sum_index += 1; - let content = tokenise::get_string_token(tokens, sum_index) + let content = tokenise::get_keyword_token(tokens, sum_index) .map_err(|err| (err, index))?; - api::modify_data(data_to_send, token.to_lowercase(), content, character_name, vec![]); + api::modify_data(data_to_send, keyword.to_lowercase(), content, character_name, vec![]); }, // Catch all condition, if the instruction is unrecognised as a // character command - _ => return Err((format!("Invalid command: {token}"),sum_index)), + _ => return Err((format!("Invalid command: {keyword}"),sum_index)), } sum_index += 1; Ok(sum_index) diff --git a/server/src/tokenise.rs b/server/src/tokenise.rs index 8fdcef9..bb029b9 100644 --- a/server/src/tokenise.rs +++ b/server/src/tokenise.rs @@ -1,13 +1,23 @@ use crate:: { exit, + HashMap, }; #[derive(Debug, Clone)] pub enum Token { - String(String), - Object(Vec), + String(String), + Keyword(String), // Keywords aren't checked for validity in this stage + Identifier(String), + Bracket((Bracket,usize)), // Stores the index of the matching deliminator + Character(String), +} +#[derive(Debug,Clone,PartialEq)] +pub enum Bracket +{ + Opening, + Closing, } pub fn get_string_token(tokens: &[Token], index: usize) @@ -19,12 +29,20 @@ pub fn get_string_token(tokens: &[Token], index: usize) None => Err("File unexpectedly reached termination point".to_string()), } } - -pub fn get_object_token(tokens: &[Token], index: usize) --> Result<&Vec, String> +pub fn get_keyword_token(tokens: &[Token], index: usize) +-> Result { match tokens.get(index) { - Some(Token::Object(v)) => Ok(v), + Some(Token::Keyword(s)) => Ok(s.clone()), + Some(_) => Err("Unexpected token".to_string()), + None => Err("File unexpectedly reached termination point".to_string()), + } +} +pub fn get_closing_index(tokens: &[Token], index: usize) +-> Result +{ + match tokens.get(index) { + Some(Token::Bracket((_, index))) => Ok(*index), // TODO check for closing Some(_) => Err("Unexpected token".to_string()), None => Err("File unexpectedly reached termination point".to_string()), } @@ -34,37 +52,55 @@ pub fn get_object_token(tokens: &[Token], index: usize) // 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> +-> Result<(Vec,HashMap,usize),String> { let mut tokenised_data: Vec = Vec::new(); - let mut labels: Vec<(String,usize)> = Vec::new(); + let mut labels: HashMap = HashMap::new(); + let mut bracket_stack: Vec = Vec::new(); while index < space_seperated.len() { let mut item = space_seperated[index].to_string(); - let object: Vec; - if item.starts_with('"') // TODO support ' + // Characters + if item.starts_with('@') { - (index, item) = match tokenise_string(space_seperated, index) - { - Some((index,item)) => (index,item), - None => exit(1), - }; + let mut chars = item.chars(); + chars.next(); + tokenised_data.push(Token::Character(chars.as_str().to_string())); + } + // Strings + else if item.starts_with('"') // TODO support ' + { + let Some((new_index, new_item)) = tokenise_string(space_seperated, index) + else { exit(1) }; + index = new_index; + item = new_item; tokenised_data.push(Token::String(item)); } + // Labels + else if item.ends_with(':') + { + let mut chars = item.chars(); + chars.next_back(); + let label = chars.as_str(); + labels.insert + ( + label.to_string(), + tokenised_data.len(), + ); + } + // On a new indentation level, do a recursive call else if item == "{" { - (object, labels, index) = match tokenise(space_seperated, index+1) // !WARNING! recursive call - { - Ok((object,labels,index)) => (object,labels,index), - Err(err) => return Err(err), - }; - tokenised_data.push(Token::Object(object)); + bracket_stack.push(tokenised_data.len()); + tokenised_data.push(Token::Bracket((Bracket::Opening,0))); // TODO fix no closing brace edge case } else if item == "}" { - return Ok((tokenised_data,labels,index)); + let prev_index = bracket_stack.pop().unwrap(); // TODO eh + tokenised_data[prev_index] = Token::Bracket((Bracket::Opening, tokenised_data.len())); + tokenised_data.push(Token::Bracket((Bracket::Closing,prev_index))); } - else { tokenised_data.push(Token::String(item)); } + else { tokenised_data.push(Token::Keyword(item)); } index += 1; } Ok((tokenised_data, labels, 0)) diff --git a/stories/characters.json b/stories/characters.json index 8de2708..7d0549d 100644 --- a/stories/characters.json +++ b/stories/characters.json @@ -2,9 +2,9 @@ "tim": { "name": "Timothy Sharpshooter", "gender": "Male", - "skin_color": "", - "eye_color": "", - "hair_color": "", + "skin_color": [0,0,0], + "eye_color": [0,0,0], + "hair_color": [0,0,0], "pronoun_subject": "He", "pronoun_object": "Him", "pronoun_deppos": "His", diff --git a/stories/story.ha b/stories/story.ha index 298181a..ded3738 100644 --- a/stories/story.ha +++ b/stories/story.ha @@ -1,25 +1,14 @@ @tim says "hello world, it's a good day" @tim change name "Timothy Fineshooter" -@tim change boop "Timothy Fineshooter" -@tim to fr +label: 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" + goto label } END diff --git a/stories/test.zip b/stories/test.zip index 3070a5f..3e1fc1f 100644 Binary files a/stories/test.zip and b/stories/test.zip differ