1use anyhow::{bail, ensure, Context, Result};
5use std::collections::HashMap;
6
7pub const MAX_IDL: usize = 256 * 1024;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum Type {
11 Bool,
12 Byte,
13 I16,
14 I32,
15 I64,
16 Double,
17 String,
18 Binary,
19 Uuid,
20 List(Box<Type>),
21 Set(Box<Type>),
22 Map(Box<Type>, Box<Type>),
23 Struct(String),
25 Enum(String),
26}
27
28#[derive(Debug, Clone)]
29pub struct Field {
30 pub id: i16,
31 pub name: String,
32 pub ty: Type,
33 pub required: bool,
34}
35
36#[derive(Debug, Clone)]
37pub struct Function {
38 pub name: String,
39 pub oneway: bool,
40 pub returns: Option<Type>,
42 pub args: Vec<Field>,
43 pub throws: Vec<Field>,
44}
45
46#[derive(Debug, Clone, Default)]
47pub struct Service {
48 pub name: String,
49 pub extends: Option<String>,
50 pub functions: Vec<Function>,
51}
52
53#[derive(Debug, Default)]
54pub struct Idl {
55 pub structs: HashMap<String, Vec<Field>>,
56 pub unions: Vec<String>,
57 pub exceptions: Vec<String>,
58 pub enums: HashMap<String, Vec<(String, i32)>>,
59 pub services: Vec<Service>,
60 typedefs: HashMap<String, Type>,
61}
62
63impl Idl {
64 pub fn service(&self, name: Option<&str>) -> Result<&Service> {
65 match name {
66 Some(n) => self
67 .services
68 .iter()
69 .find(|s| s.name == n)
70 .with_context(|| format!("the IDL has no service {n}")),
71 None => self.services.last().context("the IDL defines no service"),
72 }
73 }
74 pub fn function<'a>(&'a self, service: &'a Service, name: &str) -> Option<&'a Function> {
76 let mut s = Some(service);
77 let mut hops = 0;
78 while let (Some(svc), true) = (s, hops < 16) {
79 if let Some(f) = svc.functions.iter().find(|f| f.name == name) {
80 return Some(f);
81 }
82 s = svc.extends.as_deref().and_then(|e| {
83 self.services
84 .iter()
85 .find(|x| x.name == e.rsplit('.').next().unwrap_or(e))
86 });
87 hops += 1;
88 }
89 None
90 }
91 pub fn enum_name(&self, name: &str, value: i32) -> Option<&str> {
92 self.enums
93 .get(name)?
94 .iter()
95 .find(|(_, v)| *v == value)
96 .map(|(n, _)| n.as_str())
97 }
98 pub fn enum_value(&self, name: &str, label: &str) -> Option<i32> {
99 self.enums
100 .get(name)?
101 .iter()
102 .find(|(n, _)| n == label)
103 .map(|(_, v)| *v)
104 }
105}
106
107#[derive(Debug, Clone, PartialEq)]
108enum Tok {
109 Ident(String),
110 Int(i64),
111 Str(String),
112 Sym(char),
113}
114
115fn lex(src: &str) -> Result<Vec<Tok>> {
116 let b: Vec<char> = src.chars().collect();
117 let mut out = Vec::new();
118 let mut i = 0;
119 while i < b.len() {
120 let c = b[i];
121 if c.is_whitespace() {
122 i += 1;
123 } else if c == '#' || (c == '/' && b.get(i + 1) == Some(&'/')) {
124 while i < b.len() && b[i] != '\n' {
125 i += 1;
126 }
127 } else if c == '/' && b.get(i + 1) == Some(&'*') {
128 i += 2;
129 while i + 1 < b.len() && !(b[i] == '*' && b[i + 1] == '/') {
130 i += 1;
131 }
132 i += 2;
133 } else if c == '"' || c == '\'' {
134 let q = c;
135 let start = i + 1;
136 i += 1;
137 while i < b.len() && b[i] != q {
138 i += 1;
139 }
140 ensure!(i < b.len(), "unterminated string literal");
141 out.push(Tok::Str(b[start..i].iter().collect()));
142 i += 1;
143 } else if c.is_ascii_digit()
144 || (c == '-' && b.get(i + 1).is_some_and(|d| d.is_ascii_digit()))
145 {
146 let start = i;
147 i += 1;
148 while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == '.' || b[i] == 'x') {
149 i += 1;
150 }
151 let text: String = b[start..i].iter().collect();
152 let n = if let Some(hex) = text.strip_prefix("0x") {
153 i64::from_str_radix(hex, 16)
154 } else {
155 text.parse::<i64>()
156 };
157 out.push(Tok::Int(n.unwrap_or(0)));
159 } else if c.is_alphabetic() || c == '_' {
160 let start = i;
161 while i < b.len() && (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '.') {
162 i += 1;
163 }
164 out.push(Tok::Ident(b[start..i].iter().collect()));
165 } else {
166 out.push(Tok::Sym(c));
167 i += 1;
168 }
169 }
170 Ok(out)
171}
172
173struct Parser {
174 toks: Vec<Tok>,
175 pos: usize,
176}
177
178impl Parser {
179 fn peek(&self) -> Option<&Tok> {
180 self.toks.get(self.pos)
181 }
182 fn next(&mut self) -> Result<Tok> {
183 let t = self
184 .toks
185 .get(self.pos)
186 .cloned()
187 .context("unexpected end of IDL")?;
188 self.pos += 1;
189 Ok(t)
190 }
191 fn ident(&mut self) -> Result<String> {
192 match self.next()? {
193 Tok::Ident(s) => Ok(s),
194 t => bail!("expected a name, found {t:?}"),
195 }
196 }
197 fn sym(&mut self, c: char) -> Result<()> {
198 match self.next()? {
199 Tok::Sym(s) if s == c => Ok(()),
200 t => bail!("expected '{c}', found {t:?}"),
201 }
202 }
203 fn eat(&mut self, c: char) -> bool {
204 if self.peek() == Some(&Tok::Sym(c)) {
205 self.pos += 1;
206 true
207 } else {
208 false
209 }
210 }
211 fn eat_word(&mut self, w: &str) -> bool {
212 if matches!(self.peek(), Some(Tok::Ident(s)) if s == w) {
213 self.pos += 1;
214 true
215 } else {
216 false
217 }
218 }
219 fn separator(&mut self) {
220 let _ = self.eat(',') || self.eat(';');
221 }
222 fn skip_value(&mut self) -> Result<()> {
224 match self.next()? {
225 Tok::Sym('[') | Tok::Sym('{') => {
226 let mut depth = 1;
227 while depth > 0 {
228 match self.next()? {
229 Tok::Sym('[') | Tok::Sym('{') => depth += 1,
230 Tok::Sym(']') | Tok::Sym('}') => depth -= 1,
231 _ => {}
232 }
233 }
234 Ok(())
235 }
236 _ => Ok(()),
237 }
238 }
239 fn skip_annotations(&mut self) -> Result<()> {
241 if self.eat('(') {
242 while !self.eat(')') {
243 self.next()?;
244 }
245 }
246 Ok(())
247 }
248 fn ty(&mut self, depth: usize) -> Result<Type> {
249 ensure!(depth < 32, "types nest too deeply");
250 let name = self.ident()?;
251 let t = match name.as_str() {
252 "bool" => Type::Bool,
253 "byte" | "i8" => Type::Byte,
254 "i16" => Type::I16,
255 "i32" => Type::I32,
256 "i64" => Type::I64,
257 "double" => Type::Double,
258 "string" => Type::String,
259 "binary" => Type::Binary,
260 "uuid" => Type::Uuid,
261 "list" | "set" => {
262 self.sym('<')?;
263 let e = self.ty(depth + 1)?;
264 self.sym('>')?;
265 if name == "list" {
266 Type::List(Box::new(e))
267 } else {
268 Type::Set(Box::new(e))
269 }
270 }
271 "map" => {
272 self.sym('<')?;
273 let k = self.ty(depth + 1)?;
274 self.sym(',')?;
275 let v = self.ty(depth + 1)?;
276 self.sym('>')?;
277 Type::Map(Box::new(k), Box::new(v))
278 }
279 other => Type::Struct(other.rsplit('.').next().unwrap_or(other).to_owned()),
280 };
281 self.skip_annotations()?;
282 Ok(t)
283 }
284 fn fields(&mut self, close: char) -> Result<Vec<Field>> {
285 let mut out: Vec<Field> = Vec::new();
286 while !self.eat(close) {
287 let id = match self.next()? {
288 Tok::Int(n) => i16::try_from(n).context("field id out of range")?,
289 t => {
290 bail!("expected a field id, found {t:?} (implicit field ids are not supported)")
291 }
292 };
293 self.sym(':')?;
294 let required = self.eat_word("required");
295 let _ = self.eat_word("optional");
296 let ty = self.ty(0)?;
297 let name = self.ident()?;
298 if self.eat('=') {
299 self.skip_value()?;
300 }
301 self.skip_annotations()?;
302 self.separator();
303 ensure!(
304 !out.iter().any(|f| f.id == id),
305 "field id {id} is used twice"
306 );
307 out.push(Field {
308 id,
309 name,
310 ty,
311 required,
312 });
313 }
314 Ok(out)
315 }
316}
317
318pub fn parse(src: &str) -> Result<Idl> {
319 ensure!(
320 src.len() <= MAX_IDL,
321 "the IDL is over {} KiB",
322 MAX_IDL / 1024
323 );
324 let mut p = Parser {
325 toks: lex(src)?,
326 pos: 0,
327 };
328 let mut idl = Idl::default();
329 while let Some(t) = p.peek().cloned() {
330 let Tok::Ident(word) = t else {
331 bail!("unexpected {t:?} at the top level")
332 };
333 p.pos += 1;
334 match word.as_str() {
335 "namespace" => {
336 p.next()?;
337 p.next()?;
338 }
339 "include" | "cpp_include" => {
340 bail!("include is not supported: give one self-contained IDL")
341 }
342 "typedef" => {
343 let t = p.ty(0)?;
344 let name = p.ident()?;
345 p.skip_annotations()?;
346 p.separator();
347 idl.typedefs.insert(name, t);
348 }
349 "const" => {
350 p.ty(0)?;
351 p.ident()?;
352 p.sym('=')?;
353 p.skip_value()?;
354 p.separator();
355 }
356 "enum" => {
357 let name = p.ident()?;
358 p.sym('{')?;
359 let mut values = Vec::new();
360 let mut next = 0i32;
361 while !p.eat('}') {
362 let label = p.ident()?;
363 if p.eat('=') {
364 next = match p.next()? {
365 Tok::Int(n) => i32::try_from(n).context("enum value out of range")?,
366 t => bail!("expected an enum value, found {t:?}"),
367 };
368 }
369 p.skip_annotations()?;
370 values.push((label, next));
371 next += 1;
372 p.separator();
373 }
374 p.skip_annotations()?;
375 idl.enums.insert(name, values);
376 }
377 "struct" | "union" | "exception" => {
378 let name = p.ident()?;
379 p.sym('{')?;
380 let fields = p.fields('}')?;
381 p.skip_annotations()?;
382 if word == "union" {
383 idl.unions.push(name.clone());
384 }
385 if word == "exception" {
386 idl.exceptions.push(name.clone());
387 }
388 idl.structs.insert(name, fields);
389 }
390 "service" => {
391 let name = p.ident()?;
392 let extends = if p.eat_word("extends") {
393 Some(p.ident()?)
394 } else {
395 None
396 };
397 p.sym('{')?;
398 let mut functions = Vec::new();
399 while !p.eat('}') {
400 let oneway = p.eat_word("oneway");
401 let returns = if p.eat_word("void") {
402 None
403 } else {
404 Some(p.ty(0)?)
405 };
406 let fname = p.ident()?;
407 p.sym('(')?;
408 let args = p.fields(')')?;
409 let throws = if p.eat_word("throws") {
410 p.sym('(')?;
411 p.fields(')')?
412 } else {
413 vec![]
414 };
415 p.skip_annotations()?;
416 p.separator();
417 ensure!(
418 !oneway || (returns.is_none() && throws.is_empty()),
419 "oneway {fname} must be void without throws"
420 );
421 functions.push(Function {
422 name: fname,
423 oneway,
424 returns,
425 args,
426 throws,
427 });
428 }
429 p.skip_annotations()?;
430 idl.services.push(Service {
431 name,
432 extends,
433 functions,
434 });
435 }
436 other => bail!("unsupported IDL construct {other:?}"),
437 }
438 }
439 idl.resolve()?;
440 Ok(idl)
441}
442
443impl Idl {
444 fn resolve(&mut self) -> Result<()> {
446 let typedefs = self.typedefs.clone();
447 let enums: Vec<String> = self.enums.keys().cloned().collect();
448 let structs: Vec<String> = self.structs.keys().cloned().collect();
449 let fix = |t: &mut Type| -> Result<()> { resolve_type(t, &typedefs, &enums, &structs, 0) };
450 for fields in self.structs.values_mut() {
451 for f in fields.iter_mut() {
452 fix(&mut f.ty)?;
453 }
454 }
455 for s in &mut self.services {
456 for f in &mut s.functions {
457 if let Some(r) = &mut f.returns {
458 fix(r)?;
459 }
460 for a in f.args.iter_mut().chain(f.throws.iter_mut()) {
461 fix(&mut a.ty)?;
462 }
463 }
464 }
465 Ok(())
466 }
467}
468
469fn resolve_type(
470 t: &mut Type,
471 typedefs: &HashMap<String, Type>,
472 enums: &[String],
473 structs: &[String],
474 depth: usize,
475) -> Result<()> {
476 ensure!(depth < 32, "typedefs nest too deeply");
477 match t {
478 Type::List(e) | Type::Set(e) => resolve_type(e, typedefs, enums, structs, depth + 1),
479 Type::Map(k, v) => {
480 resolve_type(k, typedefs, enums, structs, depth + 1)?;
481 resolve_type(v, typedefs, enums, structs, depth + 1)
482 }
483 Type::Struct(name) => {
484 if let Some(real) = typedefs.get(name) {
485 *t = real.clone();
486 resolve_type(t, typedefs, enums, structs, depth + 1)
487 } else if enums.contains(name) {
488 *t = Type::Enum(name.clone());
489 Ok(())
490 } else {
491 ensure!(structs.contains(name), "unknown type {name}");
492 Ok(())
493 }
494 }
495 _ => Ok(()),
496 }
497}