Changed how the data is tokenised

This commit is contained in:
2026-05-17 12:47:22 +01:00
parent 0c28bc113d
commit 13049309b2
10 changed files with 303 additions and 164 deletions
+7
View File
@@ -1,12 +1,16 @@
import requests import requests
import os import os
import time
# Loop and get new api # Loop and get new api
def main(): def main():
response = {} response = {}
id = -1
while True: while True:
try: try:
response = api_get() response = api_get()
if response["id"] != id:
id = response["id"]
print(response) print(response)
match response["action_type"]: match response["action_type"]:
case "output": case "output":
@@ -14,10 +18,13 @@ def main():
output(character, response["content"]) output(character, response["content"])
case "choice": case "choice":
user_choice = choice(response["choices"]) user_choice = choice(response["choices"])
time.sleep(0.5)
continue continue
case "end": case "end":
print("Exitting successfully") print("Exitting successfully")
os._exit(0) os._exit(0)
else:
continue
except: except:
print("Server not up or cannot be reached") print("Server not up or cannot be reached")
input() # Enter to go to next loop (testing) input() # Enter to go to next loop (testing)
+184
View File
@@ -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<tokenise::Token>,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>,
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<String> = Vec::new();
let mut choice_indeces: Vec<usize> = 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::<bool>, choice, None::<String>),
Err(err) =>
{
warn!("Error receiving choice from client, defaulting to choice 0 {err}");
(None::<bool>, 0, None::<String>)
}
};
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<Mutex<api::DataToSend>>,
) -> Result<(usize, Vec<usize>), String>
{
let mut sum_index: usize = index;
let mut choices: Vec<String> = Vec::new();
let mut choice_indeces: Vec<usize> = 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))
}
+3 -1
View File
@@ -20,6 +20,7 @@ use crate::
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DataToSend { pub struct DataToSend {
pub id: u32,
pub action_type: String, pub action_type: String,
pub content: String, pub content: String,
pub character: String, pub character: String,
@@ -53,7 +54,7 @@ pub async fn api_process
// Perform this code on a GET request // Perform this code on a GET request
.map(|state: Arc<Mutex<DataToSend>>, tx_handle: Sender<(bool,usize,String)>| .map(|state: Arc<Mutex<DataToSend>>, tx_handle: Sender<(bool,usize,String)>|
{ {
debug!("GET: {state:?}"); //debug!("GET: {state:?}");
let reply = state.as_ref(); let reply = state.as_ref();
let _ = tx_handle.send((true,0,String::new())); let _ = tx_handle.send((true,0,String::new()));
warp::reply::json(&reply) // Send the reply data (data_to_send formatted as JSON) 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? 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.action_type = action_type;
data.content = content; data.content = content;
data.character = character_name; data.character = character_name;
+7 -13
View File
@@ -45,7 +45,7 @@ async fn main()
let (tx,rx) = mpsc::channel(); let (tx,rx) = mpsc::channel();
// Unzip zip archive to get data for story // Unzip zip archive to get data for story
let file_name = args().nth(1) // Get filename from arguments let file_name = args().nth(1) // Get filename from arguments
.unwrap_or_else .unwrap_or_else // TODO unwrap or exit
(|| { (|| {
error!("No filename specified"); error!("No filename specified");
exit(10); exit(10);
@@ -79,6 +79,7 @@ async fn main()
// Initialise the data strcut that will be sent out during API GET requests // Initialise the data strcut that will be sent out during API GET requests
let data_to_send = Arc::new(Mutex::new(api::DataToSend let data_to_send = Arc::new(Mutex::new(api::DataToSend
{ {
id: 0,
action_type: "begin".to_owned(), action_type: "begin".to_owned(),
content: String::new(), content: String::new(),
character: String::new(), character: String::new(),
@@ -113,22 +114,15 @@ async fn main()
error!("Unable to read story file to string: {err}"); error!("Unable to read story file to string: {err}");
exit(14); 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 let space_seperated: Vec<&str> = file_contents
.split_whitespace() .split_whitespace()
.collect(); .collect();
if ! space_seperated.contains(&"END")
{
warn!("No END statement, story may exit unexpectedly");
}
let (tokens, labels,_) = tokenise::tokenise(&space_seperated, 0).unwrap(); let (tokens, labels,_) = tokenise::tokenise(&space_seperated, 0).unwrap();
debug!("{tokens:?}"); debug!("{tokens:?}");
/*
let data_clone2 = Arc::clone(&data_to_send); let data_clone2 = Arc::clone(&data_to_send);
let characters_clone2 = Arc::clone(&characters); let characters_clone2 = Arc::clone(&characters);
// Run the parsing process for the DSL // Run the parsing process for the DSL
@@ -138,6 +132,7 @@ async fn main()
Ok(()) => Ok(()) =>
{ {
api::modify_data(&data_to_send, "end".to_string(), String::new(), String::new(), vec![]); 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 let _ = rx.recv(); // Wait for the client to respond
info!("Program exited successfully"); info!("Program exited successfully");
exit(0); exit(0);
@@ -148,5 +143,4 @@ async fn main()
exit(1); exit(1);
}, },
} }
*/
} }
+33 -65
View File
@@ -4,6 +4,7 @@ use crate::
// Internal code // Internal code
character, character,
api, api,
tokenise,
// Libraries // Libraries
mpsc::Receiver, mpsc::Receiver,
@@ -14,13 +15,12 @@ use crate::
warn, warn,
}; };
mod strings;
mod character_parse; mod character_parse;
// Parse the tokens in a file // Parse the tokens in a file
// Returns success or an error string // Returns success or an error string
pub fn token_parse( pub fn token_parse(
tokens: &Vec<&str>, tokens: &Vec<tokenise::Token>,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>, characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>, data_to_send: &Arc<Mutex<api::DataToSend>>,
rx: &Receiver<(bool,usize,String)>, rx: &Receiver<(bool,usize,String)>,
@@ -31,16 +31,14 @@ pub fn token_parse(
if rx.recv().is_err() if rx.recv().is_err()
{ {
warn!("Some issue with api"); warn!("Some issue with api");
// TOD eh? // TODO eh?
} }
info!("Client has connected"); info!("Client has connected");
// Run an infinite loop // Run an infinite loop
'parse_loop: loop 'parse_loop: loop
{ {
// Get the next token // Get the next token
let token = tokens let token = tokenise::get_string_token(tokens, index)?;
.get(index)
.ok_or_else(|| "File unexpectedly reached termination point".to_string())?;
debug!("{index}: {token}"); debug!("{index}: {token}");
// The instructions are related to characters // The instructions are related to characters
if token.starts_with('@') if token.starts_with('@')
@@ -70,39 +68,48 @@ pub fn token_parse(
}, },
"choice" => "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<String> = Vec::new();
let mut choice_indeces: Vec<usize> = Vec::new();
while next_token == "or" || next_token == "choice"
{ {
Ok((increment,jump_point)) => (increment,jump_point), index += 1;
Err(error) => return Err(error), 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"); } if rx.recv().is_err() { warn!("Error sending choices to client"); }
let (_, choice, _) = match rx.recv() let choice = match rx.recv()
{ {
Ok((_,choice,_)) => (None::<bool>, choice, None::<String>), Ok((_,choice,_)) => choice_indeces[choice],
Err(err) => Err(err) =>
{ {
warn!("Error receiving choice from client, defaulting to choice 0 {err}"); warn!("Error receiving choice from client, defaulting to choice 0 {err}");
(None::<bool>, 0, None::<String>) 0
} }
}; };
index = jump_points[choice]; let object = tokenise::get_object_token(tokens, choice)?; // TODO make more efficient
info!("CHOICE command with {} choices",jump_points.len()); debug!("{object:?}");
debug!("{jump_points:?} {choice} {index}"); // Don't propogate exit error
let _ = token_parse(object, characters, data_to_send, rx);
continue 'parse_loop continue 'parse_loop
}, },
"or" => "or" =>
{ {
info!("OR command, jumping over"); info!("OR command, jumping over");
index += match strings::closing_char(&tokens[index..], '{','}') index += 2;
{
Some(index) => index,
None => return Err(String::from("No closing brace")),
};
continue
},
"}" =>
{
index += 1;
continue 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<Mutex<api::DataToSend>>,
) -> Result<(usize, Vec<usize>), String>
{
let mut sum_index: usize = index;
let mut choices: Vec<String> = Vec::new();
let mut choice_indeces: Vec<usize> = 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))
}
+20 -28
View File
@@ -1,9 +1,9 @@
use super::strings;
use crate:: use crate::
{ {
// Internal code // Internal code
character, character,
api, api,
tokenise,
UnwrapOrExit, UnwrapOrExit,
//Libs //Libs
Mutex, Mutex,
@@ -20,7 +20,7 @@ use crate::
pub fn character_parse pub fn character_parse
( (
index: usize, index: usize,
tokens: &Vec<&str>, tokens: &Vec<tokenise::Token>,
character_name: String, character_name: String,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>, characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>, data_to_send: &Arc<Mutex<api::DataToSend>>,
@@ -28,9 +28,11 @@ pub fn character_parse
{ {
let mut sum_index: usize = index; let mut sum_index: usize = index;
// Ensure the index is valid (the index is not beyond the vector) // Ensure the index is valid (the index is not beyond the vector)
let token = tokens let token = match tokens.get(sum_index) {
.get(sum_index) Some(tokenise::Token::String(s)) => s.clone(),
.ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?; 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() match token.to_lowercase().as_str()
{ {
// The character is saying something, so grab the text and pass it // The character is saying something, so grab the text and pass it
@@ -38,35 +40,26 @@ pub fn character_parse
"says" => "says" =>
{ {
info!("SAYS command with character {character_name}"); info!("SAYS command with character {character_name}");
match strings::extract_quoted(&tokens[sum_index+1..]) sum_index += 1;
{ let output = tokenise::get_string_token(tokens, sum_index)
Some((output_string, counter)) => .map_err(|err| (err, index))?;
{ debug!("Saying {output}");
debug!("{output_string}"); api::modify_data(data_to_send, "output".to_string(), output.to_string(), character_name, vec![]);
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)),
}
}, },
// Change the property of the selected character eg @tim CHANGE name "Bill Buffins" // 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 // will change the character with ID tim to "Bill Buffins"; a character's ID cannot change
"change" => "change" =>
{ {
sum_index += 1; sum_index += 1;
let feature = tokens let feature = tokenise::get_string_token(tokens, sum_index)
.get(sum_index) .map_err(|err| (err, index))?;
.ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?; sum_index += 1;
let output_string: String; let string = tokenise::get_string_token(tokens, sum_index)
(output_string, sum_index) = match strings::extract_quoted(&tokens[sum_index+1..]) .map_err(|err| (err, index))?;
{
Some((string,counter)) => (string,sum_index+counter),
None => return Err(("Unable to parse property to change character".to_string(),sum_index)),
};
info!("CHANGE command with character {character_name} feature {feature}"); info!("CHANGE command with character {character_name} feature {feature}");
let mut characters = characters.lock().unwrap_or_exit("Character Mutex was poisoned",3); let mut characters = characters.lock().unwrap_or_exit("Character Mutex was poisoned",3);
if let Some(character) = characters.get_mut(&character_name) 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") } .is_err() { warn!("Feature {feature} does not exist") }
drop(characters); drop(characters);
api::modify_data(data_to_send, "change".to_string(), String::new(), character_name, vec![]); api::modify_data(data_to_send, "change".to_string(), String::new(), character_name, vec![]);
@@ -76,9 +69,8 @@ pub fn character_parse
"to"|"animate" => "to"|"animate" =>
{ {
sum_index += 1; sum_index += 1;
let content = tokens let content = tokenise::get_string_token(tokens, sum_index)
.get(sum_index) .map_err(|err| (err, index))?;
.ok_or_else(|| ("File unexpectedly reached termination point".to_string(), sum_index))?;
api::modify_data(data_to_send, token.to_lowercase(), content.to_string(), character_name, vec![]); 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 // Catch all condition, if the instruction is unrecognised as a
-42
View File
@@ -1,42 +0,0 @@
pub fn closing_char(parts: &[&str], open: char, close: char)
-> Option<usize>
{
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
}
+25 -4
View File
@@ -1,10 +1,9 @@
use crate:: use crate::
{ {
debug,
exit, exit,
}; };
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Token pub enum Token
{ {
Null, Null,
@@ -12,6 +11,29 @@ pub enum Token
Object(Vec<Token>), Object(Vec<Token>),
} }
pub fn get_string_token(tokens: &Vec<Token>, index: usize)
-> Result<String, String>
{
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<Token>, index: usize)
-> Result<&Vec<Token>, 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<Token>
// It can contain sub-objects (using recursive calls)
// TODO check for END
pub fn tokenise(space_seperated: &Vec<&str>, mut index: usize) pub fn tokenise(space_seperated: &Vec<&str>, mut index: usize)
-> Result<(Vec<Token>,Vec<(String,usize)>,usize),String> -> Result<(Vec<Token>,Vec<(String,usize)>,usize),String>
{ {
@@ -20,8 +42,7 @@ pub fn tokenise(space_seperated: &Vec<&str>, mut index: usize)
while index < space_seperated.len() while index < space_seperated.len()
{ {
let mut item = space_seperated[index].to_string(); let mut item = space_seperated[index].to_string();
let mut object: Vec<Token> = Vec::new(); let mut object: Vec<Token>;
debug!("{item}");
if item.starts_with("\"") if item.starts_with("\"")
{ {
(index, item) = match tokenise_string(&space_seperated, index) (index, item) = match tokenise_string(&space_seperated, index)
+13
View File
@@ -1,6 +1,9 @@
@tim says "hello world, it's a good day" @tim says "hello world, it's a good day"
@tim change name "Timothy Fineshooter" @tim change name "Timothy Fineshooter"
@tim to fr @tim to fr
choice "choice numero uno" {
@tim animate wave
@tim says "super sad"
choice "choice numero uno" { choice "choice numero uno" {
@tim animate wave @tim animate wave
@tim says "super sad" @tim says "super sad"
@@ -8,4 +11,14 @@ choice "choice numero uno" {
or "choice numero duo" { or "choice numero duo" {
@tim says "super unsad" @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 END
BIN
View File
Binary file not shown.