Compare commits
2 Commits
d4947a4434
...
5f294cceb2
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f294cceb2 | |||
| 051bfe46e4 |
+561
-560
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
An interactive story telling software.
|
An interactive story telling software.
|
||||||
Read and write people's stories
|
Read and write people's stories
|
||||||
|
For help with making a story, see [this documentation](/docs/MAKING_A_STORY.md)
|
||||||
|
For help with syntax, see [this documentation](/docs/SYNTAX.md)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@@ -9,11 +11,7 @@ This is not really out of development, but to run it, clone the repo, go into /s
|
|||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
### Back-end server -- Rust
|
### File layout
|
||||||
|
|
||||||
The server component of Happening will be written in Rust.<br/>
|
|
||||||
Parses the code and sends it via the API.<br/>
|
|
||||||
File layout:
|
|
||||||
|
|
||||||
- animations/
|
- animations/
|
||||||
- features/
|
- features/
|
||||||
@@ -30,13 +28,24 @@ File layout:
|
|||||||
- scenes/
|
- scenes/
|
||||||
- images/
|
- images/
|
||||||
|
|
||||||
The variables are stored as a hashmap, characters are objects.<br/>
|
### Back-end server -- Rust
|
||||||
|
|
||||||
|
The server component of Happening will be written in Rust.<br/>
|
||||||
|
Parses the code and sends it via the API.<br/>
|
||||||
|
File layout:
|
||||||
|
|
||||||
|
The variables are stored as a hashmap, characters are structs in a hashmap.<br/>
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
Using the network interface, port 20264.<br/>
|
Using the network interface, port 20264.<br/>
|
||||||
|
|
||||||
Characters are sent to the frontend and stored there when the character is created on the frontend.<br/>
|
Characters are sent to the frontend and stored there when the character is created on the frontend.<br/>
|
||||||
|
There are 4 endpoints.
|
||||||
|
|
||||||
|
#### /happening
|
||||||
|
|
||||||
|
GET Returns what is happening on the next cycle:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
@@ -47,35 +56,41 @@ content: String,
|
|||||||
|
|
||||||
character: String,
|
character: String,
|
||||||
|
|
||||||
|
choice: [String,String,String,...]
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
#### /character/\<character name\>
|
||||||
|
GET Returns the struct of the character, ideally this should only be gotten when a new character is made or a character changes a feature.
|
||||||
|
|
||||||
|
#### /choice
|
||||||
|
POST the choice as a JSON formatting number, eg 1 to choose choice index 1, returns "ack" as acknowledgment.
|
||||||
|
|
||||||
|
#### /input
|
||||||
|
POST not yet fully implemented on the syntax side but allows client to send a user input to the server as text. returns "ack" as acknowlegment.
|
||||||
|
|
||||||
### Frontend -- Python
|
### Frontend -- Python
|
||||||
|
|
||||||
Things the frontend can do:
|
Things the frontend should be able to:
|
||||||
|
|
||||||
- Output text
|
- Output text
|
||||||
- Display the character
|
- Display the character
|
||||||
- Move characters around
|
- Move characters around
|
||||||
- Display a choice to the user that gets sent back to the server
|
- Display a choice to the user that gets sent back to the server
|
||||||
|
|
||||||
### Syntax
|
### Files
|
||||||
|
|
||||||
#### Setup
|
For more info on these files see [Making a Story](/docs/MAKING_A_STORY.md)
|
||||||
|
|
||||||
This is done in the about.json file,
|
#### story.ha
|
||||||
|
|
||||||
```
|
This contains the code for the story, syntax for writing the code can be seen in [the syntax section](/docs/SYNTAX.md)
|
||||||
{
|
|
||||||
|
|
||||||
"title": "My Great Story",
|
#### about.json
|
||||||
|
|
||||||
"description": "please read!"
|
This file contains details about your story such as the title and description.
|
||||||
|
|
||||||
}
|
#### characters.json
|
||||||
```
|
|
||||||
|
|
||||||
#### Characters
|
|
||||||
|
|
||||||
See [Character documentation](/docs/CHARACTER.md) for more info
|
See [Character documentation](/docs/CHARACTER.md) for more info
|
||||||
Referencing a character using the @, @NARRATOR is reserved for the Narrator.<br/>
|
Referencing a character using the @, @NARRATOR is reserved for the Narrator.<br/>
|
||||||
@@ -85,72 +100,6 @@ Move a character with @CHARACTER to fr
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> fr means front-right for instance.
|
> fr means front-right for instance.
|
||||||
|
|
||||||
#### Outputs
|
|
||||||
|
|
||||||
```
|
|
||||||
@CHARACTER says "this string
|
|
||||||
is multi-line
|
|
||||||
and ends with a"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Variables
|
|
||||||
|
|
||||||
Variables are referenced with the \$, only integers will be supported.<br/>
|
|
||||||
|
|
||||||
#### Selection
|
|
||||||
|
|
||||||
Condition based:
|
|
||||||
|
|
||||||
```
|
|
||||||
if (condition) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
elif (condition) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Choice based:
|
|
||||||
|
|
||||||
```
|
|
||||||
choice "choice 1" {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
or "choice 2" {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
or "choice 3" {
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Positioning
|
|
||||||
|
|
||||||
```
|
|
||||||
@CHARACTER to position
|
|
||||||
|
|
||||||
PAN to position
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Other
|
|
||||||
|
|
||||||
```
|
|
||||||
label:
|
|
||||||
|
|
||||||
GOTO label
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Ending
|
|
||||||
|
|
||||||
`END` to exit out of the story
|
|
||||||
|
|
||||||
## Implemented stuff
|
## Implemented stuff
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
@@ -160,7 +109,7 @@ GOTO label
|
|||||||
| END | Yes |
|
| END | Yes |
|
||||||
| CHOICE/OR/OR | Yes |
|
| CHOICE/OR/OR | Yes |
|
||||||
| IF/ELSE IF/ELSE | No |
|
| IF/ELSE IF/ELSE | No |
|
||||||
| GOTO | No |
|
| GOTO | Yes |
|
||||||
| PAN | No |
|
| PAN | No |
|
||||||
|
|
||||||
### Character sub-commands
|
### Character sub-commands
|
||||||
@@ -178,6 +127,7 @@ GOTO label
|
|||||||
| ---------- | ----------- |
|
| ---------- | ----------- |
|
||||||
| Variables | No |
|
| Variables | No |
|
||||||
| about.json | No |
|
| about.json | No |
|
||||||
|
| INPUT | Partially |
|
||||||
|
|
||||||
## Error codes
|
## Error codes
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Making a Story
|
||||||
|
|
||||||
|
## Setting up
|
||||||
|
|
||||||
|
You need these three files to make a story:
|
||||||
|
|
||||||
|
- about.json
|
||||||
|
|
||||||
|
- story.ha
|
||||||
|
|
||||||
|
- characters.json
|
||||||
|
|
||||||
|
## Filling out the files
|
||||||
|
|
||||||
|
Inside about.json should be the following content:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"title": "My Great Story",
|
||||||
|
"description": "please read!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the title and description to whatever you would like.<br/>
|
||||||
|
Inside story.ha should be the code for your story, see [the documentation on coding](/docs/SYNTAX.md) for help with this.<br/>
|
||||||
|
Finally, inside characters.json should be a JSON formatted object containing all your characters. See [the documentation on characters](/docs/CHARACTERS.md) for help with this file.<br/>
|
||||||
|
|
||||||
|
## Making a story file
|
||||||
|
|
||||||
|
A story file is just a zip file of these files, so zip up story.ha, characters.json and about.json into a zip file (make sure these are in the root of the zip file).
|
||||||
|
|
||||||
|
## Playing your story
|
||||||
|
|
||||||
|
To play your story, simply run cargo run \<your zip file\>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Syntax
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
This is done in the about.json file,
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
|
||||||
|
"title": "My Great Story",
|
||||||
|
|
||||||
|
"description": "please read!"
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Characters
|
||||||
|
|
||||||
|
See [Character documentation](/docs/CHARACTER.md) for more info
|
||||||
|
Referencing a character using the @, @NARRATOR is reserved for the Narrator.<br/>
|
||||||
|
Customisation is done with @CHARACTER change \<feature\> into \<feature name\><br/>
|
||||||
|
Move a character with @CHARACTER to fr
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> fr means front-right for instance.
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
```
|
||||||
|
@CHARACTER says "this string
|
||||||
|
is multi-line
|
||||||
|
and ends with a"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
Variables are referenced with the \$, only integers will be supported.<br/>
|
||||||
|
|
||||||
|
## Selection
|
||||||
|
|
||||||
|
Condition based:
|
||||||
|
|
||||||
|
```
|
||||||
|
if (condition) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
elif (condition) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Choice based:
|
||||||
|
|
||||||
|
```
|
||||||
|
choice "choice 1" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
or "choice 2" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
or "choice 3" {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Positioning
|
||||||
|
|
||||||
|
```
|
||||||
|
@CHARACTER to position
|
||||||
|
|
||||||
|
PAN to position
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
```
|
||||||
|
label:
|
||||||
|
|
||||||
|
GOTO label
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ending
|
||||||
|
|
||||||
|
`END` to exit out of the story
|
||||||
+12
-2
@@ -17,7 +17,9 @@ pub struct Character
|
|||||||
{
|
{
|
||||||
name: String,
|
name: String,
|
||||||
gender: String,
|
gender: String,
|
||||||
eye_color: String,
|
eye_color: Colour,
|
||||||
|
hair_color: Colour,
|
||||||
|
skin_color: Colour,
|
||||||
pronoun_subject: String,
|
pronoun_subject: String,
|
||||||
pronoun_object: String,
|
pronoun_object: String,
|
||||||
pronoun_deppos: String,
|
pronoun_deppos: String,
|
||||||
@@ -28,7 +30,6 @@ pub struct Character
|
|||||||
torso_shape: String,
|
torso_shape: String,
|
||||||
arm_shape: String,
|
arm_shape: String,
|
||||||
leg_shape: String,
|
leg_shape: String,
|
||||||
hair_color: String, // TODO RGB enum
|
|
||||||
clothing: Clothing,
|
clothing: Clothing,
|
||||||
}
|
}
|
||||||
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
||||||
@@ -39,6 +40,15 @@ pub struct Clothing
|
|||||||
bottom: String,
|
bottom: String,
|
||||||
shoes: String,
|
shoes: String,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug,Deserialize,Serialize,Clone,Default)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct Colour
|
||||||
|
{
|
||||||
|
red: u8,
|
||||||
|
green: u8,
|
||||||
|
blue: u8,
|
||||||
|
}
|
||||||
|
|
||||||
impl Character {
|
impl Character {
|
||||||
// Big ass ugly match case
|
// Big ass ugly match case
|
||||||
pub fn set_field(&mut self, field: &str, value: &str) -> Result<(), String> {
|
pub fn set_field(&mut self, field: &str, value: &str) -> Result<(), String> {
|
||||||
|
|||||||
+5
-5
@@ -51,7 +51,7 @@ async fn main()
|
|||||||
error!("No filename specified");
|
error!("No filename specified");
|
||||||
exit(10);
|
exit(10);
|
||||||
});
|
});
|
||||||
let file = File::open(format!("../stories/{file_name}")) // Get the file
|
let file = File::open(file_name) // Get the file
|
||||||
.unwrap_or_exit("Failed to read file.", 11);
|
.unwrap_or_exit("Failed to read file.", 11);
|
||||||
let mut archive = ZipArchive::new(file) // Open the archive
|
let mut archive = ZipArchive::new(file) // Open the archive
|
||||||
.unwrap_or_exit("Failed to open archive", 12);
|
.unwrap_or_exit("Failed to open archive", 12);
|
||||||
@@ -93,7 +93,7 @@ async fn main()
|
|||||||
|
|
||||||
// setup the parsing stuff //
|
// setup the parsing stuff //
|
||||||
// Read the file and split it into tokens
|
// Read the file and split it into tokens
|
||||||
// file read from a zip file /story.ha
|
// file read relative to current directory
|
||||||
let mut story_file = archive.by_name("story.ha")
|
let mut story_file = archive.by_name("story.ha")
|
||||||
.unwrap_or_exit("Unable to read story file", 14);
|
.unwrap_or_exit("Unable to read story file", 14);
|
||||||
let mut file_contents = String::new();
|
let mut file_contents = String::new();
|
||||||
@@ -106,18 +106,18 @@ async fn main()
|
|||||||
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")
|
if ! space_seperated.contains(&"END") // TODO remove
|
||||||
{
|
{
|
||||||
warn!("No END statement, story may exit unexpectedly");
|
warn!("No END statement, story may exit unexpectedly");
|
||||||
}
|
}
|
||||||
let (tokens, labels,_) = tokenise::tokenise(&space_seperated, 0)
|
let (tokens, labels,_) = tokenise::tokenise(&space_seperated, 0)
|
||||||
.unwrap_or_exit("Unable to tokenise data", 15);
|
.unwrap_or_exit("Unable to tokenise data", 15);
|
||||||
debug!("{tokens:?}");
|
debug!("{tokens:?}\n{labels:?}");
|
||||||
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
|
||||||
info!("DSL parsing begun");
|
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
|
// Exit with error or success
|
||||||
Ok(()) =>
|
Ok(()) =>
|
||||||
|
|||||||
+111
-69
@@ -21,6 +21,7 @@ mod character_parse;
|
|||||||
// Returns success or an error string
|
// Returns success or an error string
|
||||||
pub fn token_parse(
|
pub fn token_parse(
|
||||||
tokens: &[tokenise::Token],
|
tokens: &[tokenise::Token],
|
||||||
|
labels: &HashMap<String,usize>,
|
||||||
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)>,
|
||||||
@@ -36,85 +37,126 @@ pub fn token_parse(
|
|||||||
// Run an infinite loop
|
// Run an infinite loop
|
||||||
'parse_loop: loop
|
'parse_loop: loop
|
||||||
{
|
{
|
||||||
|
debug!("Reading {index}");
|
||||||
// Get the next token
|
// 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)) => // TODO add support for narrator
|
||||||
|
{
|
||||||
|
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}");
|
debug!("{index}: {token}");
|
||||||
// The instructions are related to characters
|
// 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();
|
"end" =>
|
||||||
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,
|
info!("END command, exiting");
|
||||||
Err((err,increment)) =>
|
return Ok(()) // quit successfully
|
||||||
{
|
},
|
||||||
warn!("{err}");
|
"choice" =>
|
||||||
increment
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Miscelleneous instructions
|
|
||||||
else
|
|
||||||
{
|
|
||||||
match token.to_lowercase().as_str()
|
|
||||||
{
|
{
|
||||||
"end" =>
|
let mut next_token: String = token;
|
||||||
|
let mut choices: Vec<String> = Vec::new();
|
||||||
|
let mut choice_indeces: Vec<usize> = Vec::new();
|
||||||
|
while next_token == "or" || next_token == "choice"
|
||||||
{
|
{
|
||||||
info!("END command, exiting");
|
index += 1;
|
||||||
return Ok(()) // quit successfully
|
choices.push
|
||||||
},
|
(
|
||||||
"choice" =>
|
tokenise::get_string_token(tokens, index)?
|
||||||
{
|
);
|
||||||
debug!("Doing CHOICE");
|
index += 1;
|
||||||
let mut next_token = token;
|
choice_indeces.push(index+1);
|
||||||
let mut choices: Vec<String> = Vec::new();
|
index = match tokenise::get_closing_index(tokens,index)
|
||||||
let mut choice_indeces: Vec<usize> = Vec::new();
|
|
||||||
while next_token == "or" || next_token == "choice"
|
|
||||||
{
|
{
|
||||||
index += 1;
|
Ok(new_index) => new_index + 1,
|
||||||
choices.push
|
Err(_) => break,
|
||||||
(
|
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let object = tokenise::get_object_token(tokens, choice)?; // TODO make more efficient
|
next_token = match tokenise::get_keyword_token(tokens, index)
|
||||||
debug!("{object:?}");
|
{
|
||||||
// Don't propogate exit error
|
Ok(string) => string,
|
||||||
let _ = token_parse(object, characters, data_to_send, rx);
|
Err(_) => break,
|
||||||
continue 'parse_loop
|
}
|
||||||
},
|
|
||||||
"or" =>
|
|
||||||
{
|
|
||||||
info!("OR command, jumping over");
|
|
||||||
index += 2;
|
|
||||||
continue
|
|
||||||
},
|
|
||||||
_ =>
|
|
||||||
{
|
|
||||||
warn!("Invalid command: {token}");
|
|
||||||
}
|
}
|
||||||
|
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()
|
if rx.recv().is_err()
|
||||||
|
|||||||
@@ -28,12 +28,9 @@ 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 = match tokens.get(sum_index) {
|
let keyword = tokenise::get_keyword_token(tokens, sum_index)
|
||||||
Some(tokenise::Token::String(s)) => s.clone(),
|
.map_err(|err| (err, sum_index))?;
|
||||||
Some(_) => return Err(("Unexpected token".to_string(), sum_index + 1)),
|
match keyword.to_lowercase().as_str()
|
||||||
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
|
// The character is saying something, so grab the text and pass it
|
||||||
// to the client
|
// to the client
|
||||||
@@ -51,7 +48,7 @@ pub fn character_parse
|
|||||||
"change" =>
|
"change" =>
|
||||||
{
|
{
|
||||||
sum_index += 1;
|
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))?;
|
.map_err(|err| (err, index))?;
|
||||||
sum_index += 1;
|
sum_index += 1;
|
||||||
let string = tokenise::get_string_token(tokens, sum_index)
|
let string = tokenise::get_string_token(tokens, sum_index)
|
||||||
@@ -69,13 +66,13 @@ pub fn character_parse
|
|||||||
"to"|"animate" =>
|
"to"|"animate" =>
|
||||||
{
|
{
|
||||||
sum_index += 1;
|
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))?;
|
.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
|
// Catch all condition, if the instruction is unrecognised as a
|
||||||
// character command
|
// character command
|
||||||
_ => return Err((format!("Invalid command: {token}"),sum_index)),
|
_ => return Err((format!("Invalid command: {keyword}"),sum_index)),
|
||||||
}
|
}
|
||||||
sum_index += 1;
|
sum_index += 1;
|
||||||
Ok(sum_index)
|
Ok(sum_index)
|
||||||
|
|||||||
+59
-23
@@ -1,13 +1,23 @@
|
|||||||
use crate::
|
use crate::
|
||||||
{
|
{
|
||||||
exit,
|
exit,
|
||||||
|
HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Token
|
pub enum Token
|
||||||
{
|
{
|
||||||
String(String),
|
String(String),
|
||||||
Object(Vec<Self>),
|
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)
|
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()),
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_keyword_token(tokens: &[Token], index: usize)
|
||||||
pub fn get_object_token(tokens: &[Token], index: usize)
|
-> Result<String, String>
|
||||||
-> Result<&Vec<Token>, String>
|
|
||||||
{
|
{
|
||||||
match tokens.get(index) {
|
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<usize, String>
|
||||||
|
{
|
||||||
|
match tokens.get(index) {
|
||||||
|
Some(Token::Bracket((_, index))) => Ok(*index), // TODO check for closing
|
||||||
Some(_) => Err("Unexpected token".to_string()),
|
Some(_) => Err("Unexpected token".to_string()),
|
||||||
None => Err("File unexpectedly reached termination point".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)
|
// It can contain sub-objects (using recursive calls)
|
||||||
// TODO check for END
|
// 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>,HashMap<String,usize>,usize),String>
|
||||||
{
|
{
|
||||||
let mut tokenised_data: Vec<Token> = Vec::new();
|
let mut tokenised_data: Vec<Token> = Vec::new();
|
||||||
let mut labels: Vec<(String,usize)> = Vec::new();
|
let mut labels: HashMap<String, usize> = HashMap::new();
|
||||||
|
let mut bracket_stack: Vec<usize> = Vec::new();
|
||||||
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 object: Vec<Token>;
|
// Characters
|
||||||
if item.starts_with('"') // TODO support '
|
if item.starts_with('@')
|
||||||
{
|
{
|
||||||
(index, item) = match tokenise_string(space_seperated, index)
|
let mut chars = item.chars();
|
||||||
{
|
chars.next();
|
||||||
Some((index,item)) => (index,item),
|
tokenised_data.push(Token::Character(chars.as_str().to_string()));
|
||||||
None => exit(1),
|
}
|
||||||
};
|
// 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));
|
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 == "{"
|
else if item == "{"
|
||||||
{
|
{
|
||||||
(object, labels, index) = match tokenise(space_seperated, index+1) // !WARNING! recursive call
|
bracket_stack.push(tokenised_data.len());
|
||||||
{
|
tokenised_data.push(Token::Bracket((Bracket::Opening,0))); // TODO fix no closing brace edge case
|
||||||
Ok((object,labels,index)) => (object,labels,index),
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
tokenised_data.push(Token::Object(object));
|
|
||||||
}
|
}
|
||||||
else if item == "}"
|
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;
|
index += 1;
|
||||||
}
|
}
|
||||||
Ok((tokenised_data, labels, 0))
|
Ok((tokenised_data, labels, 0))
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
"tim": {
|
"tim": {
|
||||||
"name": "Timothy Sharpshooter",
|
"name": "Timothy Sharpshooter",
|
||||||
"gender": "Male",
|
"gender": "Male",
|
||||||
"skin_color": "",
|
"skin_color": [0,0,0],
|
||||||
"eye_color": "",
|
"eye_color": [0,0,0],
|
||||||
"hair_color": "",
|
"hair_color": [0,0,0],
|
||||||
"pronoun_subject": "He",
|
"pronoun_subject": "He",
|
||||||
"pronoun_object": "Him",
|
"pronoun_object": "Him",
|
||||||
"pronoun_deppos": "His",
|
"pronoun_deppos": "His",
|
||||||
|
|||||||
+2
-13
@@ -1,25 +1,14 @@
|
|||||||
@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 change boop "Timothy Fineshooter"
|
label:
|
||||||
@tim to fr
|
|
||||||
choice "choice numero uno" {
|
choice "choice numero uno" {
|
||||||
@tim animate wave
|
|
||||||
@tim says "super sad"
|
@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" {
|
or "choice numero duo" {
|
||||||
@tim says "super unsad"
|
@tim says "super unsad"
|
||||||
}
|
}
|
||||||
or "choice numero tres" {
|
or "choice numero tres" {
|
||||||
@tim says "hola mi amigos"
|
@tim says "hola mi amigos"
|
||||||
|
goto label
|
||||||
}
|
}
|
||||||
END
|
END
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user