Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

let mommy change her personality at runtime #66

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ default = ["thirsty", "yikes"]
thirsty = []
yikes = []

runtime-responses = []

[dependencies]
bumpalo = "3.14.0"
fastrand = "1.8.0"
serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.91"
regex = "1.10.2"

[build-dependencies]
bumpalo = "3.14.0"
fastrand = "1.8.0"
serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.91"
serde-tuple-vec-map = "1.0.1"
regex = "1.10.2"

[workspace.metadata.release]
Expand Down
171 changes: 25 additions & 146 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,168 +10,47 @@
//! If your new mood or variable include... "spicy" terms, make sure to set an
//! explicit `spiciness`.

use std::collections::BTreeMap;
#![allow(dead_code)]

use std::env;
use std::fmt::Write;
use std::fs;
use std::ops::Range;
use std::path::Path;

use regex::Regex;
// Mommy needs to use some code that's part of the final binary in her build
// script, too~
#[path = "src/json.rs"]
mod json;
#[path = "src/template.rs"]
mod template;

const RESPONSES: &str = include_str!("./responses.json");

#[derive(PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
enum Spiciness {
Chill,
Thirsty,
Yikes,
}

impl Spiciness {
const CONFIGURED: Spiciness = if cfg!(feature = "yikes") {
Self::Yikes
} else if cfg!(feature = "thirsty") {
Self::Thirsty
} else {
Self::Chill
};
}

impl Default for Spiciness {
fn default() -> Self {
Self::Chill
}
}

#[derive(serde::Deserialize)]
struct Mood {
positive: Vec<String>,
negative: Vec<String>,
overflow: Vec<String>,

#[serde(default)]
spiciness: Spiciness,
}

#[derive(serde::Deserialize)]
struct Var {
defaults: Vec<String>,
#[serde(default)]
env_key: Option<String>,

#[serde(default)]
spiciness: Spiciness,

// Mommy needs a way to reference variables by index when doing template
// substitution. This type is the value of an ordered map, so we can just
// stick an index in after parsing~
#[serde(skip)]
index: usize,
}

#[derive(serde::Deserialize)]
struct Config {
vars: BTreeMap<String, Var>,
moods: BTreeMap<String, Mood>,
}
// This needs to exist so Var::load() compiles, but don't worry, mommy will never
// call that from build.rs~
const CONFIG: template::Config<'static> = template::Config {
vars: &[],
moods: &[],
mood: !0,
emote: !0,
pronoun: !0,
role: !0,
};

fn main() {
let out_dir = &env::var("OUT_DIR").unwrap();
let dest_path = Path::new(out_dir).join("responses.rs");

let mut config: Config = serde_json::from_str(RESPONSES).unwrap();
let mut i = 0;
let mut vars = String::new();
for (name, var) in config.vars.iter_mut() {
if var.spiciness > Spiciness::CONFIGURED {
continue;
}
var.index = i;
i += 1;

let env_key = var
.env_key
.clone()
.unwrap_or_else(|| format!("{}S", name.to_uppercase()));
let defaults = &var.defaults;
let _ = write!(
vars,
r#"Var {{ env_key: "{env_key}", defaults: &{defaults:?} }},"#
);
}

let pattern = Regex::new(r"\{\w+\}").unwrap();
let mut responses = String::new();
for (name, mood) in &config.moods {
if mood.spiciness > Spiciness::CONFIGURED {
continue;
}

let parse_response = |text: &str, out: &mut String| {
let _ = write!(out, "&[");

// Mommy has to the template on matches for `pattern`, and generate
// an array of alternating Chunk::Text and Chunk::Var values.
let mut prev = 0;
for var in pattern.find_iter(text) {
let var_name = &var.as_str()[1..var.len() - 1];
let var_idx = match config.vars.get(var_name) {
Some(var) => {
assert!(
var.spiciness <= Spiciness::CONFIGURED,
"{{{var_name}}} is too spicy!"
);
var.index
}
None => panic!("undeclared variable {{{var_name}}}"),
};

let Range { start, end } = var.range();
let prev_chunk = &text[prev..start];
prev = end;

let _ = write!(out, "Chunk::Text({prev_chunk:?}), Chunk::Var({var_idx}), ");
}

let _ = write!(out, "Chunk::Text({:?})],", &text[prev..],);
};

let _ = write!(responses, "Mood {{ name: {name:?}, positive: &[");
for response in &mood.positive {
parse_response(response, &mut responses)
}
let _ = write!(responses, "], negative: &[");
for response in &mood.negative {
parse_response(response, &mut responses)
}
let _ = write!(responses, "], overflow: &[");
for response in &mood.overflow {
parse_response(response, &mut responses)
}
let _ = write!(responses, "] }},");
}

// Mommy needs some hard-coded vars at a specific location~
let mood_idx = config.vars["mood"].index;
let emote_idx = config.vars["emote"].index;
let pronoun_idx = config.vars["pronoun"].index;
let role_idx = config.vars["role"].index;
let bump = bumpalo::Bump::new();
let config = json::Config::parse("mommy", RESPONSES)
.unwrap()
.build("mommy", &bump)
.unwrap();

fs::write(
dest_path,
format!(
r"
static CONFIG: Config<'static> = Config {{
vars: &[{vars}],
moods: &[{responses}],
}};
static MOOD: &Var<'static> = &CONFIG.vars[{mood_idx}];
static EMOTE: &Var<'static> = &CONFIG.vars[{emote_idx}];
static PRONOUN: &Var<'static> = &CONFIG.vars[{pronoun_idx}];
static ROLE: &Var<'static> = &CONFIG.vars[{role_idx}];
"
"const CONFIG: template::Config<'static> = {};",
config.const_string()
),
)
.unwrap();
Expand Down
Loading