1#[derive(Debug, Clone)]
6pub enum SExpr {
7 Atom(String),
8 Str(String),
9 Num(f64),
10 List(Vec<SExpr>),
11}
12
13pub fn read(source: &str) -> Vec<SExpr> {
14 let chars: Vec<char> = source.chars().collect();
15 let mut pos = 0;
16 let mut exprs = Vec::new();
17
18 skip_ws(&chars, &mut pos);
19 while pos < chars.len() {
20 if let Some(expr) = read_expr(&chars, &mut pos) {
21 exprs.push(expr);
22 }
23 skip_ws(&chars, &mut pos);
24 }
25 exprs
26}
27
28fn skip_ws(chars: &[char], pos: &mut usize) {
29 while *pos < chars.len() {
30 match chars[*pos] {
31 ' ' | '\t' | '\n' | '\r' => *pos += 1,
32 ';' => {
33 while *pos < chars.len() && chars[*pos] != '\n' {
34 *pos += 1;
35 }
36 }
37 _ => break,
38 }
39 }
40}
41
42fn read_expr(chars: &[char], pos: &mut usize) -> Option<SExpr> {
43 skip_ws(chars, pos);
44 if *pos >= chars.len() {
45 return None;
46 }
47
48 match chars[*pos] {
49 '(' => Some(read_list(chars, pos)),
50 '"' => Some(read_string(chars, pos)),
51 _ => Some(read_atom_or_num(chars, pos)),
52 }
53}
54
55fn read_list(chars: &[char], pos: &mut usize) -> SExpr {
56 *pos += 1; let mut values = Vec::new();
58 skip_ws(chars, pos);
59 while *pos < chars.len() && chars[*pos] != ')' {
60 if let Some(expr) = read_expr(chars, pos) {
61 values.push(expr);
62 }
63 skip_ws(chars, pos);
64 }
65 if *pos < chars.len() {
66 *pos += 1; }
68 SExpr::List(values)
69}
70
71fn read_string(chars: &[char], pos: &mut usize) -> SExpr {
72 *pos += 1; let mut value = String::new();
74 while *pos < chars.len() && chars[*pos] != '"' {
75 if chars[*pos] == '\\' && *pos + 1 < chars.len() {
76 *pos += 1;
77 match chars[*pos] {
78 'n' => value.push('\n'),
79 't' => value.push('\t'),
80 '\\' => value.push('\\'),
81 '"' => value.push('"'),
82 c => value.push(c),
83 }
84 } else {
85 value.push(chars[*pos]);
86 }
87 *pos += 1;
88 }
89 if *pos < chars.len() {
90 *pos += 1; }
92 SExpr::Str(value)
93}
94
95fn read_atom_or_num(chars: &[char], pos: &mut usize) -> SExpr {
96 let mut value = String::new();
97 while *pos < chars.len() {
98 match chars[*pos] {
99 ' ' | '\t' | '\n' | '\r' | '(' | ')' | ';' => break,
100 c => {
101 value.push(c);
102 *pos += 1;
103 }
104 }
105 }
106
107 if let Ok(n) = value.parse::<f64>() {
109 SExpr::Num(n)
110 } else {
111 SExpr::Atom(value)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn empty_input() {
121 assert!(read("").is_empty());
122 }
123
124 #[test]
125 fn whitespace_only() {
126 assert!(read(" \t\n ").is_empty());
127 }
128
129 #[test]
130 fn single_atom() {
131 let exprs = read("hello");
132 assert_eq!(exprs.len(), 1);
133 assert!(matches!(&exprs[0], SExpr::Atom(s) if s == "hello"));
134 }
135
136 #[test]
137 fn integer_number() {
138 let exprs = read("42");
139 assert_eq!(exprs.len(), 1);
140 assert!(matches!(&exprs[0], SExpr::Num(n) if *n == 42.0));
141 }
142
143 #[test]
144 fn float_number() {
145 let exprs = read("3.14");
146 assert_eq!(exprs.len(), 1);
147 assert!(matches!(&exprs[0], SExpr::Num(n) if (*n - 3.14).abs() < f64::EPSILON));
148 }
149
150 #[test]
151 fn negative_number() {
152 let exprs = read("-7");
153 assert_eq!(exprs.len(), 1);
154 assert!(matches!(&exprs[0], SExpr::Num(n) if *n == -7.0));
155 }
156
157 #[test]
158 fn simple_string() {
159 let exprs = read("\"hello world\"");
160 assert_eq!(exprs.len(), 1);
161 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "hello world"));
162 }
163
164 #[test]
165 fn string_escape_newline() {
166 let exprs = read("\"a\\nb\"");
167 assert_eq!(exprs.len(), 1);
168 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "a\nb"));
169 }
170
171 #[test]
172 fn string_escape_tab() {
173 let exprs = read("\"a\\tb\"");
174 assert_eq!(exprs.len(), 1);
175 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "a\tb"));
176 }
177
178 #[test]
179 fn string_escape_backslash() {
180 let exprs = read("\"a\\\\b\"");
181 assert_eq!(exprs.len(), 1);
182 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "a\\b"));
183 }
184
185 #[test]
186 fn string_escape_quote() {
187 let exprs = read("\"a\\\"b\"");
188 assert_eq!(exprs.len(), 1);
189 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "a\"b"));
190 }
191
192 #[test]
193 fn string_unknown_escape() {
194 let exprs = read("\"a\\xb\"");
195 assert_eq!(exprs.len(), 1);
196 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "axb"));
197 }
198
199 #[test]
200 fn simple_list() {
201 let exprs = read("(+ 1 2)");
202 assert_eq!(exprs.len(), 1);
203 match &exprs[0] {
204 SExpr::List(vals) => {
205 assert_eq!(vals.len(), 3);
206 assert!(matches!(&vals[0], SExpr::Atom(s) if s == "+"));
207 assert!(matches!(&vals[1], SExpr::Num(n) if *n == 1.0));
208 assert!(matches!(&vals[2], SExpr::Num(n) if *n == 2.0));
209 }
210 _ => panic!("expected list"),
211 }
212 }
213
214 #[test]
215 fn empty_list() {
216 let exprs = read("()");
217 assert_eq!(exprs.len(), 1);
218 match &exprs[0] {
219 SExpr::List(vals) => assert!(vals.is_empty()),
220 _ => panic!("expected list"),
221 }
222 }
223
224 #[test]
225 fn nested_list() {
226 let exprs = read("(define x (+ 1 2))");
227 assert_eq!(exprs.len(), 1);
228 match &exprs[0] {
229 SExpr::List(vals) => {
230 assert_eq!(vals.len(), 3);
231 assert!(matches!(&vals[2], SExpr::List(_)));
232 }
233 _ => panic!("expected list"),
234 }
235 }
236
237 #[test]
238 fn multiple_top_level() {
239 let exprs = read("a b c");
240 assert_eq!(exprs.len(), 3);
241 }
242
243 #[test]
244 fn line_comment() {
245 let exprs = read("; comment\nhello");
246 assert_eq!(exprs.len(), 1);
247 assert!(matches!(&exprs[0], SExpr::Atom(s) if s == "hello"));
248 }
249
250 #[test]
251 fn inline_comment() {
252 let exprs = read("a ; comment\nb");
253 assert_eq!(exprs.len(), 2);
254 }
255
256 #[test]
257 fn tab_whitespace() {
258 let exprs = read("(a\tb)");
259 assert_eq!(exprs.len(), 1);
260 match &exprs[0] {
261 SExpr::List(vals) => assert_eq!(vals.len(), 2),
262 _ => panic!("expected list"),
263 }
264 }
265
266 #[test]
267 fn unterminated_string() {
268 let exprs = read("\"unterminated");
270 assert_eq!(exprs.len(), 1);
271 assert!(matches!(&exprs[0], SExpr::Str(s) if s == "unterminated"));
272 }
273
274 #[test]
275 fn unterminated_list() {
276 let exprs = read("(a b");
278 assert_eq!(exprs.len(), 1);
279 match &exprs[0] {
280 SExpr::List(vals) => assert_eq!(vals.len(), 2),
281 _ => panic!("expected list"),
282 }
283 }
284}