Skip to main content

synapse_parser/ast/
builder.rs

1use pest::{Parser, error::Error, iterators::Pair};
2
3use crate::synapse::{Rule, SynapseParser};
4
5use super::*;
6
7// ── Entry point ───────────────────────────────────────────────────────────────
8
9pub fn parse(input: &str) -> Result<SynFile, Error<Rule>> {
10    let file_pair = SynapseParser::parse(Rule::file, input)?.next().unwrap();
11    Ok(build_file(file_pair))
12}
13
14// ── Builders ──────────────────────────────────────────────────────────────────
15
16fn build_file(pair: Pair<Rule>) -> SynFile {
17    let items = pair
18        .into_inner()
19        .filter_map(|p| match p.as_rule() {
20            Rule::namespace_decl => Some(Item::Namespace(build_namespace(p))),
21            Rule::import_decl => Some(Item::Import(build_import(p))),
22            Rule::const_decl => Some(Item::Const(build_const(p))),
23            Rule::enum_def => Some(Item::Enum(build_enum(p))),
24            Rule::struct_def => Some(Item::Struct(build_struct(p))),
25            Rule::table_def => Some(Item::Table(build_struct(p))),
26            Rule::command_def => Some(Item::Command(build_packet(p, PacketKind::Command))),
27            Rule::telemetry_def => Some(Item::Telemetry(build_packet(p, PacketKind::Telemetry))),
28            Rule::message_def => Some(Item::Message(build_packet(p, PacketKind::Message))),
29            Rule::EOI => None,
30            r => unreachable!("unexpected rule: {:?}", r),
31        })
32        .collect();
33    SynFile { items }
34}
35
36fn build_namespace(pair: Pair<Rule>) -> NamespaceDecl {
37    let scoped = pair.into_inner().next().unwrap();
38    NamespaceDecl {
39        name: build_scoped_ident(scoped),
40    }
41}
42
43fn build_import(pair: Pair<Rule>) -> ImportDecl {
44    let s = pair.into_inner().next().unwrap().as_str();
45    ImportDecl {
46        path: s[1..s.len() - 1].to_string(),
47    }
48}
49
50fn build_const(pair: Pair<Rule>) -> ConstDecl {
51    let mut inner = pair.into_inner().peekable();
52    let doc = extract_doc(&mut inner);
53    let attrs = extract_attrs(&mut inner);
54    let name = inner.next().unwrap().as_str().to_string();
55    let ty = build_type_expr(inner.next().unwrap());
56    let value = build_literal(inner.next().unwrap());
57    ConstDecl {
58        name,
59        ty,
60        value,
61        doc,
62        attrs,
63    }
64}
65
66fn build_enum(pair: Pair<Rule>) -> EnumDef {
67    let mut inner = pair.into_inner().peekable();
68    let doc = extract_doc(&mut inner);
69    let attrs = extract_attrs(&mut inner);
70    let first = inner.next().unwrap();
71    let (repr, name) = if first.as_rule() == Rule::primitive_type {
72        let repr = build_primitive_type(first);
73        (Some(repr), inner.next().unwrap().as_str().to_string())
74    } else {
75        (None, first.as_str().to_string())
76    };
77    let variants = inner.map(build_enum_variant).collect();
78    EnumDef {
79        name,
80        repr,
81        variants,
82        doc,
83        attrs,
84    }
85}
86
87fn build_enum_variant(pair: Pair<Rule>) -> EnumVariant {
88    let mut inner = pair.into_inner().peekable();
89    let doc = extract_doc(&mut inner);
90    let name = inner.next().unwrap().as_str().to_string();
91    let value = inner.next().map(|p| p.as_str().parse::<i64>().unwrap());
92    EnumVariant { name, value, doc }
93}
94
95fn build_struct(pair: Pair<Rule>) -> StructDef {
96    let mut inner = pair.into_inner().peekable();
97    let doc = extract_doc(&mut inner);
98    let attrs = extract_attrs(&mut inner);
99    let name = inner.next().unwrap().as_str().to_string();
100    let fields = inner.map(build_field).collect();
101    StructDef {
102        name,
103        fields,
104        doc,
105        attrs,
106    }
107}
108
109fn build_packet(pair: Pair<Rule>, kind: PacketKind) -> MessageDef {
110    let mut inner = pair.into_inner().peekable();
111    let doc = extract_doc(&mut inner);
112    let attrs = extract_attrs(&mut inner);
113    let name = inner.next().unwrap().as_str().to_string();
114    let fields = inner.map(build_field).collect();
115    MessageDef {
116        kind,
117        name,
118        fields,
119        doc,
120        attrs,
121    }
122}
123
124fn build_field(pair: Pair<Rule>) -> FieldDef {
125    let mut inner = pair.into_inner().peekable();
126    let doc = extract_doc(&mut inner);
127    let name = inner.next().unwrap().as_str().to_string();
128
129    let next = inner.next().unwrap();
130    let (optional, type_pair) = if next.as_rule() == Rule::optional_marker {
131        (true, inner.next().unwrap())
132    } else {
133        (false, next)
134    };
135
136    let ty = build_type_expr(type_pair);
137    let default = inner.next().map(build_literal);
138
139    FieldDef {
140        name,
141        optional,
142        ty,
143        default,
144        doc,
145    }
146}
147
148/// Consume a leading `doc_block` (if present) and return the trimmed doc lines.
149fn extract_doc<'i>(
150    inner: &mut std::iter::Peekable<impl Iterator<Item = Pair<'i, Rule>>>,
151) -> Vec<String> {
152    if inner.peek().map(|p| p.as_rule()) == Some(Rule::doc_block) {
153        inner
154            .next()
155            .unwrap()
156            .into_inner()
157            .map(|p| {
158                p.as_str()
159                    .strip_prefix("///")
160                    .unwrap_or("")
161                    .trim()
162                    .to_string()
163            })
164            .collect()
165    } else {
166        vec![]
167    }
168}
169
170/// Consume zero or more leading `attribute` pairs and return them.
171fn extract_attrs<'i>(
172    inner: &mut std::iter::Peekable<impl Iterator<Item = Pair<'i, Rule>>>,
173) -> Vec<Attribute> {
174    let mut attrs = vec![];
175    while inner.peek().map(|p| p.as_rule()) == Some(Rule::attribute) {
176        let attr = inner.next().unwrap();
177        let mut ai = attr.into_inner();
178        let name = ai.next().unwrap().as_str().to_string();
179        let value = build_literal(ai.next().unwrap());
180        attrs.push(Attribute { name, value });
181    }
182    attrs
183}
184
185fn build_type_expr(pair: Pair<Rule>) -> TypeExpr {
186    let mut inner = pair.into_inner();
187    let base = build_base_type(inner.next().unwrap());
188    let array = inner.next().map(build_array_suffix);
189    TypeExpr { base, array }
190}
191
192fn build_base_type(pair: Pair<Rule>) -> BaseType {
193    let inner = pair.into_inner().next().unwrap();
194    match inner.as_rule() {
195        Rule::string_type => BaseType::String,
196        Rule::primitive_type => BaseType::Primitive(build_primitive_type(inner)),
197        Rule::type_ref => BaseType::Ref(build_scoped_ident(inner.into_inner().next().unwrap())),
198        r => unreachable!("unexpected base_type rule: {:?}", r),
199    }
200}
201
202fn build_primitive_type(pair: Pair<Rule>) -> PrimitiveType {
203    match pair.as_str() {
204        "f32" => PrimitiveType::F32,
205        "f64" => PrimitiveType::F64,
206        "i8" => PrimitiveType::I8,
207        "i16" => PrimitiveType::I16,
208        "i32" => PrimitiveType::I32,
209        "i64" => PrimitiveType::I64,
210        "u8" => PrimitiveType::U8,
211        "u16" => PrimitiveType::U16,
212        "u32" => PrimitiveType::U32,
213        "u64" => PrimitiveType::U64,
214        "bool" => PrimitiveType::Bool,
215        "bytes" => PrimitiveType::Bytes,
216        s => unreachable!("unknown primitive: {}", s),
217    }
218}
219
220fn build_array_suffix(pair: Pair<Rule>) -> ArraySuffix {
221    match pair.into_inner().next() {
222        None => ArraySuffix::Dynamic,
223        Some(p) => {
224            let inner = p.into_inner().next().unwrap();
225            match inner.as_rule() {
226                Rule::bounded_size => {
227                    let n = inner
228                        .into_inner()
229                        .next()
230                        .unwrap()
231                        .as_str()
232                        .parse::<u64>()
233                        .unwrap();
234                    ArraySuffix::Bounded(n)
235                }
236                Rule::pos_int => ArraySuffix::Fixed(inner.as_str().parse::<u64>().unwrap()),
237                r => unreachable!("unexpected array_size rule: {:?}", r),
238            }
239        }
240    }
241}
242
243fn build_literal(pair: Pair<Rule>) -> Literal {
244    let inner = pair.into_inner().next().unwrap();
245    match inner.as_rule() {
246        Rule::float_lit => Literal::Float(inner.as_str().parse::<f64>().unwrap()),
247        Rule::hex_lit => {
248            let s = inner.as_str();
249            let digits = &s[2..]; // strip 0x / 0X
250            Literal::Hex(u64::from_str_radix(digits, 16).unwrap())
251        }
252        Rule::int_lit => Literal::Int(inner.as_str().parse::<i64>().unwrap()),
253        Rule::bool_lit => Literal::Bool(inner.as_str() == "true"),
254        Rule::string_lit => {
255            let s = inner.as_str();
256            Literal::Str(unescape(&s[1..s.len() - 1]))
257        }
258        Rule::ident_lit => Literal::Ident(build_scoped_ident(inner.into_inner().next().unwrap())),
259        r => unreachable!("unexpected literal rule: {:?}", r),
260    }
261}
262
263fn build_scoped_ident(pair: Pair<Rule>) -> ScopedIdent {
264    pair.into_inner().map(|p| p.as_str().to_string()).collect()
265}
266
267fn unescape(s: &str) -> String {
268    let mut out = String::with_capacity(s.len());
269    let mut chars = s.chars();
270    while let Some(c) = chars.next() {
271        if c == '\\' {
272            match chars.next() {
273                Some('n') => out.push('\n'),
274                Some('t') => out.push('\t'),
275                Some('r') => out.push('\r'),
276                Some('\\') => out.push('\\'),
277                Some('"') => out.push('"'),
278                Some(c) => {
279                    out.push('\\');
280                    out.push(c);
281                }
282                None => out.push('\\'),
283            }
284        } else {
285            out.push(c);
286        }
287    }
288    out
289}