Skip to content

Commit 175d550

Browse files
committed
if blocks
Signed-off-by: Nick Mitchell <[email protected]>
1 parent 60bd039 commit 175d550

File tree

5 files changed

+100
-7
lines changed

5 files changed

+100
-7
lines changed

pdl-live-react/src-tauri/src/pdl/ast.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,13 @@ pub struct ReadBlock {
302302
pub multiline: Option<bool>,
303303
}
304304

305+
#[derive(Serialize, Deserialize, Debug, Clone)]
306+
#[serde(untagged)]
307+
pub enum StringOrBoolean {
308+
String(String),
309+
Boolean(bool),
310+
}
311+
305312
/// Conditional control structure.
306313
///
307314
/// Example:
@@ -317,15 +324,18 @@ pub struct ReadBlock {
317324
pub struct IfBlock {
318325
/// The condition to check
319326
#[serde(rename = "if")]
320-
condition: String,
327+
pub condition: StringOrBoolean,
321328

322329
/// Branch to execute if the condition is true
323-
then: Box<PdlBlock>,
330+
pub then: Box<PdlBlock>,
324331

325332
/// Branch to execute if the condition is false.
326333
#[serde(rename = "else")]
327334
#[serde(skip_serializing_if = "Option::is_none")]
328-
else_: Option<Box<PdlBlock>>,
335+
pub else_: Option<Box<PdlBlock>>,
336+
337+
#[serde(skip_serializing_if = "Option::is_none")]
338+
pub defs: Option<HashMap<String, PdlBlock>>,
329339
}
330340

331341
#[derive(Serialize, Deserialize, Debug, Clone)]

pdl-live-react/src-tauri/src/pdl/interpreter.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use serde_json::{from_str, to_string, Map, Value};
2525
use serde_norway::{from_reader, from_str as from_yaml_str};
2626

2727
use crate::pdl::ast::{
28-
CallBlock, ListOrString, ModelBlock, PdlBlock, PdlParser, PdlUsage, PythonCodeBlock, ReadBlock,
29-
RepeatBlock, Role, TextBlock,
28+
CallBlock, IfBlock, ListOrString, ModelBlock, PdlBlock, PdlParser, PdlUsage, PythonCodeBlock,
29+
ReadBlock, RepeatBlock, Role, StringOrBoolean, TextBlock,
3030
};
3131

3232
type Context = Vec<ChatMessage>;
@@ -82,8 +82,9 @@ impl<'a> Interpreter<'a> {
8282
let (messages, trace) = match program {
8383
PdlBlock::String(s) => self.run_string(s, context).await,
8484
PdlBlock::Call(block) => self.run_call(block, context).await,
85-
PdlBlock::PythonCode(block) => self.run_python_code(block, context).await,
85+
PdlBlock::If(block) => self.run_if(block, context).await,
8686
PdlBlock::Model(block) => self.run_model(block, context).await,
87+
PdlBlock::PythonCode(block) => self.run_python_code(block, context).await,
8788
PdlBlock::Read(block) => self.run_read(block, context).await,
8889
PdlBlock::Repeat(block) => self.run_repeat(block, context).await,
8990
PdlBlock::Text(block) => self.run_text(block, context).await,
@@ -238,6 +239,34 @@ impl<'a> Interpreter<'a> {
238239
res
239240
}
240241

242+
// Run a PdlBlock::Call
243+
async fn run_if(&mut self, block: &IfBlock, context: Context) -> Interpretation {
244+
if self.debug {
245+
eprintln!("If {:?}({:?})", block.condition, block.then);
246+
}
247+
248+
self.extend_scope_with_block_map(&block.defs);
249+
250+
let cond = match &block.condition {
251+
StringOrBoolean::Boolean(b) => Value::Bool(*b),
252+
StringOrBoolean::String(s) => self.eval::<Value>(s)?,
253+
};
254+
let res = match cond {
255+
Value::Bool(true) => self.run_quiet(&block.then, context).await,
256+
Value::Bool(false) => match &block.else_ {
257+
Some(else_block) => self.run_quiet(&else_block, context).await,
258+
None => Ok((vec![], PdlBlock::If(block.clone()))),
259+
},
260+
x => Err(Box::from(format!(
261+
"if block condition evaluated to non-boolean value: {:?}",
262+
x
263+
))),
264+
};
265+
266+
self.scope.pop();
267+
res
268+
}
269+
241270
fn to_ollama_model_options(
242271
&self,
243272
maybe_parameters: &Option<HashMap<String, Value>>,

pdl-live-react/src-tauri/src/pdl/interpreter_tests.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod tests {
99
interpreter::{run_json_sync as run_json, run_sync as run},
1010
};
1111

12-
use ollama_rs::generation::chat::{ChatMessage, MessageRole};
12+
use ollama_rs::generation::chat::MessageRole;
1313

1414
const DEFAULT_MODEL: &'static str = "ollama/granite3.2:2b";
1515

@@ -294,4 +294,51 @@ mod tests {
294294
assert_eq!(messages[2].content, "6c");
295295
Ok(())
296296
}
297+
298+
#[test]
299+
fn text_if_true() -> Result<(), Box<dyn Error>> {
300+
let program = json!({
301+
"if": true,
302+
"then": "good"
303+
});
304+
305+
let (messages, _) = run_json(program, false)?;
306+
assert_eq!(messages.len(), 1);
307+
assert_eq!(messages[0].role, MessageRole::User);
308+
assert_eq!(messages[0].content, "good");
309+
Ok(())
310+
}
311+
312+
#[test]
313+
fn text_if_false() -> Result<(), Box<dyn Error>> {
314+
let program = json!({
315+
"if": false,
316+
"then": "bug",
317+
"else": "good"
318+
});
319+
320+
let (messages, _) = run_json(program, false)?;
321+
assert_eq!(messages.len(), 1);
322+
assert_eq!(messages[0].role, MessageRole::User);
323+
assert_eq!(messages[0].content, "good");
324+
Ok(())
325+
}
326+
327+
#[test]
328+
fn text_if_with_defs() -> Result<(), Box<dyn Error>> {
329+
let program = json!({
330+
"defs": {
331+
"x": 5
332+
},
333+
"if": "${x!=5}",
334+
"then": "bug",
335+
"else": "good"
336+
});
337+
338+
let (messages, _) = run_json(program, false)?;
339+
assert_eq!(messages.len(), 1);
340+
assert_eq!(messages[0].role, MessageRole::User);
341+
assert_eq!(messages[0].content, "good");
342+
Ok(())
343+
}
297344
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
if: true
2+
then: hi
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defs:
2+
x: 5
3+
if: ${x!=5}
4+
then: bug
5+
else: good

0 commit comments

Comments
 (0)