Compare commits
2 Commits
cc3eca857d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ae74c07948 | |||
| 148fb73f7f |
@@ -7,7 +7,7 @@ For help with syntax, see [this documentation](/docs/SYNTAX.md)
|
||||
|
||||
## 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
|
||||
|
||||
@@ -103,14 +103,11 @@ Move a character with @CHARACTER to fr
|
||||
> fr means front-right for instance.
|
||||
|
||||
## 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
|
||||
- backslashes in strings
|
||||
- Brace index getter check for closing
|
||||
- Proper Error messages centralised???
|
||||
|
||||
|
||||
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
|
||||
|
||||
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/>
|
||||
Customisation is done with @CHARACTER change \<feature\> \<feature name\><br/>
|
||||
Move a character with @CHARACTER to fr
|
||||
Outputting can be done with @CHARACTER says "string", see more at (outputs)[##outputs]
|
||||
|
||||
> [!NOTE]
|
||||
> 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
|
||||
|
||||
```
|
||||
@CHARACTER says "this string
|
||||
is multi-line
|
||||
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
|
||||
|
||||
@@ -41,18 +76,20 @@ Variables are referenced with the \$, only integers will be supported.<br/>
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@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
|
||||
|
||||
```
|
||||
@@ -89,3 +146,4 @@ GOTO label
|
||||
## Ending
|
||||
|
||||
`END` to exit out of the story
|
||||
|
||||
|
||||
Generated
+1
@@ -461,6 +461,7 @@ version = "0.0.3"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde-patch",
|
||||
"serde_json",
|
||||
|
||||
@@ -25,6 +25,7 @@ unwrap_used = "warn"
|
||||
[dependencies]
|
||||
env_logger = "0.11.10"
|
||||
log = "0.4.29"
|
||||
regex = "1.12.3"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde-patch = "0.2.3"
|
||||
serde_json = "1.0.149"
|
||||
|
||||
@@ -71,8 +71,11 @@ pub fn character_parse(archive: &mut ZipArchive<File>)
|
||||
|
||||
// Serialise this to a HashMap
|
||||
let characters: HashMap<String, Character> =
|
||||
serde_json::from_str(&file_contents)
|
||||
.map_err (|err| format!("Invalid JSON in characters.json: {err}"))?;
|
||||
serde_json::from_str::<HashMap<String,Character>>(&file_contents)
|
||||
.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");
|
||||
debug!("{characters:?}");
|
||||
Ok(Arc::new(Mutex::new(characters)))
|
||||
|
||||
@@ -40,6 +40,7 @@ use crate::
|
||||
{
|
||||
traits::UnwrapOrExit,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main()
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::
|
||||
VecDeque,
|
||||
warn,
|
||||
debug,
|
||||
info,
|
||||
mpsc::Receiver,
|
||||
};
|
||||
use super::keyword_parse;
|
||||
@@ -62,6 +63,7 @@ pub fn identifier_parse
|
||||
"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,
|
||||
@@ -72,6 +74,7 @@ pub fn identifier_parse
|
||||
}
|
||||
};
|
||||
variables.insert(identifier.to_owned(), tokenise::Value::String(input));
|
||||
sum_index += 1;
|
||||
},
|
||||
_ =>
|
||||
{
|
||||
|
||||
@@ -89,6 +89,7 @@ pub fn keyword_parse(
|
||||
// Jump to a particular index based on a label eg GOTO character_check
|
||||
"goto" =>
|
||||
{
|
||||
info!("GOTO command, jumping there");
|
||||
index += 1;
|
||||
let label = tokenise::get_keyword_token(tokens, index)?;
|
||||
index = if let Some(label_index) = labels.get(&label) { *label_index }
|
||||
@@ -98,10 +99,17 @@ pub fn keyword_parse(
|
||||
index + 1
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -142,6 +150,7 @@ pub fn choice_parse
|
||||
}
|
||||
};
|
||||
api::modify_data(happening_queue, "choice".to_string(), String::new(), String::new(), choices.clone());
|
||||
info!("Waiting for client choice");
|
||||
debug!("{choice_indeces:?}");
|
||||
let choice = match rx.recv()
|
||||
{
|
||||
|
||||
+40
-3
@@ -1,6 +1,7 @@
|
||||
use crate::
|
||||
{
|
||||
HashMap,
|
||||
Regex,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -49,6 +50,19 @@ pub enum Comparison
|
||||
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)
|
||||
@@ -105,7 +119,11 @@ pub fn get_string_token(tokens: &[Token], index: usize, variables: &HashMap<Stri
|
||||
{
|
||||
match val
|
||||
{
|
||||
Value::String(s) => Ok(s.clone()),
|
||||
Value::String(s) =>
|
||||
{
|
||||
let string = insert_variables_into_string(s, variables);
|
||||
Ok(string)
|
||||
},
|
||||
_ => Err("Unexpected value type".to_string()),
|
||||
}
|
||||
},
|
||||
@@ -116,7 +134,11 @@ pub fn get_string_token(tokens: &[Token], index: usize, variables: &HashMap<Stri
|
||||
.ok_or(format!("Variable {iden} not initialised"))?;
|
||||
match val
|
||||
{
|
||||
Value::String(s) => Ok(s.clone()),
|
||||
Value::String(s) =>
|
||||
{
|
||||
let string = insert_variables_into_string(s, variables);
|
||||
Ok(string)
|
||||
},
|
||||
_ => Err("Unexpected value type".to_string()),
|
||||
}
|
||||
},
|
||||
@@ -125,6 +147,19 @@ pub fn get_string_token(tokens: &[Token], index: usize, variables: &HashMap<Stri
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
// It can contain sub-objects (using recursive calls)
|
||||
// TODO check for END
|
||||
@@ -165,7 +200,7 @@ pub fn tokenise(file_contents: &str)
|
||||
{
|
||||
let mut chars = item.chars();
|
||||
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
|
||||
else if item.starts_with('"') // TODO support '
|
||||
@@ -227,6 +262,8 @@ pub fn tokenise(file_contents: &str)
|
||||
Ok((tokenised_data, labels))
|
||||
}
|
||||
|
||||
|
||||
// TODO support strings that contain " in them by using a backslash
|
||||
fn tokenise_string(space_seperated: &[&str], mut index: usize)
|
||||
-> Option<(usize, String)>
|
||||
{
|
||||
|
||||
Binary file not shown.
+7
-5
@@ -2,11 +2,11 @@ import requests
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
debug = False
|
||||
debug = True
|
||||
try:
|
||||
if sys.argv[1] == "debug": debug = True
|
||||
if sys.argv[1] == "silent": debug = False
|
||||
except:
|
||||
debug = False
|
||||
debug = True
|
||||
|
||||
# Loop and get new api
|
||||
def main():
|
||||
@@ -22,7 +22,9 @@ def main():
|
||||
output(character, response["content"])
|
||||
case "choice":
|
||||
user_choice = choice(response["choices"])
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
case "input":
|
||||
get_input()
|
||||
continue
|
||||
case "end":
|
||||
print("Exitting successfully")
|
||||
@@ -46,7 +48,7 @@ def choice(choices):
|
||||
|
||||
def get_input():
|
||||
api_url = "http://localhost:20264/input"
|
||||
input_string = input()
|
||||
input_string = input("Input: ")
|
||||
requests.post(api_url, json=input_string);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user