Compare commits
8 Commits
93ca1ea34a
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ae74c07948 | |||
| 148fb73f7f | |||
| cc3eca857d | |||
| f6a95f76bd | |||
| 59f32e975b | |||
| 4f7abb5f19 | |||
| 20369ef838 | |||
| 21bf659718 |
@@ -7,7 +7,7 @@ For help with syntax, see [this documentation](/docs/SYNTAX.md)
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
This is not really out of development, but to run it, clone the repo, go into /server/ and use cargo run story.zip to run a file called story.zip, relative to your current location.
|
This is not really out of development, but to run it, clone the repo, go into /server/ and use cargo run story.zip to run a file called story.zip, absolute or relative file location.
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
@@ -103,14 +103,11 @@ Move a character with @CHARACTER to fr
|
|||||||
> fr means front-right for instance.
|
> fr means front-right for instance.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- Variables
|
|
||||||
- /about.json
|
- /about.json
|
||||||
- If/Else if/Else
|
|
||||||
- PAN
|
|
||||||
- INPUT
|
|
||||||
- tokeniser check for lack of END
|
- tokeniser check for lack of END
|
||||||
- Fix no closing brace edge case
|
- Fix no closing brace edge case
|
||||||
- Support single quotes for strings
|
- Support single quotes for strings
|
||||||
|
- backslashes in strings
|
||||||
- Brace index getter check for closing
|
- Brace index getter check for closing
|
||||||
- Proper Error messages centralised???
|
- Proper Error messages centralised???
|
||||||
|
|
||||||
|
|||||||
Generated
+92
@@ -206,6 +206,16 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@@ -379,11 +389,13 @@ dependencies = [
|
|||||||
name = "happening-client"
|
name = "happening-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"home",
|
||||||
"macroquad",
|
"macroquad",
|
||||||
"phf",
|
"phf",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -403,6 +415,15 @@ version = "0.17.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -724,6 +745,15 @@ version = "0.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.29"
|
||||||
@@ -846,6 +876,29 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -1047,6 +1100,15 @@ dependencies = [
|
|||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.3"
|
version = "0.13.3"
|
||||||
@@ -1217,6 +1279,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.7.0"
|
version = "3.7.0"
|
||||||
@@ -1295,6 +1363,16 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -1463,11 +1541,25 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.4"
|
version = "0.26.4"
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
home = "0.5.12"
|
||||||
macroquad = "0.4.15"
|
macroquad = "0.4.15"
|
||||||
phf = {version="0.13.1",features=["macros"]}
|
phf = {version="0.13.1",features=["macros"]}
|
||||||
reqwest = {version="0.13.3",features=["blocking","json"]}
|
reqwest = {version="0.13.3",features=["blocking","json"]}
|
||||||
serde = {version="1.0.228",features=["derive"]}
|
serde = {version="1.0.228",features=["derive"]}
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
tokio = {version="1.52.3",features=["full"]}
|
||||||
|
|||||||
+89
-21
@@ -1,14 +1,24 @@
|
|||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
use reqwest::*;
|
use reqwest::*;
|
||||||
use reqwest::blocking;
|
use reqwest::blocking;
|
||||||
use std::collections::HashMap;
|
|
||||||
use phf::phf_map;
|
use phf::phf_map;
|
||||||
use std::{thread, time::Duration};
|
use std::
|
||||||
|
{
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
collections::
|
||||||
|
{
|
||||||
|
HashMap,
|
||||||
|
HashSet,
|
||||||
|
},
|
||||||
|
process::exit,
|
||||||
|
};
|
||||||
use serde::
|
use serde::
|
||||||
{
|
{
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
};
|
};
|
||||||
|
use home::home_dir;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
@@ -69,45 +79,92 @@ static POSITIONS: phf::Map<&'static str, [f32;2]> = phf_map! [
|
|||||||
async fn main()
|
async fn main()
|
||||||
{
|
{
|
||||||
let mut characters: HashMap<String, (Character, Texture2D)> = HashMap::new();
|
let mut characters: HashMap<String, (Character, Texture2D)> = HashMap::new();
|
||||||
let mut textures: Vec<(Texture2D, f32, f32, Color)> = Vec::new();
|
characters.insert("narrator".to_string(), (Character { name: "Narrator".to_string(), ..Default::default() }, Texture2D::empty()));
|
||||||
|
let mut textures: HashMap<String,(Texture2D, f32, f32, Color)> = HashMap::new();
|
||||||
|
let mut text: String = String::new();
|
||||||
|
let mut checking_for_choice: bool = false;
|
||||||
|
let mut data: Data = next_happening(); // First one should be begin
|
||||||
loop
|
loop
|
||||||
{
|
{
|
||||||
clear_background(RED);
|
clear_background(RED);
|
||||||
// Get the next character
|
for (name, (texture, x, y, colour)) in &textures
|
||||||
let data = next_happening();
|
|
||||||
let character_name: &str = data.character.as_str();
|
|
||||||
// Add the character to the HashMap if it's not already
|
|
||||||
if character_name != "" && !characters.contains_key(character_name)
|
|
||||||
{
|
{
|
||||||
let new_character = get_character(character_name).await;
|
draw_texture(&texture, *x, *y, *colour);
|
||||||
characters.insert(character_name.to_string(), new_character);
|
}
|
||||||
|
draw_multiline_text(&text, 50.0,30.0,40.0,None,WHITE);
|
||||||
|
if !is_any_key_down()
|
||||||
|
{
|
||||||
|
next_frame().await;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let keys: HashSet<KeyCode> = get_keys_pressed();
|
||||||
|
if checking_for_choice
|
||||||
|
{
|
||||||
|
for key in &keys
|
||||||
|
{
|
||||||
|
let keycode = *key as u16;
|
||||||
|
let length: u16 = data.choices.len() as u16;
|
||||||
|
println!("key: {key:?} {keycode}");
|
||||||
|
if keycode > 48 && keycode <= length+48
|
||||||
|
{
|
||||||
|
checking_for_choice = false;
|
||||||
|
println!("Sending POST: {}",keycode-49);
|
||||||
|
let value = keycode - 49;
|
||||||
|
send_choice(value);
|
||||||
|
}
|
||||||
|
else { continue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the next character
|
||||||
|
data = next_happening();
|
||||||
|
let character_name: String = data.character.to_lowercase();
|
||||||
|
// Add the character to the HashMap if it's not already
|
||||||
|
if character_name != "" && !characters.contains_key(&character_name)
|
||||||
|
{
|
||||||
|
println!("Fetching {character_name}");
|
||||||
|
let new_character = get_character(&character_name).await;
|
||||||
|
characters.insert(character_name.clone(), new_character);
|
||||||
}
|
}
|
||||||
// Matchbox for all the commands
|
// Matchbox for all the commands
|
||||||
match data.action_type.to_lowercase().as_str()
|
match data.action_type.to_lowercase().as_str()
|
||||||
{
|
{
|
||||||
|
"choice" =>
|
||||||
|
{
|
||||||
|
for (index, choice) in data.choices.iter().enumerate()
|
||||||
|
{
|
||||||
|
text += format!("\n{}. {}",index+1, choice).as_str();
|
||||||
|
}
|
||||||
|
checking_for_choice = true;
|
||||||
|
},
|
||||||
"output" =>
|
"output" =>
|
||||||
{
|
{
|
||||||
println!("SAYING");
|
println!("SAYING");
|
||||||
draw_text(characters[character_name].0.name.clone(), 50.0,20.0,40.0,WHITE);
|
text = format!("{}: {}", characters[&character_name].0.name.clone(),wrap_text(&data.content).as_str());
|
||||||
draw_text(data.content.as_str(), 50.0,40.0,30.0,WHITE);
|
|
||||||
},
|
},
|
||||||
"to" =>
|
"to" =>
|
||||||
{
|
{
|
||||||
let position = POSITIONS.get(&data.content).cloned().unwrap();
|
let position = POSITIONS.get(&data.content).cloned().unwrap();
|
||||||
let texture = &characters[character_name].1;
|
let texture = &characters[&character_name].1;
|
||||||
textures.push((texture.clone(), position[0], position[1], WHITE)); // Heavy
|
textures.insert(character_name.clone(),(texture.clone(), position[0], position[1], WHITE)); // Heavy
|
||||||
}
|
}
|
||||||
_ => println!("Unknown action"),
|
"begin" => (),
|
||||||
|
"end" => exit(0),
|
||||||
|
_ => println!("Unknown action, {}", data.action_type),
|
||||||
}
|
}
|
||||||
for (texture, x, y, colour) in &textures
|
|
||||||
{
|
|
||||||
draw_texture(&texture, *x, *y, *colour);
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(1000));
|
|
||||||
next_frame().await;
|
next_frame().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wrap_text(text: &str) -> String
|
||||||
|
{
|
||||||
|
text.chars()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.chunks(30)
|
||||||
|
.map(|chunk| chunk.iter().collect::<String>())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
fn next_happening()
|
fn next_happening()
|
||||||
-> Data
|
-> Data
|
||||||
{
|
{
|
||||||
@@ -116,6 +173,17 @@ fn next_happening()
|
|||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn send_choice(index: u16)
|
||||||
|
{
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let res = client.post("http://localhost:20264/choice")
|
||||||
|
.json(&index)
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_character(name: &str)
|
async fn get_character(name: &str)
|
||||||
-> (Character, Texture2D)
|
-> (Character, Texture2D)
|
||||||
{
|
{
|
||||||
@@ -125,7 +193,7 @@ async fn get_character(name: &str)
|
|||||||
let skin: Color = Color::from_rgba(skin_colour.red,skin_colour.green,skin_colour.blue,255);
|
let skin: Color = Color::from_rgba(skin_colour.red,skin_colour.green,skin_colour.blue,255);
|
||||||
let hair_colour = character.hair_color.clone();
|
let hair_colour = character.hair_color.clone();
|
||||||
let hair: Color = Color::from_rgba(hair_colour.red,hair_colour.green,hair_colour.blue,255);
|
let hair: Color = Color::from_rgba(hair_colour.red,hair_colour.green,hair_colour.blue,255);
|
||||||
let head_path: String = format!("../images/head/{}.png",character.head_shape);
|
let head_path: String = format!("/home/deadvey/.local/share/happening/images/head/{}.png",character.head_shape);
|
||||||
let head: Texture2D = change_colour(&mut load_image(head_path.as_str()).await.unwrap(), &skin, &hair);
|
let head: Texture2D = change_colour(&mut load_image(head_path.as_str()).await.unwrap(), &skin, &hair);
|
||||||
(character, head)
|
(character, head)
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
killall happening-server
|
||||||
|
RUST_LOG=debug
|
||||||
|
happening-server $XDG_DATA_HOME/happening/stories/$1 &
|
||||||
|
cargo run .
|
||||||
+64
-6
@@ -15,22 +15,57 @@ This is done in the about.json file,
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Characters
|
## 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/>
|
||||||
Customisation is done with @CHARACTER change \<feature\> into \<feature name\><br/>
|
Customisation is done with @CHARACTER change \<feature\> \<feature name\><br/>
|
||||||
Move a character with @CHARACTER to fr
|
Move a character with @CHARACTER to fr
|
||||||
|
Outputting can be done with @CHARACTER says "string", see more at (outputs)[##outputs]
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> fr means front-right for instance.
|
> fr means front-right for instance.
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
Currently only strings and integers are supported as variable types (booleans are planned).<br/>
|
||||||
|
The interpreter can assume the data type so this doesn't need to be specified.<br/>
|
||||||
|
To assign new variables (or overwrite existing ones):
|
||||||
|
```
|
||||||
|
$x = 1
|
||||||
|
$an_integer = 3
|
||||||
|
$y = "hello"
|
||||||
|
$a_string = "world"
|
||||||
|
```
|
||||||
|
> [!NOTE]
|
||||||
|
> Variable names can be as long as you want and can contain any characters except whitespaces
|
||||||
|
|
||||||
|
To modify existing variables:
|
||||||
|
```
|
||||||
|
$x + 1 // Adds 1 to an integer x
|
||||||
|
$x - 1 // Subtracts 1 from an integer x
|
||||||
|
$y + " world" // Appends " world" to the end of a string y
|
||||||
|
```
|
||||||
|
|
||||||
|
Other uses:
|
||||||
|
```
|
||||||
|
$x = choice "choice 1" { // Assigns to x the choice made by the client
|
||||||
|
...
|
||||||
|
$x = input // Assigns to x the string input made by the client
|
||||||
|
```
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
```
|
```
|
||||||
@CHARACTER says "this string
|
@CHARACTER says "this string
|
||||||
is multi-line
|
is multi-line
|
||||||
and ends with a"
|
and ends with a"
|
||||||
|
|
||||||
|
$x = "hello world"
|
||||||
|
@CHARACTER says $x
|
||||||
|
|
||||||
|
$name = "deadvey"
|
||||||
|
@CHARACTER says "hello $name"
|
||||||
```
|
```
|
||||||
|
> [!NOTE]
|
||||||
|
> Strings only support double quotes now ("") and do not support having quotes within quotes.
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
@@ -41,18 +76,20 @@ Variables are referenced with the \$, only integers will be supported.<br/>
|
|||||||
Condition based:
|
Condition based:
|
||||||
|
|
||||||
```
|
```
|
||||||
if (condition) {
|
if condition {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elif (condition) {
|
elif condition { // Only gets checked if the if and all previous elif statements failed
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else // Always passes if all previous if and elif statements failed
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
> [!NOTE]
|
||||||
|
> See [conditions](##conditions)
|
||||||
|
|
||||||
Choice based:
|
Choice based:
|
||||||
|
|
||||||
@@ -69,15 +106,35 @@ or "choice 3" {
|
|||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
You can assign a variable to the result of a choice by doing the following:
|
||||||
|
```
|
||||||
|
$x = choice "choice 1" {
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
## Positioning
|
## Positioning
|
||||||
|
|
||||||
```
|
```
|
||||||
@CHARACTER to position
|
@CHARACTER to position
|
||||||
|
|
||||||
PAN to position
|
PAN position
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Conditions
|
||||||
|
A condition works works like this:
|
||||||
|
```
|
||||||
|
$x == "hello" // evaluates true only if x is "hello"
|
||||||
|
$x > 1 // evaulates true if x is an integer and is more than 1
|
||||||
|
$x < 1 // evaluates true if x is an integer and is less than 1
|
||||||
|
$x >= 1 // evaluates true if x is an integer and is more than or equal to 1
|
||||||
|
$x <= 1 // evaluates true if x is an integer and is less than or equal to 1
|
||||||
|
|
||||||
|
```
|
||||||
|
> [!NOTE]
|
||||||
|
> == can be used for integers or strings whereas the rest can only be used with integers
|
||||||
|
|
||||||
|
The order (variable operator value) is currently fixed and so must be layed out like this.
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -89,3 +146,4 @@ GOTO label
|
|||||||
## Ending
|
## Ending
|
||||||
|
|
||||||
`END` to exit out of the story
|
`END` to exit out of the story
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-1
@@ -457,10 +457,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "happening-server"
|
name = "happening-server"
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-patch",
|
"serde-patch",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "happening-server"
|
name = "happening-server"
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "GPL-3"
|
license = "GPL-3"
|
||||||
repository = "https://git.javalsai.tuxcord.net/deadvey/happening/"
|
repository = "https://git.javalsai.tuxcord.net/deadvey/happening/"
|
||||||
@@ -25,6 +25,7 @@ unwrap_used = "warn"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.11.10"
|
env_logger = "0.11.10"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
|
regex = "1.12.3"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde-patch = "0.2.3"
|
serde-patch = "0.2.3"
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ pub fn modify_data // TODO rename
|
|||||||
character,
|
character,
|
||||||
choices,
|
choices,
|
||||||
};
|
};
|
||||||
|
debug!("{new_data:?}");
|
||||||
queue.push_back(new_data);
|
queue.push_back(new_data);
|
||||||
drop(queue);
|
drop(queue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,11 @@ pub fn character_parse(archive: &mut ZipArchive<File>)
|
|||||||
|
|
||||||
// Serialise this to a HashMap
|
// Serialise this to a HashMap
|
||||||
let characters: HashMap<String, Character> =
|
let characters: HashMap<String, Character> =
|
||||||
serde_json::from_str(&file_contents)
|
serde_json::from_str::<HashMap<String,Character>>(&file_contents)
|
||||||
.map_err (|err| format!("Invalid JSON in characters.json: {err}"))?;
|
.map_err (|err| format!("Invalid JSON in characters.json: {err}"))?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k,v)| (k.to_lowercase(), v))
|
||||||
|
.collect();
|
||||||
info!("Parsed characters from characters.json");
|
info!("Parsed characters from characters.json");
|
||||||
debug!("{characters:?}");
|
debug!("{characters:?}");
|
||||||
Ok(Arc::new(Mutex::new(characters)))
|
Ok(Arc::new(Mutex::new(characters)))
|
||||||
|
|||||||
+11
-1
@@ -40,6 +40,7 @@ use crate::
|
|||||||
{
|
{
|
||||||
traits::UnwrapOrExit,
|
traits::UnwrapOrExit,
|
||||||
};
|
};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main()
|
async fn main()
|
||||||
@@ -108,9 +109,18 @@ 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 the file
|
||||||
let (tokens, labels) = tokenise::tokenise(&file_contents)
|
let (tokens, labels) = tokenise::tokenise(&file_contents)
|
||||||
.unwrap_or_exit("Unable to tokenise data", 15);
|
.unwrap_or_exit("Unable to tokenise data", 15);
|
||||||
debug!("{tokens:?}\n{labels:?}");
|
// TESTING
|
||||||
|
//debug!("{tokens:?}\n{labels:?}");
|
||||||
|
for (index,token) in tokens.iter().enumerate()
|
||||||
|
{
|
||||||
|
debug!("{index}: {token:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the syntactic parser
|
||||||
let characters_clone2 = Arc::clone(&characters);
|
let characters_clone2 = Arc::clone(&characters);
|
||||||
let happening_stack2 = Arc::clone(&happening_stack);
|
let happening_stack2 = Arc::clone(&happening_stack);
|
||||||
// Run the parsing process for the DSL
|
// Run the parsing process for the DSL
|
||||||
|
|||||||
+22
-5
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use crate::
|
use crate::
|
||||||
{
|
{
|
||||||
// Internal code
|
// Internal code
|
||||||
@@ -11,6 +10,7 @@ use crate::
|
|||||||
Arc,
|
Arc,
|
||||||
Mutex,
|
Mutex,
|
||||||
VecDeque,
|
VecDeque,
|
||||||
|
HashMap,
|
||||||
info,
|
info,
|
||||||
debug,
|
debug,
|
||||||
warn,
|
warn,
|
||||||
@@ -18,6 +18,8 @@ use crate::
|
|||||||
|
|
||||||
mod character_parse;
|
mod character_parse;
|
||||||
mod keyword_parse;
|
mod keyword_parse;
|
||||||
|
mod identifier_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
|
||||||
@@ -30,6 +32,7 @@ pub fn token_parse(
|
|||||||
) -> Result<(),String>
|
) -> Result<(),String>
|
||||||
{
|
{
|
||||||
let mut index: usize = 0;
|
let mut index: usize = 0;
|
||||||
|
let mut variables: HashMap<String, tokenise::Value> = HashMap::new();
|
||||||
info!("Client has connected");
|
info!("Client has connected");
|
||||||
// Run an infinite loop
|
// Run an infinite loop
|
||||||
loop
|
loop
|
||||||
@@ -38,10 +41,11 @@ pub fn token_parse(
|
|||||||
// Get the next token
|
// Get the next token
|
||||||
match tokens.get(index)
|
match tokens.get(index)
|
||||||
{
|
{
|
||||||
|
// Keyword, eg IF, CHOICE or GOTO
|
||||||
Some(tokenise::Token::Keyword(token)) =>
|
Some(tokenise::Token::Keyword(token)) =>
|
||||||
{
|
{
|
||||||
if token.to_lowercase().as_str() == "end" { return Ok(()); }
|
if token.to_lowercase().as_str() == "end" { return Ok(()); }
|
||||||
index = keyword_parse::keyword_parse(tokens, token, index, happening_queue, labels, rx)?;
|
index = keyword_parse::keyword_parse(tokens, token, index, happening_queue, labels, &mut variables, rx)?;
|
||||||
},
|
},
|
||||||
// Ignore closing braces and jump over opening brace blocks
|
// Ignore closing braces and jump over opening brace blocks
|
||||||
Some(tokenise::Token::Bracket((bracket,new_index))) =>
|
Some(tokenise::Token::Bracket((bracket,new_index))) =>
|
||||||
@@ -56,7 +60,7 @@ pub fn token_parse(
|
|||||||
// Handle a character
|
// Handle a character
|
||||||
Some(tokenise::Token::Character(character_name)) =>
|
Some(tokenise::Token::Character(character_name)) =>
|
||||||
{
|
{
|
||||||
index = match character_parse::character_parse(index+1,tokens,character_name.clone(),characters,happening_queue)
|
index = match character_parse::character_parse(index+1,tokens,character_name.clone(),characters,happening_queue,&variables)
|
||||||
{
|
{
|
||||||
Ok(increment) => increment,
|
Ok(increment) => increment,
|
||||||
Err((err,increment)) =>
|
Err((err,increment)) =>
|
||||||
@@ -66,9 +70,22 @@ pub fn token_parse(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Some(_) =>
|
// Identifier
|
||||||
|
Some(tokenise::Token::Identifier(name)) =>
|
||||||
{
|
{
|
||||||
warn!("Unexpected token");
|
index = match identifier_parse::identifier_parse(index+1,name,tokens,&mut variables,rx,happening_queue)
|
||||||
|
{
|
||||||
|
Ok((increment,_)) => increment,
|
||||||
|
Err((err,increment)) =>
|
||||||
|
{
|
||||||
|
warn!("{err}");
|
||||||
|
increment
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(tok) =>
|
||||||
|
{
|
||||||
|
warn!("Unexpected token, at index {index}, expected character, keyword or identifier, got {tok:?}");
|
||||||
index += 1;
|
index += 1;
|
||||||
},
|
},
|
||||||
None => return Err("File unexpectedly reached termination point".to_string()),
|
None => return Err("File unexpectedly reached termination point".to_string()),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub fn character_parse
|
|||||||
character_name: String,
|
character_name: String,
|
||||||
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
|
characters: &Arc<Mutex<HashMap::<String, character::Character>>>,
|
||||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||||
|
variables: &HashMap<String, tokenise::Value>,
|
||||||
) -> Result<usize,(String,usize)>
|
) -> Result<usize,(String,usize)>
|
||||||
{
|
{
|
||||||
let mut sum_index: usize = index;
|
let mut sum_index: usize = index;
|
||||||
@@ -45,8 +46,8 @@ pub fn character_parse
|
|||||||
{
|
{
|
||||||
info!("SAYS command with character {character_name}");
|
info!("SAYS command with character {character_name}");
|
||||||
sum_index += 1;
|
sum_index += 1;
|
||||||
let output = tokenise::get_string_token(tokens, sum_index)
|
let output = tokenise::get_string_token(tokens, sum_index, variables)
|
||||||
.map_err(|err| (err, index))?;
|
.map_err(|err| (err, sum_index))?;
|
||||||
debug!("Saying {output}");
|
debug!("Saying {output}");
|
||||||
api::modify_data(happening_queue, "output".to_string(), output, character_name, vec![]);
|
api::modify_data(happening_queue, "output".to_string(), output, character_name, vec![]);
|
||||||
},
|
},
|
||||||
@@ -56,10 +57,10 @@ pub fn character_parse
|
|||||||
{
|
{
|
||||||
sum_index += 1;
|
sum_index += 1;
|
||||||
let feature = tokenise::get_keyword_token(tokens, sum_index)
|
let feature = tokenise::get_keyword_token(tokens, sum_index)
|
||||||
.map_err(|err| (err, index))?;
|
.map_err(|err| (err, sum_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,variables)
|
||||||
.map_err(|err| (err, index))?;
|
.map_err(|err| (err, 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)
|
||||||
@@ -74,7 +75,7 @@ pub fn character_parse
|
|||||||
{
|
{
|
||||||
sum_index += 1;
|
sum_index += 1;
|
||||||
let content = tokenise::get_keyword_token(tokens, sum_index)
|
let content = tokenise::get_keyword_token(tokens, sum_index)
|
||||||
.map_err(|err| (err, index))?;
|
.map_err(|err| (err, sum_index))?;
|
||||||
api::modify_data(happening_queue, 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
|
// Catch all condition, if the instruction is unrecognised as a
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
use crate::
|
||||||
|
{
|
||||||
|
// Internal code
|
||||||
|
tokenise,
|
||||||
|
api,
|
||||||
|
//Libs
|
||||||
|
HashMap,
|
||||||
|
Arc,
|
||||||
|
Mutex,
|
||||||
|
VecDeque,
|
||||||
|
warn,
|
||||||
|
debug,
|
||||||
|
info,
|
||||||
|
mpsc::Receiver,
|
||||||
|
};
|
||||||
|
use super::keyword_parse;
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub fn identifier_parse
|
||||||
|
(
|
||||||
|
index: usize,
|
||||||
|
identifier: &String,
|
||||||
|
tokens: &[tokenise::Token],
|
||||||
|
variables: &mut HashMap<String, tokenise::Value>,
|
||||||
|
rx: &Receiver<(usize,String)>,
|
||||||
|
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||||
|
) -> Result<(usize,bool),(String,usize)>
|
||||||
|
{
|
||||||
|
let mut sum_index: usize = index;
|
||||||
|
let current = variables
|
||||||
|
.entry(identifier.clone())
|
||||||
|
.or_insert(tokenise::Value::Null)
|
||||||
|
.clone();
|
||||||
|
let operator = tokenise::get_operator_token(tokens, sum_index)
|
||||||
|
.map_err(|err| (err, sum_index))?;
|
||||||
|
sum_index += 1;
|
||||||
|
let result: bool = match tokenise::get_value_token(tokens, sum_index)
|
||||||
|
{
|
||||||
|
// An value that can be directly assigned to or compared against the variable
|
||||||
|
Ok(value) =>
|
||||||
|
{
|
||||||
|
sum_index += 1;
|
||||||
|
operator_match(¤t,value,operator,identifier,variables)
|
||||||
|
},
|
||||||
|
// Another thing like a choice or an input
|
||||||
|
Err(_) =>
|
||||||
|
{
|
||||||
|
if operator != tokenise::Operator::Assignment // Only assignment is valid here
|
||||||
|
{
|
||||||
|
return Err((format!("Unexpected operator: {operator:?} at index {}",sum_index-1),sum_index + 1))
|
||||||
|
};
|
||||||
|
let keyword = tokenise::get_keyword_token(tokens,sum_index)
|
||||||
|
.map_err(|err| (err,sum_index))?;
|
||||||
|
match keyword.to_lowercase().as_str()
|
||||||
|
{
|
||||||
|
"choice" =>
|
||||||
|
{
|
||||||
|
let choice: String;
|
||||||
|
(sum_index, choice) = keyword_parse::choice_parse(tokens, index+1, happening_queue,rx,variables)
|
||||||
|
.map_err(|err| (err,sum_index+1))?;
|
||||||
|
variables.insert(identifier.to_owned(), tokenise::Value::String(choice));
|
||||||
|
},
|
||||||
|
"input" =>
|
||||||
|
{
|
||||||
|
api::modify_data(happening_queue, "input".to_string(), String::new(), String::new(), Vec::new());
|
||||||
|
info!("Waiting for client input");
|
||||||
|
let input = match rx.recv()
|
||||||
|
{
|
||||||
|
Ok((_,input)) => input,
|
||||||
|
Err(err) =>
|
||||||
|
{
|
||||||
|
warn!("Error receiving input from client, defaulting to choice \"\" {err}");
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
variables.insert(identifier.to_owned(), tokenise::Value::String(input));
|
||||||
|
sum_index += 1;
|
||||||
|
},
|
||||||
|
_ =>
|
||||||
|
{
|
||||||
|
warn!("Unexpected keyword {keyword}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
};
|
||||||
|
debug!("{variables:?}");
|
||||||
|
Ok((sum_index,result))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn operator_match
|
||||||
|
(
|
||||||
|
current: &tokenise::Value,
|
||||||
|
value: tokenise::Value,
|
||||||
|
operator: tokenise::Operator,
|
||||||
|
identifier: &String,
|
||||||
|
variables: &mut HashMap<String, tokenise::Value>
|
||||||
|
)
|
||||||
|
-> bool
|
||||||
|
{
|
||||||
|
// Operator match box
|
||||||
|
match operator
|
||||||
|
{
|
||||||
|
// Changing a value
|
||||||
|
tokenise::Operator::Assignment =>
|
||||||
|
{
|
||||||
|
variables.insert(identifier.to_owned(), value);
|
||||||
|
} ,
|
||||||
|
tokenise::Operator::Add =>
|
||||||
|
{
|
||||||
|
let result: tokenise::Value = match (value.clone(), current)
|
||||||
|
{
|
||||||
|
(tokenise::Value::Integer(int1),tokenise::Value::Integer(int2)) => tokenise::Value::Integer(int1 + int2),
|
||||||
|
(tokenise::Value::String(str1),tokenise::Value::String(str2)) => tokenise::Value::String(format!("{str1}{str2}")),
|
||||||
|
_ => value, // otherwise invalid
|
||||||
|
|
||||||
|
};
|
||||||
|
variables.insert(identifier.to_owned(), result);
|
||||||
|
},
|
||||||
|
tokenise::Operator::Sub =>
|
||||||
|
{
|
||||||
|
let result: tokenise::Value = match (value.clone(), current)
|
||||||
|
{
|
||||||
|
(tokenise::Value::Integer(int1),tokenise::Value::Integer(int2)) => tokenise::Value::Integer(int2 - int1),
|
||||||
|
_ => value, // otherwise invalid
|
||||||
|
|
||||||
|
};
|
||||||
|
variables.insert(identifier.to_owned(), result);
|
||||||
|
},
|
||||||
|
// Comparisons, return a boolean
|
||||||
|
tokenise::Operator::Comparison(comp) =>
|
||||||
|
{
|
||||||
|
let result = match (current, &value)
|
||||||
|
{
|
||||||
|
// Integer
|
||||||
|
(tokenise::Value::Integer(current), tokenise::Value::Integer(comparing)) =>
|
||||||
|
{
|
||||||
|
match comp
|
||||||
|
{
|
||||||
|
tokenise::Comparison::Equate => current == comparing,
|
||||||
|
tokenise::Comparison::Greater => current > comparing,
|
||||||
|
tokenise::Comparison::Less => current < comparing,
|
||||||
|
tokenise::Comparison::GreaterOrEqual => current >= comparing,
|
||||||
|
tokenise::Comparison::LessOrEqual => current <= comparing,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// String
|
||||||
|
(tokenise::Value::String(current), tokenise::Value::String(comparing)) =>
|
||||||
|
{
|
||||||
|
match comp
|
||||||
|
{
|
||||||
|
tokenise::Comparison::Equate => current == comparing,
|
||||||
|
tokenise::Comparison::Greater => current > comparing,
|
||||||
|
tokenise::Comparison::Less => current < comparing,
|
||||||
|
tokenise::Comparison::GreaterOrEqual => current >= comparing,
|
||||||
|
tokenise::Comparison::LessOrEqual => current <= comparing,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
warn!("Invalid comparison of {current:?} and {value:?}, evaluating false");
|
||||||
|
false
|
||||||
|
},
|
||||||
|
};
|
||||||
|
debug!("Comparison {current:?} comp {value:?} evaluates to {result}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ use crate::
|
|||||||
info,
|
info,
|
||||||
mpsc::Receiver,
|
mpsc::Receiver,
|
||||||
};
|
};
|
||||||
|
use super::identifier_parse;
|
||||||
|
|
||||||
pub fn keyword_parse(
|
pub fn keyword_parse(
|
||||||
tokens: &[tokenise::Token],
|
tokens: &[tokenise::Token],
|
||||||
@@ -19,6 +20,7 @@ pub fn keyword_parse(
|
|||||||
mut index: usize,
|
mut index: usize,
|
||||||
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||||
labels: &HashMap<String, usize>,
|
labels: &HashMap<String, usize>,
|
||||||
|
variables: &mut HashMap<String, tokenise::Value>,
|
||||||
rx: &Receiver<(usize,String)>,
|
rx: &Receiver<(usize,String)>,
|
||||||
)
|
)
|
||||||
-> Result<usize, String>
|
-> Result<usize, String>
|
||||||
@@ -28,18 +30,54 @@ pub fn keyword_parse(
|
|||||||
{
|
{
|
||||||
"choice" =>
|
"choice" =>
|
||||||
{
|
{
|
||||||
let choice_indeces = choice_parse(tokens, index, happening_queue)?;
|
(index,_) = choice_parse(tokens, index, happening_queue, rx,variables)?;
|
||||||
debug!("{choice_indeces:?}");
|
},
|
||||||
let choice = match rx.recv()
|
"if" =>
|
||||||
{
|
{
|
||||||
Ok((choice,_)) => choice_indeces[choice],
|
// TODO can this go in a function?
|
||||||
Err(err) =>
|
let mut keyword = "if".to_string();
|
||||||
|
while keyword == "if" || keyword == "elif" || keyword == "else" // TODO less beefy??
|
||||||
{
|
{
|
||||||
warn!("Error receiving choice from client, defaulting to choice 0 {err}");
|
index += 1;
|
||||||
0
|
let mut result: bool = true;
|
||||||
|
if keyword != "else"
|
||||||
|
{
|
||||||
|
let identifier = tokenise::get_identifier_token(tokens, index)?;
|
||||||
|
(index,result) = match identifier_parse::identifier_parse(index+1, &identifier, tokens, variables,rx,happening_queue)
|
||||||
|
{
|
||||||
|
Ok((increment, result)) => (increment,result),
|
||||||
|
Err((err,increment)) =>
|
||||||
|
{
|
||||||
|
warn!("{err}");
|
||||||
|
(increment,false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result { index += 1; break; }
|
||||||
|
index = tokenise::get_closing_index(tokens,index)?;
|
||||||
|
index += 1;
|
||||||
|
keyword = match tokenise::get_keyword_token(tokens,index)
|
||||||
|
{
|
||||||
|
Ok(keyword) => keyword,
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"else" =>
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
index = tokenise::get_closing_index(tokens, index)?;
|
||||||
|
},
|
||||||
|
"elif" =>
|
||||||
|
{
|
||||||
|
loop
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
let Ok(new_index) = tokenise::get_closing_index(tokens, index)
|
||||||
|
else { continue; };
|
||||||
|
index = new_index;
|
||||||
|
break
|
||||||
}
|
}
|
||||||
};
|
|
||||||
index = choice;
|
|
||||||
},
|
},
|
||||||
"or" =>
|
"or" =>
|
||||||
{
|
{
|
||||||
@@ -51,6 +89,7 @@ pub fn keyword_parse(
|
|||||||
// Jump to a particular index based on a label eg GOTO character_check
|
// Jump to a particular index based on a label eg GOTO character_check
|
||||||
"goto" =>
|
"goto" =>
|
||||||
{
|
{
|
||||||
|
info!("GOTO command, jumping there");
|
||||||
index += 1;
|
index += 1;
|
||||||
let label = tokenise::get_keyword_token(tokens, index)?;
|
let label = tokenise::get_keyword_token(tokens, index)?;
|
||||||
index = if let Some(label_index) = labels.get(&label) { *label_index }
|
index = if let Some(label_index) = labels.get(&label) { *label_index }
|
||||||
@@ -60,18 +99,32 @@ pub fn keyword_parse(
|
|||||||
index + 1
|
index + 1
|
||||||
};
|
};
|
||||||
debug!("Jumping to {index}");
|
debug!("Jumping to {index}");
|
||||||
}
|
},
|
||||||
|
"pan" =>
|
||||||
|
{
|
||||||
|
info!("PAN command, informing client");
|
||||||
|
index += 1;
|
||||||
|
let location = tokenise::get_keyword_token(tokens, index)?;
|
||||||
|
api::modify_data(happening_queue, "pan".to_string(), location, String::new(), Vec::new());
|
||||||
|
},
|
||||||
_ =>
|
_ =>
|
||||||
{
|
{
|
||||||
warn!("Invalid command: {token}");
|
warn!("Invalid command: {token}, index {index}");
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(index)
|
Ok(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choice_parse(tokens: &[tokenise::Token], mut index: usize, happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,)
|
pub fn choice_parse
|
||||||
-> Result<Vec<usize>, String>
|
(
|
||||||
|
tokens: &[tokenise::Token],
|
||||||
|
mut index: usize,
|
||||||
|
happening_queue: &Arc<Mutex<VecDeque<api::DataToSend>>>,
|
||||||
|
rx: &Receiver<(usize,String)>,
|
||||||
|
variables: &HashMap<String,tokenise::Value>,
|
||||||
|
)
|
||||||
|
-> Result<(usize,String), String>
|
||||||
{
|
{
|
||||||
let mut next_token: String = "or".to_string();
|
let mut next_token: String = "or".to_string();
|
||||||
let mut choices: Vec<String> = Vec::new();
|
let mut choices: Vec<String> = Vec::new();
|
||||||
@@ -81,7 +134,7 @@ fn choice_parse(tokens: &[tokenise::Token], mut index: usize, happening_queue: &
|
|||||||
index += 1;
|
index += 1;
|
||||||
choices.push
|
choices.push
|
||||||
(
|
(
|
||||||
tokenise::get_string_token(tokens, index)?
|
tokenise::get_string_token(tokens, index,variables)?
|
||||||
);
|
);
|
||||||
index += 1;
|
index += 1;
|
||||||
choice_indeces.push(index+1);
|
choice_indeces.push(index+1);
|
||||||
@@ -96,6 +149,17 @@ fn choice_parse(tokens: &[tokenise::Token], mut index: usize, happening_queue: &
|
|||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
api::modify_data(happening_queue, "choice".to_string(), String::new(), String::new(), choices);
|
api::modify_data(happening_queue, "choice".to_string(), String::new(), String::new(), choices.clone());
|
||||||
Ok(choice_indeces)
|
info!("Waiting for client choice");
|
||||||
|
debug!("{choice_indeces:?}");
|
||||||
|
let choice = match rx.recv()
|
||||||
|
{
|
||||||
|
Ok((choice,_)) => choice,
|
||||||
|
Err(err) =>
|
||||||
|
{
|
||||||
|
warn!("Error receiving choice from client, defaulting to choice 0 {err}");
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((choice_indeces[choice],choices[choice].clone()))
|
||||||
}
|
}
|
||||||
|
|||||||
+164
-11
@@ -1,31 +1,76 @@
|
|||||||
use crate::
|
use crate::
|
||||||
{
|
{
|
||||||
HashMap,
|
HashMap,
|
||||||
|
Regex,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Token
|
pub enum Token
|
||||||
{
|
{
|
||||||
String(String),
|
Value(Value),
|
||||||
|
Operator(Operator),
|
||||||
Keyword(String), // Keywords aren't checked for validity in this stage
|
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),
|
Identifier(String),
|
||||||
Bracket((Bracket,usize)), // Stores the index of the matching deliminator
|
Bracket((Bracket,usize)), // Stores the index of the matching deliminator
|
||||||
Character(String),
|
Character(String),
|
||||||
}
|
}
|
||||||
#[derive(Debug,Clone,PartialEq, Eq)]
|
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||||
|
pub enum Value
|
||||||
|
{
|
||||||
|
String(String),
|
||||||
|
Integer(i64),
|
||||||
|
Bool(bool),
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||||
pub enum Bracket
|
pub enum Bracket
|
||||||
{
|
{
|
||||||
Opening,
|
Opening,
|
||||||
Closing,
|
Closing,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_string_token(tokens: &[Token], index: usize)
|
#[derive(Debug, Clone,PartialEq)]
|
||||||
-> Result<String, String>
|
pub enum Operator
|
||||||
|
{
|
||||||
|
// Changing a value
|
||||||
|
Assignment,
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Comparison(Comparison),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparing a value
|
||||||
|
#[derive(Debug,Clone,PartialEq)]
|
||||||
|
pub enum Comparison
|
||||||
|
{
|
||||||
|
Equate,
|
||||||
|
Greater,
|
||||||
|
Less,
|
||||||
|
GreaterOrEqual,
|
||||||
|
LessOrEqual,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value
|
||||||
|
{
|
||||||
|
pub fn as_string(&self) -> String
|
||||||
|
{
|
||||||
|
match self
|
||||||
|
{
|
||||||
|
Value::String(s) => s.clone(),
|
||||||
|
Value::Integer(i) => i.to_string(),
|
||||||
|
Value::Bool(b) => b.to_string(),
|
||||||
|
Value::Null => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO error reporting for all of these
|
||||||
|
pub fn get_operator_token(tokens: &[Token], index: usize)
|
||||||
|
-> Result<Operator, String>
|
||||||
{
|
{
|
||||||
match tokens.get(index) {
|
match tokens.get(index) {
|
||||||
Some(Token::String(s)) => Ok(s.clone()),
|
Some(Token::Operator(op)) => Ok(op.clone()),
|
||||||
Some(_) => Err("Unexpected token".to_string()),
|
Some(tok) => Err(format!("Unexpected token at index {index}, expected operator, got {tok:?}")),
|
||||||
None => Err("File unexpectedly reached termination point".to_string()),
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +79,7 @@ pub fn get_keyword_token(tokens: &[Token], index: usize)
|
|||||||
{
|
{
|
||||||
match tokens.get(index) {
|
match tokens.get(index) {
|
||||||
Some(Token::Keyword(s)) => Ok(s.clone()),
|
Some(Token::Keyword(s)) => Ok(s.clone()),
|
||||||
Some(_) => Err("Unexpected token".to_string()),
|
Some(tok) => Err(format!("Unexpected token at index {index}, expected keyword, got {tok:?}")),
|
||||||
None => Err("File unexpectedly reached termination point".to_string()),
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,10 +88,77 @@ pub fn get_closing_index(tokens: &[Token], index: usize)
|
|||||||
{
|
{
|
||||||
match tokens.get(index) {
|
match tokens.get(index) {
|
||||||
Some(Token::Bracket((_, index))) => Ok(*index), // TODO check for closing
|
Some(Token::Bracket((_, index))) => Ok(*index), // TODO check for closing
|
||||||
Some(_) => Err("Unexpected token".to_string()),
|
Some(tok) => Err(format!("Unexpected token at index {index}, expected opening brace, got {tok:?}")),
|
||||||
None => Err("File unexpectedly reached termination point".to_string()),
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_value_token(tokens: &[Token], index: usize)
|
||||||
|
-> Result<Value, String>
|
||||||
|
{
|
||||||
|
match tokens.get(index) {
|
||||||
|
Some(Token::Value(val)) => Ok(val.clone()),
|
||||||
|
Some(tok) => Err(format!("Unexpected token at index {index}, expected value (int, string or boolean), got {tok:?}")),
|
||||||
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_identifier_token(tokens: &[Token], index: usize)
|
||||||
|
-> Result<String, String>
|
||||||
|
{
|
||||||
|
match tokens.get(index) {
|
||||||
|
Some(Token::Identifier(s)) => Ok(s.clone()),
|
||||||
|
Some(tok) => Err(format!("Unexpected token at index {index}, expected identifier, got {tok:?}")),
|
||||||
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_string_token(tokens: &[Token], index: usize, variables: &HashMap<String, Value>)
|
||||||
|
-> Result<String, String>
|
||||||
|
{
|
||||||
|
match tokens.get(index) {
|
||||||
|
// Either get string directly
|
||||||
|
Some(Token::Value(val)) =>
|
||||||
|
{
|
||||||
|
match val
|
||||||
|
{
|
||||||
|
Value::String(s) =>
|
||||||
|
{
|
||||||
|
let string = insert_variables_into_string(s, variables);
|
||||||
|
Ok(string)
|
||||||
|
},
|
||||||
|
_ => Err("Unexpected value type".to_string()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Or get the string that the identifier references
|
||||||
|
Some(Token::Identifier(iden)) =>
|
||||||
|
{
|
||||||
|
let val = variables.get(iden)
|
||||||
|
.ok_or(format!("Variable {iden} not initialised"))?;
|
||||||
|
match val
|
||||||
|
{
|
||||||
|
Value::String(s) =>
|
||||||
|
{
|
||||||
|
let string = insert_variables_into_string(s, variables);
|
||||||
|
Ok(string)
|
||||||
|
},
|
||||||
|
_ => Err("Unexpected value type".to_string()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(tok) => Err(format!("Unexpected token at index {index}, expected string value, got {tok:?}")),
|
||||||
|
None => Err("File unexpectedly reached termination point".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_variables_into_string(string: &str, variables: &HashMap<String, Value>)
|
||||||
|
-> String
|
||||||
|
{
|
||||||
|
let re = Regex::new(r"\$([A-Za-z0-9-_]*)").unwrap();
|
||||||
|
re.replace_all(string, |caps: ®ex::Captures| {
|
||||||
|
let key = &caps[1].to_lowercase();
|
||||||
|
variables.get(key)
|
||||||
|
.map(|v| v.as_string())
|
||||||
|
.unwrap_or_else(|| caps[0].to_string()) // leave unchanged if missing
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
// Tokenise the story to Vec<Token>
|
// Tokenise the story to Vec<Token>
|
||||||
// It can contain sub-objects (using recursive calls)
|
// It can contain sub-objects (using recursive calls)
|
||||||
@@ -64,12 +176,31 @@ pub fn tokenise(file_contents: &str)
|
|||||||
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();
|
||||||
|
// Operator
|
||||||
|
let op: Option<Operator> = match item.as_str()
|
||||||
|
{
|
||||||
|
"=" => Some(Operator::Assignment),
|
||||||
|
"+" => Some(Operator::Add),
|
||||||
|
"-" => Some(Operator::Sub),
|
||||||
|
"==" => Some(Operator::Comparison(Comparison::Equate)),
|
||||||
|
">" => Some(Operator::Comparison(Comparison::Greater)),
|
||||||
|
"<" => Some(Operator::Comparison(Comparison::Less)),
|
||||||
|
">=" => Some(Operator::Comparison(Comparison::GreaterOrEqual)),
|
||||||
|
"<=" => Some(Operator::Comparison(Comparison::LessOrEqual)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(op) = op
|
||||||
|
{
|
||||||
|
tokenised_data.push(Token::Operator(op));
|
||||||
|
index += 1;
|
||||||
|
continue // skip the rest of this loop
|
||||||
|
}
|
||||||
// Characters
|
// Characters
|
||||||
if item.starts_with('@')
|
if item.starts_with('@')
|
||||||
{
|
{
|
||||||
let mut chars = item.chars();
|
let mut chars = item.chars();
|
||||||
chars.next();
|
chars.next();
|
||||||
tokenised_data.push(Token::Character(chars.as_str().to_string()));
|
tokenised_data.push(Token::Character(chars.as_str().to_lowercase().to_string())); // Force character to be lowecase
|
||||||
}
|
}
|
||||||
// Strings
|
// Strings
|
||||||
else if item.starts_with('"') // TODO support '
|
else if item.starts_with('"') // TODO support '
|
||||||
@@ -78,8 +209,28 @@ pub fn tokenise(file_contents: &str)
|
|||||||
else { return Err("File unexpectedly ended: No closing quote".to_string()) };
|
else { return Err("File unexpectedly ended: No closing quote".to_string()) };
|
||||||
index = new_index;
|
index = new_index;
|
||||||
item = new_item;
|
item = new_item;
|
||||||
tokenised_data.push(Token::String(item));
|
tokenised_data.push(Token::Value(Value::String(item)));
|
||||||
}
|
}
|
||||||
|
else if let Ok(value) = item.parse::<i64>()
|
||||||
|
{
|
||||||
|
tokenised_data.push(Token::Value(Value::Integer(value))); // unwrap is fine here because I checked
|
||||||
|
}
|
||||||
|
else if item == "true"
|
||||||
|
{
|
||||||
|
tokenised_data.push(Token::Value(Value::Bool(true)));
|
||||||
|
}
|
||||||
|
else if item == "false"
|
||||||
|
{
|
||||||
|
tokenised_data.push(Token::Value(Value::Bool(false)));
|
||||||
|
}
|
||||||
|
// variable/identifier
|
||||||
|
else if item.starts_with('$')
|
||||||
|
{
|
||||||
|
let mut chars = item.chars();
|
||||||
|
chars.next();
|
||||||
|
tokenised_data.push(Token::Identifier(chars.as_str().to_string()));
|
||||||
|
}
|
||||||
|
// Integer
|
||||||
// Labels
|
// Labels
|
||||||
else if item.ends_with(':')
|
else if item.ends_with(':')
|
||||||
{
|
{
|
||||||
@@ -111,6 +262,8 @@ pub fn tokenise(file_contents: &str)
|
|||||||
Ok((tokenised_data, labels))
|
Ok((tokenised_data, labels))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO support strings that contain " in them by using a backslash
|
||||||
fn tokenise_string(space_seperated: &[&str], mut index: usize)
|
fn tokenise_string(space_seperated: &[&str], mut index: usize)
|
||||||
-> Option<(usize, String)>
|
-> Option<(usize, String)>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
@tim says "hello world, it's a good day"
|
|
||||||
@tim change name "Timothy Fineshooter"
|
|
||||||
label:
|
|
||||||
choice "choice numero uno" {
|
|
||||||
@tim says "super sad"
|
|
||||||
}
|
|
||||||
or "choice numero duo" {
|
|
||||||
@tim says "super unsad"
|
|
||||||
}
|
|
||||||
or "choice numero tres" {
|
|
||||||
@tim says "hola mi amigos"
|
|
||||||
goto label
|
|
||||||
}
|
|
||||||
END
|
|
||||||
Binary file not shown.
@@ -10,7 +10,7 @@
|
|||||||
"pronoun_deppos": "His",
|
"pronoun_deppos": "His",
|
||||||
"pronoun_indpos": "His",
|
"pronoun_indpos": "His",
|
||||||
"pronoun_reflex": "Himself",
|
"pronoun_reflex": "Himself",
|
||||||
"head_shape": "",
|
"head_shape": "normal-male",
|
||||||
"hair_style": "",
|
"hair_style": "",
|
||||||
"torso_shape": "",
|
"torso_shape": "",
|
||||||
"arm_shape": "",
|
"arm_shape": "",
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
$name = input
|
||||||
|
@tim says "hello"
|
||||||
|
@tim says $name
|
||||||
|
$x = 3
|
||||||
|
$x + 1
|
||||||
|
if $x == 4 {
|
||||||
|
@tim says "5"
|
||||||
|
}
|
||||||
|
elif $x < 5 {
|
||||||
|
|
||||||
|
@tim says "<5"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@tim says "whar"
|
||||||
|
}
|
||||||
|
label:
|
||||||
|
$choice_string = choice "choice numero uno" {
|
||||||
|
@tim says "super sad"
|
||||||
|
}
|
||||||
|
or "choice numero duo" {
|
||||||
|
@tim says "super unsad"
|
||||||
|
}
|
||||||
|
or "choice numero tres" {
|
||||||
|
@tim says "hola mi amigos"
|
||||||
|
goto label
|
||||||
|
}
|
||||||
|
END
|
||||||
+12
-4
@@ -2,11 +2,11 @@ import requests
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
debug = False
|
debug = True
|
||||||
try:
|
try:
|
||||||
if sys.argv[1] == "debug": debug = True
|
if sys.argv[1] == "silent": debug = False
|
||||||
except:
|
except:
|
||||||
debug = False
|
debug = True
|
||||||
|
|
||||||
# Loop and get new api
|
# Loop and get new api
|
||||||
def main():
|
def main():
|
||||||
@@ -22,7 +22,9 @@ 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
|
||||||
|
case "input":
|
||||||
|
get_input()
|
||||||
continue
|
continue
|
||||||
case "end":
|
case "end":
|
||||||
print("Exitting successfully")
|
print("Exitting successfully")
|
||||||
@@ -44,6 +46,12 @@ def choice(choices):
|
|||||||
print("Invalid choice, defaulting to 0")
|
print("Invalid choice, defaulting to 0")
|
||||||
requests.post(api_url, json=choice);
|
requests.post(api_url, json=choice);
|
||||||
|
|
||||||
|
def get_input():
|
||||||
|
api_url = "http://localhost:20264/input"
|
||||||
|
input_string = input("Input: ")
|
||||||
|
requests.post(api_url, json=input_string);
|
||||||
|
|
||||||
|
|
||||||
# Character outputs text to the user
|
# Character outputs text to the user
|
||||||
def output(character, text):
|
def output(character, text):
|
||||||
print(character["name"], "says")
|
print(character["name"], "says")
|
||||||
|
|||||||
Reference in New Issue
Block a user