Skip to main content

josie_core/
reader.rs

1use serde_json::{Value, json, Map};
2use std::iter::Peekable;
3use std::str::Chars;
4
5#[derive(Debug, PartialEq)]
6pub enum ReaderError {
7    UnexpectedChar(char),
8    UnterminatedString,
9    UnbalancedParenthesis,
10    InvalidNumber(String),
11}
12
13#[derive(Debug, Clone)]
14enum Val {
15    Literal(Value),
16    Symbol(String),
17}
18
19pub fn read(input: &str) -> Result<Value, ReaderError> {
20    let mut tokens = Tokenizer::new(input);
21    let mut results = Vec::new();
22    
23    while let Some(token) = tokens.next_token()? {
24        results.push(parse_token_wrapped(token, &mut tokens)?);
25    }
26
27    let final_vals: Vec<Value> = results.into_iter().map(|v| v_to_json_literal(v)).collect();
28
29    if final_vals.len() == 1 {
30        Ok(final_vals[0].clone())
31    } else {
32        let mut do_block = vec![json!("do")];
33        do_block.extend(final_vals);
34        Ok(Value::Array(do_block))
35    }
36}
37
38pub fn read_program(input: &str) -> Result<Value, ReaderError> {
39    let program_val = read(input)?;
40    Ok(json!({
41        "state": { "client": {}, "server": {} },
42        "program": program_val
43    }))
44}
45
46fn v_to_json_expr(v: Val) -> Value {
47    match v {
48        Val::Literal(v) => v,
49        Val::Symbol(s) => {
50            if s == "true" { json!(true) }
51            else if s == "false" { json!(false) }
52            else if s == "null" { json!(null) }
53            else { json!(["var", s]) }
54        }
55    }
56}
57
58fn v_to_json_literal(v: Val) -> Value {
59    match v {
60        Val::Literal(v) => v,
61        Val::Symbol(s) => {
62            if s == "true" { json!(true) }
63            else if s == "false" { json!(false) }
64            else if s == "null" { json!(null) }
65            else { json!(s) }
66        }
67    }
68}
69
70fn parse_token_wrapped(first: Token, tokens: &mut Tokenizer) -> Result<Val, ReaderError> {
71    match first {
72        Token::LParen => {
73            let mut list = Vec::new();
74            let mut is_first = true;
75            
76            while let Some(t) = tokens.peek_token()? {
77                if t == Token::RParen {
78                    tokens.next_token()?;
79                    return Ok(Val::Literal(Value::Array(list)));
80                }
81                
82                let next_t = tokens.next_token()?.unwrap();
83                let val_wrapped = parse_token_wrapped(next_t, tokens)?;
84                
85                let op_name = list.first().and_then(|v| v.as_str()).unwrap_or("");
86                let pos = list.len();
87
88                // If we have an operator but no arguments, and we're closing, 
89                // we should have handled it. But if we are reading, we continue.
90                
91                let final_val = match val_wrapped {
92                    Val::Symbol(s) => {
93                        if is_first {
94                            json!(s)
95                        } else {
96                            // Keyword check
97                            if s == "true" { json!(true) }
98                            else if s == "false" { json!(false) }
99                            else if s == "null" { json!(null) }
100                            else {
101                                // Context aware wrapping
102                                let is_literal_pos = (op_name == "set" || op_name == "def" || op_name == "call") && pos == 1;
103                                if is_literal_pos {
104                                    json!(s)
105                                } else {
106                                    json!(["var", s])
107                                }
108                            }
109                        }
110                    }
111                    Val::Literal(v) => v,
112                };
113
114                list.push(final_val);
115                is_first = false;
116            }
117            
118            // Post-processing: Ensure minimum argument counts for common operators
119            if let Some(Value::String(op)) = list.first() {
120                if (op == "!" || op == "not" || op == "len") && list.len() == 1 {
121                    list.push(json!(null));
122                }
123            }
124            
125            Err(ReaderError::UnbalancedParenthesis)
126        }
127        Token::RParen => Err(ReaderError::UnbalancedParenthesis),
128        Token::LBracket => {
129            let mut list = Vec::new();
130            while let Some(t) = tokens.peek_token()? {
131                if t == Token::RBracket {
132                    tokens.next_token()?;
133                    return Ok(Val::Literal(Value::Array(list)));
134                }
135                let next_t = tokens.next_token()?.unwrap();
136                list.push(v_to_json_expr(parse_token_wrapped(next_t, tokens)?));
137            }
138            Err(ReaderError::UnbalancedParenthesis)
139        }
140        Token::RBracket => Err(ReaderError::UnbalancedParenthesis),
141        Token::LBrace => {
142            let mut map = Map::new();
143            while let Some(t) = tokens.next_token()? {
144                match t {
145                    Token::RBrace => return Ok(Val::Literal(Value::Object(map))),
146                    Token::Symbol(key) | Token::String(key) => {
147                        if let Some(Token::Symbol(s)) = tokens.peek_token()? {
148                            if s == ":" { tokens.next_token()?; }
149                        }
150                        let val_token = tokens.next_token()?.ok_or(ReaderError::UnexpectedChar('}'))?;
151                        let val = v_to_json_expr(parse_token_wrapped(val_token, tokens)?);
152                        map.insert(key, val);
153                    }
154                    _ => return Err(ReaderError::UnexpectedChar('?')),
155                }
156            }
157            Err(ReaderError::UnexpectedChar('}'))
158        }
159        Token::RBrace => Err(ReaderError::UnexpectedChar('}')),
160        Token::String(s) => Ok(Val::Literal(json!(s))),
161        Token::Number(n) => Ok(Val::Literal(json!(n))),
162        Token::Symbol(s) => Ok(Val::Symbol(s)),
163    }
164}
165
166#[derive(Debug, PartialEq, Clone)]
167enum Token {
168    LParen, RParen, LBrace, RBrace, LBracket, RBracket,
169    String(String),
170    Number(f64),
171    Symbol(String),
172}
173
174struct Tokenizer<'a> {
175    chars: Peekable<Chars<'a>>,
176    peeked: Option<Token>,
177}
178
179impl<'a> Tokenizer<'a> {
180    fn new(input: &'a str) -> Self {
181        Self { chars: input.chars().peekable(), peeked: None }
182    }
183
184    fn peek_token(&mut self) -> Result<Option<Token>, ReaderError> {
185        if self.peeked.is_none() {
186            self.peeked = self.next_token_inner()?;
187        }
188        Ok(self.peeked.clone())
189    }
190
191    fn next_token(&mut self) -> Result<Option<Token>, ReaderError> {
192        if let Some(t) = self.peeked.take() {
193            return Ok(Some(t));
194        }
195        self.next_token_inner()
196    }
197
198    fn next_token_inner(&mut self) -> Result<Option<Token>, ReaderError> {
199        loop {
200            while let Some(&c) = self.chars.peek() {
201                if c.is_whitespace() || c == ',' || c == ':' {
202                    self.chars.next();
203                    continue;
204                }
205                break;
206            }
207
208            if let Some(&'#') = self.chars.peek() {
209                // Skip comment line
210                while let Some(c) = self.chars.next() {
211                    if c == '\n' { break; }
212                }
213                continue;
214            }
215            break;
216        }
217
218        let c = match self.chars.next() {
219            Some(c) => c,
220            None => return Ok(None),
221        };
222
223        match c {
224            '(' => Ok(Some(Token::LParen)),
225            ')' => Ok(Some(Token::RParen)),
226            '{' => Ok(Some(Token::LBrace)),
227            '}' => Ok(Some(Token::RBrace)),
228            '[' => Ok(Some(Token::LBracket)),
229            ']' => Ok(Some(Token::RBracket)),
230            '\'' | '"' => {
231                let quote = c;
232                let mut s = String::new();
233                let mut escaped = false;
234                while let Some(nc) = self.chars.next() {
235                    if escaped {
236                        s.push(nc);
237                        escaped = false;
238                    } else if nc == '\\' {
239                        escaped = true;
240                    } else if nc == quote {
241                        return Ok(Some(Token::String(s)));
242                    } else {
243                        s.push(nc);
244                    }
245                }
246                Err(ReaderError::UnterminatedString)
247            }
248            _ if c.is_digit(10) || (c == '-' && self.chars.peek().is_some_and(|&nc| nc.is_digit(10))) => {
249                let mut s = c.to_string();
250                while let Some(&nc) = self.chars.peek() {
251                    if nc.is_digit(10) || nc == '.' {
252                        s.push(self.chars.next().unwrap());
253                    } else { break; }
254                }
255                let n = s.parse::<f64>().map_err(|_| ReaderError::InvalidNumber(s))?;
256                Ok(Some(Token::Number(n)))
257            }
258            _ => {
259                let mut s = c.to_string();
260                while let Some(&nc) = self.chars.peek() {
261                    if nc.is_whitespace() || nc == '(' || nc == ')' || nc == '{' || nc == '}' || nc == '[' || nc == ']' || nc == ',' || nc == ':' {
262                        break;
263                    }
264                    s.push(self.chars.next().unwrap());
265                }
266                Ok(Some(Token::Symbol(s)))
267            }
268        }
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_basic_read() {
278        let input = "(+ 1 2)";
279        let val = read(input).unwrap();
280        assert_eq!(val, json!(["+", 1.0, 2.0]));
281    }
282
283    #[test]
284    fn test_symbol_var_wrapping() {
285        let input = "(set client.count (+ client.count 1))";
286        let val = read(input).unwrap();
287        assert_eq!(val, json!(["set", "client.count", ["+", ["var", "client.count"], 1.0]]));
288    }
289
290    #[test]
291    fn test_nested_objects() {
292        let input = "(call api.post \"/url\" { status: \"ok\", val: (+ x 1) })";
293        let val = read(input).unwrap();
294        assert_eq!(val, json!([
295            "call", "api.post", "/url",
296            {
297                "status": "ok",
298                "val": ["+", ["var", "x"], 1.0]
299            }
300        ]));
301    }
302}