1use ternlang_core::ast::{Expr, Stmt, Function, Program, Type};
18
19#[derive(Debug, PartialEq, Clone)]
21pub enum Sexp {
22 Atom(String),
23 List(Vec<Sexp>),
24}
25
26pub fn parse_sexp(input: &str) -> Result<Sexp, String> {
28 let tokens = tokenise(input);
29 let mut pos = 0;
30 parse_sexp_tokens(&tokens, &mut pos)
31}
32
33fn tokenise(input: &str) -> Vec<String> {
34 let mut tokens = Vec::new();
35 let mut current = String::new();
36 let mut in_comment = false;
37
38 for ch in input.chars() {
39 if in_comment {
40 if ch == '\n' { in_comment = false; }
41 continue;
42 }
43 match ch {
44 ';' => {
45 if !current.is_empty() { tokens.push(current.clone()); current.clear(); }
46 in_comment = true;
47 }
48 '(' | ')' => {
49 if !current.is_empty() { tokens.push(current.clone()); current.clear(); }
50 tokens.push(ch.to_string());
51 }
52 ' ' | '\t' | '\n' | '\r' => {
53 if !current.is_empty() { tokens.push(current.clone()); current.clear(); }
54 }
55 _ => current.push(ch),
56 }
57 }
58 if !current.is_empty() { tokens.push(current); }
59 tokens
60}
61
62fn parse_sexp_tokens(tokens: &[String], pos: &mut usize) -> Result<Sexp, String> {
63 if *pos >= tokens.len() {
64 return Err("Unexpected end of input".to_string());
65 }
66 let tok = tokens[*pos].clone();
67 *pos += 1;
68
69 if tok == "(" {
70 let mut list = Vec::new();
71 while *pos < tokens.len() && tokens[*pos] != ")" {
72 list.push(parse_sexp_tokens(tokens, pos)?);
73 }
74 if *pos >= tokens.len() {
75 return Err("Unmatched '('".to_string());
76 }
77 *pos += 1; Ok(Sexp::List(list))
79 } else if tok == ")" {
80 Err("Unexpected ')'".to_string())
81 } else {
82 Ok(Sexp::Atom(tok))
83 }
84}
85
86pub fn sexp_to_expr(sexp: &Sexp) -> Result<Expr, String> {
92 match sexp {
93 Sexp::Atom(s) => atom_to_expr(s),
94 Sexp::List(items) => list_to_expr(items),
95 }
96}
97
98fn atom_to_expr(s: &str) -> Result<Expr, String> {
99 match s {
100 "1" | "+1" => Ok(Expr::TritLiteral(1)),
101 "0" => Ok(Expr::TritLiteral(0)),
102 "-1" => Ok(Expr::TritLiteral(-1)),
103 "true" => Ok(Expr::TritLiteral(1)),
104 "false" => Ok(Expr::TritLiteral(-1)),
105 _ => Ok(Expr::Ident(s.to_string())),
106 }
107}
108
109fn make_call(callee: &str, args: Vec<Expr>) -> Expr {
110 Expr::Call { callee: callee.to_string(), args }
111}
112
113fn list_to_expr(items: &[Sexp]) -> Result<Expr, String> {
114 if items.is_empty() {
115 return Err("Empty S-expression list".to_string());
116 }
117
118 let head = match &items[0] {
119 Sexp::Atom(s) => s.as_str(),
120 _ => return Err("Expected operator/function name as first element".to_string()),
121 };
122
123 let args: Result<Vec<Expr>, _> = items[1..].iter().map(sexp_to_expr).collect();
124 let args = args?;
125
126 match head {
127 "+" | "add" => {
129 require(head, &args, 2)?;
130 Ok(make_call("consensus", args))
131 }
132 "neg" | "-" => {
133 if args.len() == 1 {
134 Ok(make_call("invert", args))
135 } else if args.len() == 2 {
136 let neg_b = make_call("invert", vec![args[1].clone()]);
137 Ok(make_call("consensus", vec![args[0].clone(), neg_b]))
138 } else {
139 Err(format!("{}: expected 1 or 2 args, got {}", head, args.len()))
140 }
141 }
142 "mul" | "*" => { require(head, &args, 2)?; Ok(make_call("mul", args)) }
143 "cons" => { require(head, &args, 2)?; Ok(make_call("consensus", args)) }
144 "invert" => { require(head, &args, 1)?; Ok(make_call("invert", args)) }
145
146 "truth" => Ok(make_call("truth", vec![])),
148 "hold" => Ok(make_call("hold", vec![])),
149 "conflict" => Ok(make_call("conflict", vec![])),
150
151 "if" => {
153 require(head, &args, 4)?;
154 Ok(make_call("__owlet_if__", args))
162 }
163
164 name => Ok(make_call(name, args)),
166 }
167}
168
169fn require(head: &str, args: &[Expr], n: usize) -> Result<(), String> {
170 if args.len() != n {
171 Err(format!("{}: expected {} args, got {}", head, n, args.len()))
172 } else {
173 Ok(())
174 }
175}
176
177pub struct OwletParser;
186
187impl OwletParser {
188 pub fn parse(source: &str) -> Result<Program, String> {
190 let mut functions: Vec<Function> = Vec::new();
191 let mut main_body: Vec<Stmt> = Vec::new();
192
193 let mut depth = 0usize;
195 let mut buffer = String::new();
196
197 for line in source.lines() {
198 let line = if let Some(idx) = line.find(';') { &line[..idx] } else { line }.trim();
200 if line.is_empty() { continue; }
201
202 for ch in line.chars() {
203 match ch { '(' => depth += 1, ')' => depth = depth.saturating_sub(1), _ => {} }
204 }
205 buffer.push(' ');
206 buffer.push_str(line);
207
208 if depth == 0 && !buffer.trim().is_empty() {
209 let sexp = parse_sexp(buffer.trim())?;
210 buffer.clear();
211
212 match &sexp {
213 Sexp::List(items) if !items.is_empty() => {
214 if let Sexp::Atom(head) = &items[0] {
215 match head.as_str() {
216 "fn" | "def" => {
217 functions.push(parse_fn_def(items)?);
218 continue;
219 }
220 "let" if items.len() >= 3 => {
221 let name = match &items[1] {
222 Sexp::Atom(n) => n.clone(),
223 _ => return Err("let: expected name".to_string()),
224 };
225 let val = sexp_to_expr(&items[2])?;
226 main_body.push(Stmt::Let { name, ty: Type::Trit, value: val });
227 if items.len() >= 4 {
228 let body = sexp_to_expr(&items[3])?;
229 main_body.push(Stmt::Return(body));
230 }
231 continue;
232 }
233 _ => {}
234 }
235 }
236 }
237 _ => {}
238 }
239
240 let expr = sexp_to_expr(&sexp)?;
241 main_body.push(Stmt::Return(expr));
242 }
243 }
244
245 if !main_body.is_empty() {
246 functions.push(Function {
247 name: "main".to_string(),
248 params: vec![],
249 return_type: Type::Trit,
250 body: main_body,
251 directive: None,
252 });
253 }
254
255 Ok(Program { imports: vec![], import_specs: vec![], structs: vec![], agents: vec![], functions })
256 }
257}
258
259fn parse_fn_def(items: &[Sexp]) -> Result<Function, String> {
260 if items.len() < 4 {
262 return Err("fn: expected (fn name (params) body)".to_string());
263 }
264 let name = match &items[1] {
265 Sexp::Atom(n) => n.clone(),
266 _ => return Err("fn: expected function name".to_string()),
267 };
268 let params: Vec<(String, Type)> = match &items[2] {
269 Sexp::List(ps) => ps.iter().map(|p| match p {
270 Sexp::Atom(n) => Ok((n.clone(), Type::Trit)),
271 _ => Err("fn: expected param name atom".to_string()),
272 }).collect::<Result<_, _>>()?,
273 Sexp::Atom(p) if p == "()" => vec![],
274 _ => return Err("fn: expected param list".to_string()),
275 };
276 let body_expr = sexp_to_expr(&items[3])?;
277 Ok(Function {
278 name,
279 params,
280 return_type: Type::Trit,
281 body: vec![Stmt::Return(body_expr)],
282 directive: None,
283 })
284}
285
286#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_parse_atom_pos() {
296 let s = parse_sexp("1").unwrap();
297 assert_eq!(sexp_to_expr(&s).unwrap(), Expr::TritLiteral(1));
298 }
299
300 #[test]
301 fn test_parse_atom_neg() {
302 let s = parse_sexp("-1").unwrap();
303 assert_eq!(sexp_to_expr(&s).unwrap(), Expr::TritLiteral(-1));
304 }
305
306 #[test]
307 fn test_parse_atom_zero() {
308 let s = parse_sexp("0").unwrap();
309 assert_eq!(sexp_to_expr(&s).unwrap(), Expr::TritLiteral(0));
310 }
311
312 #[test]
313 fn test_parse_invert_call() {
314 let s = parse_sexp("(neg -1)").unwrap();
315 let expr = sexp_to_expr(&s).unwrap();
316 assert!(matches!(expr, Expr::Call { ref callee, .. } if callee == "invert"));
317 }
318
319 #[test]
320 fn test_parse_consensus_call() {
321 let s = parse_sexp("(cons 1 0)").unwrap();
322 let expr = sexp_to_expr(&s).unwrap();
323 assert!(matches!(expr, Expr::Call { ref callee, .. } if callee == "consensus"));
324 }
325
326 #[test]
327 fn test_parse_add_maps_to_consensus() {
328 let s = parse_sexp("(+ 1 -1)").unwrap();
329 let expr = sexp_to_expr(&s).unwrap();
330 assert!(matches!(expr, Expr::Call { ref callee, .. } if callee == "consensus"));
331 }
332
333 #[test]
334 fn test_parse_nested() {
335 let s = parse_sexp("(neg (neg 1))").unwrap();
336 let expr = sexp_to_expr(&s).unwrap();
337 assert!(matches!(expr, Expr::Call { ref callee, .. } if callee == "invert"));
338 }
339
340 #[test]
341 fn test_unmatched_paren_error() {
342 assert!(parse_sexp("(+ 1 2").is_err());
343 }
344
345 #[test]
346 fn test_empty_list_error() {
347 let s = parse_sexp("()").unwrap();
348 assert!(sexp_to_expr(&s).is_err());
349 }
350
351 #[test]
352 fn test_owlet_fn_parse() {
353 let src = "(fn negate (x) (neg x))";
354 let prog = OwletParser::parse(src).unwrap();
355 assert_eq!(prog.functions.len(), 1);
356 assert_eq!(prog.functions[0].name, "negate");
357 assert_eq!(prog.functions[0].params.len(), 1);
358 assert_eq!(prog.functions[0].params[0].0, "x");
359 }
360
361 #[test]
362 fn test_owlet_top_level_expr_becomes_main() {
363 let src = "(neg -1)";
364 let prog = OwletParser::parse(src).unwrap();
365 assert_eq!(prog.functions.last().unwrap().name, "main");
366 }
367
368 #[test]
369 fn test_owlet_comment_stripped() {
370 let src = "; this is a comment\n(neg 1)";
371 let prog = OwletParser::parse(src).unwrap();
372 assert!(!prog.functions.is_empty());
373 }
374
375 #[test]
376 fn test_owlet_let_binding() {
377 let src = "(let x 1)";
378 let prog = OwletParser::parse(src).unwrap();
379 let main = prog.functions.last().unwrap();
380 assert!(main.body.iter().any(|s| matches!(s, Stmt::Let { name, .. } if name == "x")));
381 }
382
383 #[test]
384 fn test_owlet_mul() {
385 let s = parse_sexp("(mul 1 -1)").unwrap();
386 let expr = sexp_to_expr(&s).unwrap();
387 assert!(matches!(expr, Expr::Call { ref callee, .. } if callee == "mul"));
388 }
389}