Skip to main content

atmos/os_lib/aura/
parser.rs

1use super::lexer::Token;
2use super::number::{BigInt, Ratio};
3use alloc::boxed::Box;
4use alloc::string::String;
5use alloc::vec;
6use alloc::vec::Vec;
7
8#[derive(Debug, Clone)]
9pub enum AST {
10    Symbol(String),
11    Num(BigInt),
12    Ratio(Ratio),
13    Str(String),
14    List(Vec<AST>),
15    Dict(Vec<(String, AST)>),
16    FuncCall(Box<AST>, Vec<AST>),
17}
18
19pub fn parse(tokens: &[Token]) -> Result<Vec<AST>, String> {
20    let mut asts = Vec::new();
21    let mut pos = 0;
22    while pos < tokens.len() {
23        let (ast, next_pos) = parse_expr(tokens, pos, false)?;
24        asts.push(ast);
25        pos = next_pos;
26    }
27    Ok(asts)
28}
29
30fn parse_expr(tokens: &[Token], mut pos: usize, in_list: bool) -> Result<(AST, usize), String> {
31    if pos >= tokens.len() {
32        return Err(String::from("Unexpected end of input"));
33    }
34
35    match &tokens[pos] {
36        Token::Ident(name) => {
37            // Check if name contains dot (e.g., "net.udp")
38            let has_dot = name.contains('.');
39
40            // Check if it is a dictionary access: Ident(name) followed by Colon
41            if pos + 1 < tokens.len() && tokens[pos + 1] == Token::Colon {
42                if pos + 2 < tokens.len() {
43                    let key_str = match &tokens[pos + 2] {
44                        Token::Ident(k) => k.clone(),
45                        Token::Str(s) => s.clone(),
46                        _ => return Err(String::from("Expected identifier or string after colon")),
47                    };
48                    pos += 3; // Ident, Colon, Key
49                    Ok((
50                        AST::FuncCall(
51                            Box::new(AST::Symbol(String::from("get"))),
52                            alloc::vec![AST::Symbol(name.clone()), AST::Str(key_str)],
53                        ),
54                        pos,
55                    ))
56                } else {
57                    Err(String::from("Missing key after colon"))
58                }
59            }
60            // Check if it is a function call: Ident(name) followed by LParen (must not have leading space)
61            else if !in_list
62                && pos + 1 < tokens.len()
63                && matches!(
64                    tokens[pos + 1],
65                    Token::LParen {
66                        leading_space: false
67                    }
68                )
69            {
70                let func_expr = if has_dot {
71                    if let Some((obj_name, key_name)) = name.rsplit_once('.') {
72                        AST::FuncCall(
73                            Box::new(AST::Symbol(String::from("get"))),
74                            alloc::vec![
75                                AST::Symbol(String::from(obj_name)),
76                                AST::Str(String::from(key_name))
77                            ],
78                        )
79                    } else {
80                        AST::Symbol(name.clone())
81                    }
82                } else {
83                    AST::Symbol(name.clone())
84                };
85
86                pos += 2; // Skip Ident and LParen
87
88                let mut args = Vec::new();
89                while pos < tokens.len() && tokens[pos] != Token::RParen {
90                    let (expr, next_pos) = parse_expr(tokens, pos, false)?;
91                    args.push(expr);
92                    pos = next_pos;
93                }
94
95                if pos >= tokens.len() {
96                    return Err(String::from("Missing closing parenthesis ')'"));
97                }
98                pos += 1; // Skip RParen
99                Ok((AST::FuncCall(Box::new(func_expr), args), pos))
100            } else if has_dot {
101                // Property access without function call (e.g., net.info)
102                if let Some((obj_name, key_name)) = name.rsplit_once('.') {
103                    Ok((
104                        AST::FuncCall(
105                            Box::new(AST::Symbol(String::from("get"))),
106                            alloc::vec![
107                                AST::Symbol(String::from(obj_name)),
108                                AST::Str(String::from(key_name))
109                            ],
110                        ),
111                        pos + 1,
112                    ))
113                } else {
114                    Ok((AST::Symbol(name.clone()), pos + 1))
115                }
116            } else {
117                Ok((AST::Symbol(name.clone()), pos + 1))
118            }
119        }
120        Token::Num(num) => Ok((AST::Num(num.clone()), pos + 1)),
121        Token::Ratio(ratio) => Ok((AST::Ratio(ratio.clone()), pos + 1)),
122        Token::Str(s) => Ok((AST::Str(s.clone()), pos + 1)),
123        Token::TemplateStr(s) => {
124            let mut template_parts = String::new();
125            let mut expr_asts = Vec::new();
126            let mut chars = s.chars().peekable();
127
128            while let Some(c) = chars.next() {
129                if c == '$' {
130                    if chars.peek() == Some(&'$') {
131                        chars.next(); // consume second '$'
132                        template_parts.push('$');
133                    } else if chars.peek() == Some(&'{') {
134                        chars.next(); // consume '{'
135                        template_parts.push_str("${}");
136
137                        let mut expr_str = String::new();
138                        let mut format_spec = String::new();
139                        let mut brace_depth = 1;
140                        let mut in_format = false;
141
142                        for ec in chars.by_ref() {
143                            if ec == '{' {
144                                brace_depth += 1;
145                            } else if ec == '}' {
146                                brace_depth -= 1;
147                                if brace_depth == 0 {
148                                    break;
149                                }
150                            }
151
152                            if ec == ':' && brace_depth == 1 {
153                                in_format = true;
154                                continue;
155                            }
156
157                            if in_format {
158                                format_spec.push(ec);
159                            } else {
160                                expr_str.push(ec);
161                            }
162                        }
163
164                        let sub_tokens = super::lexer::tokenize(&expr_str);
165                        let sub_asts = super::parser::parse(&sub_tokens)?;
166                        let expr_ast = if sub_asts.is_empty() {
167                            AST::Symbol(String::from("nil"))
168                        } else {
169                            sub_asts[0].clone()
170                        };
171
172                        expr_asts.push((expr_ast, format_spec));
173                    } else {
174                        template_parts.push('$');
175                    }
176                } else {
177                    template_parts.push(c);
178                }
179            }
180
181            let mut list_exprs = vec![AST::Symbol(String::from("list"))];
182            let mut list_formats = vec![AST::Symbol(String::from("list"))];
183            for (ast, fmt) in expr_asts {
184                list_exprs.push(ast);
185                list_formats.push(AST::Str(fmt));
186            }
187
188            Ok((
189                AST::FuncCall(
190                    Box::new(AST::Symbol(String::from("os.template-eval"))),
191                    vec![
192                        AST::Str(template_parts),
193                        AST::List(list_exprs),
194                        AST::List(list_formats),
195                    ],
196                ),
197                pos + 1,
198            ))
199        }
200        Token::LParen { .. } => {
201            // Normal list (a b c)
202            let mut list = Vec::new();
203            pos += 1; // Skip LParen
204
205            while pos < tokens.len() && tokens[pos] != Token::RParen {
206                let (expr, next_pos) = parse_expr(tokens, pos, true)?;
207                list.push(expr);
208                pos = next_pos;
209            }
210
211            if pos >= tokens.len() {
212                return Err(String::from("Missing closing parenthesis ')'"));
213            }
214            pos += 1; // Skip RParen
215            Ok((AST::List(list), pos))
216        }
217        Token::RParen => Err(String::from("Unexpected closing parenthesis ')'")),
218        Token::LBrace => {
219            let mut dict = Vec::new();
220            pos += 1; // Skip LBrace
221
222            while pos < tokens.len() && tokens[pos] != Token::RBrace {
223                // Expect a string key
224                let key_str = match &tokens[pos] {
225                    Token::Str(s) => s.clone(),
226                    _ => return Err(String::from("Expected string key in dictionary")),
227                };
228                pos += 1;
229
230                // Expect colon
231                if pos >= tokens.len() || tokens[pos] != Token::Colon {
232                    return Err(String::from("Expected ':' after dictionary key"));
233                }
234                pos += 1; // Skip colon
235
236                // Expect value expression
237                let (val_expr, next_pos) = parse_expr(tokens, pos, false)?;
238                dict.push((key_str, val_expr));
239                pos = next_pos;
240            }
241
242            if pos >= tokens.len() {
243                return Err(String::from("Missing closing brace '}'"));
244            }
245            pos += 1; // Skip RBrace
246            Ok((AST::Dict(dict), pos))
247        }
248        Token::RBrace => Err(String::from("Unexpected closing brace '}'")),
249        Token::Colon => Err(String::from("Unexpected colon ':'")),
250    }
251}