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 os
import time
# Loop and get new api
def main():
response = {}
id = -1
while True:
try:
response = api_get()
if response["id"] != id:
id = response["id"]
print(response)
match response["action_type"]:
case "output":
@@ -14,10 +18,13 @@ def main():
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)
+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)]
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<Mutex<DataToSend>>, 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;
+7 -13
View File
@@ -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);
},
}
*/
}
+33 -65
View File
@@ -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<tokenise::Token>,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>,
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<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),
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::<bool>, choice, None::<String>),
Ok((_,choice,_)) => choice_indeces[choice],
Err(err) =>
{
warn!("Error receiving choice from client, defaulting to choice 0 {err}");
(None::<bool>, 0, None::<String>)
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<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::
{
// 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<tokenise::Token>,
character_name: String,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>,
@@ -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
-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::
{
debug,
exit,
};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Token
{
Null,
@@ -12,6 +11,29 @@ pub enum 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)
-> 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()
{
let mut item = space_seperated[index].to_string();
let mut object: Vec<Token> = Vec::new();
debug!("{item}");
let mut object: Vec<Token>;
if item.starts_with("\"")
{
(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 change name "Timothy Fineshooter"
@tim to fr
choice "choice numero uno" {
@tim animate wave
@tim says "super sad"
choice "choice numero uno" {
@tim animate wave
@tim says "super sad"
@@ -8,4 +11,14 @@ choice "choice numero uno" {
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
BIN
View File
Binary file not shown.