Compare commits

..

3 Commits

Author SHA1 Message Date
deadvey 556185e095 added some TODO listing in the README and removed junk file 2026-05-19 19:38:25 +01:00
deadvey 9255c1d7fa Fixed some clippy lints 2026-05-19 19:34:56 +01:00
deadvey ee34493895 Changed the data_to_send to be a stack so many lines of code can
be pre-processed before the user interacts.
When the /happening api is called it just dequeues the front item
2026-05-19 19:23:19 +01:00
9 changed files with 199 additions and 375 deletions
+11 -28
View File
@@ -102,34 +102,17 @@ Move a character with @CHARACTER to fr
> [!NOTE]
> fr means front-right for instance.
## Implemented stuff
### Commands
| Command | Implemented |
| --------------- | ----------- |
| END | Yes |
| CHOICE/OR/OR | Yes |
| IF/ELSE IF/ELSE | No |
| GOTO | Yes |
| PAN | No |
### Character sub-commands
| Character Command | Implemented |
| ----------------- | ----------- |
| SAYS | Yes |
| CHANGE | Yes |
| TO | Yes |
| ANIMATE | Yes |
### Other Features
| Feature | Implemented |
| ---------- | ----------- |
| Variables | No |
| about.json | No |
| INPUT | Partially |
## TODO
- Variables
- /about.json
- If/Else if/Else
- PAN
- INPUT
- tokeniser check for lack of END
- Fix no closing brace edge case
- Support single quotes for strings
- Brace index getter check for closing
- Proper Error messages centralised???
## Error codes
+18 -16
View File
@@ -1,6 +1,12 @@
import requests
import os
import time
import sys
debug = False
try:
if sys.argv[1] == "debug": debug = True
except:
debug = False
# Loop and get new api
def main():
@@ -9,22 +15,18 @@ def main():
while True:
try:
response = api_get()
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
if debug: 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)
except:
print("Server not up or cannot be reached")
input() # Enter to go to next loop (testing)
-184
View File
@@ -1,184 +0,0 @@
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))
}
+30 -29
View File
@@ -9,6 +9,7 @@ use crate::
HashMap,
Arc,
Mutex,
VecDeque,
config,
mpsc::Sender,
info,
@@ -18,9 +19,8 @@ use crate::
Deserialize,
};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct DataToSend {
pub id: u32,
pub action_type: String,
pub content: String,
pub character: String,
@@ -32,31 +32,30 @@ pub struct DataToSend {
// tx to allow the program executor to move onto the next bit of code
pub async fn api_process
(
data_to_send: Arc<Mutex<DataToSend>>,
happening_queue: Arc<Mutex<VecDeque<DataToSend>>>,
characters: Arc<Mutex<HashMap::<String,character::Character>>>,
tx: Sender<(bool, usize, String)>,
tx: Sender<(usize, String)>,
)
{
// This data must be passed through to the api route in order to be used
let data_filter = warp::any().map(move || Arc::clone(&data_to_send));
let happening_queue_filter = warp::any().map(move || Arc::clone(&happening_queue));
let characters_filter = warp::any().map(move || Arc::clone(&characters));
let tx_filter = warp::any().map(move || tx.clone());
let tx_filter2 = tx_filter.clone();
let tx_filter3 = tx_filter.clone();
let tx_filter1 = warp::any().map(move || tx.clone());
let tx_filter2 = tx_filter1.clone();
info!("Running server");
// The server route is loaded at address:port/happening
let main = warp::path("happening")
.and(warp::get())
.and(data_filter)
.and(tx_filter)
.and(happening_queue_filter)
// Perform this code on a GET request
.map(|state: Arc<Mutex<DataToSend>>, tx_handle: Sender<(bool,usize,String)>|
.map(|queue: Arc<Mutex<VecDeque<DataToSend>>>|
{
//debug!("GET: {state:?}");
let reply = state.as_ref();
let _ = tx_handle.send((true,0,String::new()));
let mut queue = queue.lock().unwrap_or_exit("Queue Mutex was poisoned", 2);
let reply = queue.pop_front().unwrap_or_default();
drop(queue);
warp::reply::json(&reply) // Send the reply data (data_to_send formatted as JSON)
}).boxed();
let characters = warp::path("character")
@@ -90,20 +89,20 @@ pub async fn api_process
let choice = warp::path("choice")
.and(warp::post())
.and(warp::body::json())
.and(tx_filter2)
.map(|index: usize, tx_handle: Sender<(bool,usize,String)>| {
.and(tx_filter1)
.map(|index: usize, tx_handle: Sender<(usize,String)>| {
debug!("Choice: {index}");
let _ = tx_handle.send((true,index,String::new()));
let _ = tx_handle.send((index,String::new()));
let reply = "ack";
warp::reply::json(&reply)
}).boxed();
let input = warp::path("input")
.and(warp::post())
.and(warp::body::json())
.and(tx_filter3)
.map(|input: String, tx_handle: Sender<(bool, usize, String)>|
.and(tx_filter2)
.map(|input: String, tx_handle: Sender<(usize, String)>|
{
let _ = tx_handle.send((true,0,input));
let _ = tx_handle.send((0,input));
let reply = "ack";
warp::reply::json(&reply)
}).boxed();
@@ -117,20 +116,22 @@ pub async fn api_process
// On fail, quit safely
// If successful, return nothing
pub fn modify_data
pub fn modify_data // TODO rename
(
data_to_send: &Arc<Mutex<DataToSend>>,
happening_queue: &Arc<Mutex<VecDeque<DataToSend>>>,
action_type: String,
content: String,
character_name: String,
character: String,
choices: Vec<String>,
)
{
let mut data = data_to_send.lock().unwrap_or_exit("Data to send Mutex was poisoned",2);
data.id += 1;
data.action_type = action_type;
data.content = content;
data.character = character_name;
data.choices = choices;
drop(data);
let mut queue = happening_queue.lock().unwrap_or_exit("Data to send Mutex was poisoned",2);
let new_data = DataToSend {
action_type,
content,
character,
choices,
};
queue.push_back(new_data);
drop(queue);
}
+19 -14
View File
@@ -11,7 +11,11 @@ use std::
env::args,
fs::File,
io::Read,
collections::HashMap,
collections::
{
HashMap,
VecDeque,
},
sync::{Arc, Mutex, mpsc},
};
use log::
@@ -70,25 +74,26 @@ 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(), // TODO send title and description
character: String::new(),
choices: vec![],
}));
let happening_stack = Arc::new(Mutex::new(
VecDeque::from([api::DataToSend{
action_type: "begin".to_owned(),
content: String::new(), // TODO send title and description
character: String::new(),
choices: vec![],
}])
));
// setup the api stuff //
// Make clones of the data Arc for the two processes
let data_clone1 = Arc::clone(&data_to_send);
//let data_clone1 = Arc::clone(&data_to_send);
let happening_stack1 = Arc::clone(&happening_stack);
let characters_clone1 = Arc::clone(&characters);
let tx_clone = tx;
// Spawn a thread for warp api server
tokio::spawn(
async move {
api::api_process(data_clone1, characters_clone1, tx_clone).await;
api::api_process(happening_stack1, characters_clone1, tx_clone).await;
});
// setup the parsing stuff //
@@ -106,16 +111,16 @@ async fn main()
let (tokens, labels) = tokenise::tokenise(&file_contents)
.unwrap_or_exit("Unable to tokenise data", 15);
debug!("{tokens:?}\n{labels:?}");
let data_clone2 = Arc::clone(&data_to_send);
let characters_clone2 = Arc::clone(&characters);
let happening_stack2 = Arc::clone(&happening_stack);
// Run the parsing process for the DSL
info!("DSL parsing begun");
match parsing::token_parse(&tokens, &labels, &characters_clone2, &data_clone2, &rx)
match parsing::token_parse(&tokens, &labels, &characters_clone2, &happening_stack2, &rx)
{
// Exit with error or success
Ok(()) =>
{
api::modify_data(&data_to_send, "end".to_string(), String::new(), String::new(), vec![]);
api::modify_data(&happening_stack, "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");
+14 -100
View File
@@ -10,12 +10,14 @@ use crate::
mpsc::Receiver,
Arc,
Mutex,
VecDeque,
info,
debug,
warn,
};
mod character_parse;
mod keyword_parse;
// Parse the tokens in a file
// Returns success or an error string
@@ -23,25 +25,24 @@ pub fn token_parse(
tokens: &[tokenise::Token],
labels: &HashMap<String,usize>,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>,
rx: &Receiver<(bool,usize,String)>,
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
rx: &Receiver<(usize,String)>,
) -> Result<(),String>
{
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
loop
{
debug!("Reading {index}");
// Get the next token
let token: String = match tokens.get(index)
match tokens.get(index)
{
Some(tokenise::Token::Keyword(s)) => s.clone(),
Some(tokenise::Token::Keyword(token)) =>
{
if token.to_lowercase().as_str() == "end" { return Ok(()); }
index = keyword_parse::keyword_parse(tokens, token, index, happening_queue, labels, rx)?;
},
// Ignore closing braces and jump over opening brace blocks
Some(tokenise::Token::Bracket((bracket,new_index))) =>
{
@@ -51,12 +52,11 @@ pub fn token_parse(
warn!("Unexpected brace block, jumping over...");
index = new_index + 1;
}
continue 'parse_loop
},
// Handle a character
Some(tokenise::Token::Character(character_name)) => // TODO add support for narrator
Some(tokenise::Token::Character(character_name)) =>
{
index = match character_parse::character_parse(index+1,tokens,character_name.clone(),characters,data_to_send)
index = match character_parse::character_parse(index+1,tokens,character_name.clone(),characters,happening_queue)
{
Ok(increment) => increment,
Err((err,increment)) =>
@@ -65,101 +65,15 @@ pub fn token_parse(
increment
},
};
if rx.recv().is_err() { warn!("Some issue with api"); }
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
match token.to_lowercase().as_str()
{
"end" =>
{
info!("END command, exiting");
return Ok(()) // quit successfully
},
"choice" =>
{
let choice_indeces = choice_parse(tokens, index, data_to_send)?;
debug!("{choice_indeces:?}");
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 = if let Some(label_index) = labels.get(&label) { *label_index }
else
{
warn!("Label {label} does not exist");
index + 1
};
debug!("Jumping to {index}");
continue 'parse_loop
}
_ =>
{
warn!("Invalid command: {token}");
index += 1;
}
}
if rx.recv().is_err() { warn!("Some issue with api"); }
// The instructions are related to characters
}
}
fn choice_parse(tokens: &[tokenise::Token], mut index: usize, data_to_send: &Arc<Mutex<api::DataToSend>>,)
-> Result<Vec<usize>, String>
{
let mut next_token: String = "or".to_string();
let mut choices: Vec<String> = Vec::new();
let mut choice_indeces: Vec<usize> = Vec::new();
while next_token == "or"
{
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)
{
Ok(new_index) => new_index + 1,
Err(_) => break,
};
next_token = match tokenise::get_keyword_token(tokens, index)
{
Ok(string) => string,
Err(_) => break,
}
};
api::modify_data(data_to_send, "choice".to_string(), String::new(), String::new(), choices);
Ok(choice_indeces)
}
+5 -4
View File
@@ -9,6 +9,7 @@ use crate::
Mutex,
Arc,
HashMap,
VecDeque,
info,
warn,
debug,
@@ -23,7 +24,7 @@ pub fn character_parse
tokens: &[tokenise::Token],
character_name: String,
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
data_to_send: &Arc<Mutex<api::DataToSend>>,
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
) -> Result<usize,(String,usize)>
{
let mut sum_index: usize = index;
@@ -47,7 +48,7 @@ pub fn character_parse
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, character_name, vec![]);
api::modify_data(happening_queue, "output".to_string(), output, 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
@@ -65,7 +66,7 @@ pub fn character_parse
&& 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![]);
api::modify_data(happening_queue, "change".to_string(), String::new(), character_name, vec![]);
},
// These two are mainly just actions performed by the frontend client, so just tell the client to move/animate
// the character and not much other processing needed on the serverside
@@ -74,7 +75,7 @@ pub fn character_parse
sum_index += 1;
let content = tokenise::get_keyword_token(tokens, sum_index)
.map_err(|err| (err, index))?;
api::modify_data(data_to_send, keyword.to_lowercase(), content, character_name, vec![]);
api::modify_data(happening_queue, keyword.to_lowercase(), content, character_name, vec![]);
},
// Catch all condition, if the instruction is unrecognised as a
// character command
+101
View File
@@ -0,0 +1,101 @@
use crate::
{
tokenise,
api,
HashMap,
Arc,
Mutex,
VecDeque,
warn,
debug,
info,
mpsc::Receiver,
};
pub fn keyword_parse(
tokens: &[tokenise::Token],
token: &str,
mut index: usize,
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
labels: &HashMap<String, usize>,
rx: &Receiver<(usize,String)>,
)
-> Result<usize, String>
{
match token.to_lowercase().as_str()
{
"choice" =>
{
let choice_indeces = choice_parse(tokens, index, happening_queue)?;
debug!("{choice_indeces:?}");
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;
},
"or" =>
{
info!("OR command, jumping over");
index += 2;
let new_index = tokenise::get_closing_index(tokens, index)?;
index = new_index;
},
// 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 = if let Some(label_index) = labels.get(&label) { *label_index }
else
{
warn!("Label {label} does not exist");
index + 1
};
debug!("Jumping to {index}");
}
_ =>
{
warn!("Invalid command: {token}");
index += 1;
}
}
Ok(index)
}
fn choice_parse(tokens: &[tokenise::Token], mut index: usize, happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,)
-> Result<Vec<usize>, String>
{
let mut next_token: String = "or".to_string();
let mut choices: Vec<String> = Vec::new();
let mut choice_indeces: Vec<usize> = Vec::new();
while next_token == "or"
{
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)
{
Ok(new_index) => new_index + 1,
Err(_) => break,
};
next_token = match tokenise::get_keyword_token(tokens, index)
{
Ok(string) => string,
Err(_) => break,
}
};
api::modify_data(happening_queue, "choice".to_string(), String::new(), String::new(), choices);
Ok(choice_indeces)
}
+1
View File
@@ -8,6 +8,7 @@ pub enum Token
{
String(String),
Keyword(String), // Keywords aren't checked for validity in this stage
#[allow(dead_code)] // This is unused rn, but am going to add it later
Identifier(String),
Bracket((Bracket,usize)), // Stores the index of the matching deliminator
Character(String),