sim_lib_openai_server/plan/
parse.rs1use sim_kernel::{Error, Expr, Result, Symbol};
2
3use crate::plan::combinators::plan_symbol;
4
5pub fn parse_plan(input: &str) -> Result<Expr> {
8 let mut parser = PlanParser::new(input);
9 let plan = parser.parse_plan()?;
10 parser.skip_ws();
11 if parser.is_done() {
12 Ok(plan)
13 } else {
14 Err(Error::Eval(format!(
15 "unexpected trailing plan input near {:?}",
16 parser.remaining()
17 )))
18 }
19}
20
21struct PlanParser<'a> {
22 input: &'a str,
23 pos: usize,
24}
25
26impl<'a> PlanParser<'a> {
27 fn new(input: &'a str) -> Self {
28 Self { input, pos: 0 }
29 }
30
31 fn parse_plan(&mut self) -> Result<Expr> {
32 self.skip_ws();
33 let start = self.pos;
34 let name = self.parse_ident()?;
35 self.skip_ws();
36 if self.peek() == Some('(') {
37 self.bump();
38 return self.parse_combinator(name);
39 }
40 self.pos = start;
41 self.parse_atom()
42 }
43
44 fn parse_combinator(&mut self, name: String) -> Result<Expr> {
45 let mut args = Vec::new();
46 self.skip_ws();
47 if self.peek() == Some(')') {
48 self.bump();
49 return Ok(Expr::List(vec![Expr::Symbol(plan_symbol(&name))]));
50 }
51
52 loop {
53 args.push(self.parse_arg()?);
54 self.skip_ws();
55 match self.peek() {
56 Some(',') => {
57 self.bump();
58 self.skip_ws();
59 }
60 Some(')') => {
61 self.bump();
62 break;
63 }
64 _ => {
65 return Err(Error::Eval(format!(
66 "expected ',' or ')' in plan combinator near {:?}",
67 self.remaining()
68 )));
69 }
70 }
71 }
72
73 let mut items = Vec::with_capacity(args.len() + 1);
74 items.push(Expr::Symbol(plan_symbol(&name)));
75 items.extend(args);
76 Ok(Expr::List(items))
77 }
78
79 fn parse_arg(&mut self) -> Result<Expr> {
80 self.skip_ws();
81 let start = self.pos;
82 if let Ok(name) = self.parse_ident() {
83 self.skip_ws();
84 if self.peek() == Some(':') {
85 self.bump();
86 let value = self.parse_plan_or_literal()?;
87 return Ok(keyword_expr(name, value));
88 }
89 }
90 self.pos = start;
91 self.parse_plan()
92 }
93
94 fn parse_plan_or_literal(&mut self) -> Result<Expr> {
95 self.skip_ws();
96 let start = self.pos;
97 if self.parse_ident().is_ok() {
98 self.skip_ws();
99 if self.peek() == Some('(') {
100 self.pos = start;
101 return self.parse_plan();
102 }
103 }
104 self.pos = start;
105 self.parse_atom()
106 }
107
108 fn parse_atom(&mut self) -> Result<Expr> {
109 self.skip_ws();
110 let start = self.pos;
111 while let Some(ch) = self.peek() {
112 if ch == ',' || ch == ')' {
113 break;
114 }
115 self.bump();
116 }
117 let atom = self.input[start..self.pos].trim();
118 if atom.is_empty() {
119 return Err(Error::Eval("plan atom cannot be empty".to_owned()));
120 }
121 if atom.contains('(') {
122 return Err(Error::Eval(format!("invalid plan atom {atom:?}")));
123 }
124 Ok(Expr::List(vec![
125 Expr::Symbol(plan_symbol("atom")),
126 Expr::String(atom.to_owned()),
127 ]))
128 }
129
130 fn parse_ident(&mut self) -> Result<String> {
131 self.skip_ws();
132 let mut chars = self.remaining().char_indices();
133 let Some((_, first)) = chars.next() else {
134 return Err(Error::Eval("expected plan identifier".to_owned()));
135 };
136 if !first.is_ascii_alphabetic() {
137 return Err(Error::Eval("expected plan identifier".to_owned()));
138 }
139 let mut end = self.pos + first.len_utf8();
140 for (offset, ch) in chars {
141 if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
142 end = self.pos + offset + ch.len_utf8();
143 } else {
144 break;
145 }
146 }
147 let ident = self.input[self.pos..end].to_owned();
148 self.pos = end;
149 Ok(ident)
150 }
151
152 fn skip_ws(&mut self) {
153 while matches!(self.peek(), Some(ch) if ch.is_ascii_whitespace()) {
154 self.bump();
155 }
156 }
157
158 fn is_done(&self) -> bool {
159 self.pos >= self.input.len()
160 }
161
162 fn remaining(&self) -> &str {
163 &self.input[self.pos..]
164 }
165
166 fn peek(&self) -> Option<char> {
167 self.remaining().chars().next()
168 }
169
170 fn bump(&mut self) {
171 if let Some(ch) = self.peek() {
172 self.pos += ch.len_utf8();
173 }
174 }
175}
176
177fn keyword_expr(name: String, value: Expr) -> Expr {
178 Expr::Map(vec![
179 (
180 Expr::Symbol(Symbol::new("keyword")),
181 Expr::Symbol(Symbol::new(name)),
182 ),
183 (Expr::Symbol(Symbol::new("value")), value),
184 ])
185}