summaryrefslogtreecommitdiffstats
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/mod.rs2
-rw-r--r--src/utils/render_prompt.rs155
2 files changed, 157 insertions, 0 deletions
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index bd9f381..04c5668 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,11 +1,13 @@
mod abort_signal;
mod clipboard;
mod prompt_input;
+mod render_prompt;
mod tiktoken;
pub use self::abort_signal::{create_abort_signal, AbortSignal};
pub use self::clipboard::set_text;
pub use self::prompt_input::*;
+pub use self::render_prompt::render_prompt;
pub use self::tiktoken::cl100k_base_singleton;
use sha2::{Digest, Sha256};
diff --git a/src/utils/render_prompt.rs b/src/utils/render_prompt.rs
new file mode 100644
index 0000000..12661fa
--- /dev/null
+++ b/src/utils/render_prompt.rs
@@ -0,0 +1,155 @@
+use std::collections::HashMap;
+
+/// Render REPL prompt
+///
+/// The template comprises plain text and `{...}`.
+///
+/// The syntax of `{...}`:
+/// - `{var}` - When `var` has a value, replace `var` with the value and eval `template`
+/// - `{?var <template>}` - Eval `template` when `var` is evaluated as true
+/// - `{!var <template>}` - Eval `template` when `var` is evaluated as false
+pub fn render_prompt(template: &str, variables: &HashMap<&str, String>) -> String {
+ let exprs = parse_template(template);
+ eval_exprs(&exprs, variables)
+}
+
+fn parse_template(template: &str) -> Vec<Expr> {
+ let chars: Vec<char> = template.chars().collect();
+ let mut exprs = vec![];
+ let mut current = vec![];
+ let mut balances = vec![];
+ for ch in chars.iter().cloned() {
+ if !balances.is_empty() {
+ if ch == '}' {
+ balances.pop();
+ if balances.is_empty() {
+ if !current.is_empty() {
+ let block = parse_block(&mut current);
+ exprs.push(block)
+ }
+ } else {
+ current.push(ch);
+ }
+ } else if ch == '{' {
+ balances.push(ch);
+ current.push(ch);
+ } else {
+ current.push(ch);
+ }
+ } else if ch == '{' {
+ balances.push(ch);
+ add_text(&mut exprs, &mut current);
+ } else {
+ current.push(ch)
+ }
+ }
+ add_text(&mut exprs, &mut current);
+ exprs
+}
+
+fn parse_block(current: &mut Vec<char>) -> Expr {
+ let value: String = current.drain(..).collect();
+ match value.split_once(' ') {
+ Some((name, tail)) => {
+ if let Some(name) = name.strip_prefix('?') {
+ let block_exprs = parse_template(tail);
+ Expr::Block(BlockType::Yes, name.to_string(), block_exprs)
+ } else if let Some(name) = name.strip_prefix('!') {
+ let block_exprs = parse_template(tail);
+ Expr::Block(BlockType::No, name.to_string(), block_exprs)
+ } else {
+ Expr::Text(format!("{{{value}}}"))
+ }
+ }
+ None => Expr::Variable(value),
+ }
+}
+
+fn eval_exprs(exprs: &[Expr], variables: &HashMap<&str, String>) -> String {
+ let mut output = String::new();
+ for part in exprs {
+ match part {
+ Expr::Text(text) => output.push_str(text),
+ Expr::Variable(variable) => {
+ let value = variables
+ .get(variable.as_str())
+ .cloned()
+ .unwrap_or_default();
+ output.push_str(&value);
+ }
+ Expr::Block(typ, variable, block_exprs) => {
+ let value = variables
+ .get(variable.as_str())
+ .cloned()
+ .unwrap_or_default();
+ match typ {
+ BlockType::Yes => {
+ if truly(&value) {
+ let block_output = eval_exprs(block_exprs, variables);
+ output.push_str(&block_output)
+ }
+ }
+ BlockType::No => {
+ if !truly(&value) {
+ let block_output = eval_exprs(block_exprs, variables);
+ output.push_str(&block_output)
+ }
+ }
+ }
+ }
+ }
+ }
+ output
+}
+
+fn add_text(exprs: &mut Vec<Expr>, current: &mut Vec<char>) {
+ if current.is_empty() {
+ return;
+ }
+ let value: String = current.drain(..).collect();
+ exprs.push(Expr::Text(value));
+}
+
+fn truly(value: &str) -> bool {
+ !(value.is_empty() || value == "0" || value == "false")
+}
+
+#[derive(Debug)]
+enum Expr {
+ Text(String),
+ Variable(String),
+ Block(BlockType, String, Vec<Expr>),
+}
+
+#[derive(Debug)]
+enum BlockType {
+ Yes,
+ No,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ macro_rules! assert_render {
+ ($template:expr, [$(($key:literal, $value:literal),)*], $expect:literal) => {
+ let data = HashMap::from([
+ $(($key, $value.into()),)*
+ ]);
+ assert_eq!(render_prompt($template, &data), $expect);
+ };
+ }
+
+ #[test]
+ fn test_render() {
+ let prompt = "{?session {session}{?role /}}{role}{?session )}{!session >}";
+ assert_render!(prompt, [], ">");
+ assert_render!(prompt, [("role", "coder"),], "coder>");
+ assert_render!(prompt, [("session", "temp"),], "temp)");
+ assert_render!(
+ prompt,
+ [("session", "temp"), ("role", "coder"),],
+ "temp/coder)"
+ );
+ }
+}