Skip to main content

lykn_cli/
reader.rs

1/// lykn s-expression reader
2///
3/// Parses lykn source text into a tree of SExpr nodes.
4
5#[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; // skip (
57    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; // skip )
67    }
68    SExpr::List(values)
69}
70
71fn read_string(chars: &[char], pos: &mut usize) -> SExpr {
72    *pos += 1; // skip opening "
73    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; // skip closing "
91    }
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    // Try parsing as number
108    if let Ok(n) = value.parse::<f64>() {
109        SExpr::Num(n)
110    } else {
111        SExpr::Atom(value)
112    }
113}