candid_parser/syntax/
mod.rs

1mod pretty;
2
3pub use pretty::pretty_print;
4
5use crate::error;
6use anyhow::{anyhow, bail, Context, Result};
7use candid::{
8    idl_hash,
9    types::{FuncMode, Label},
10};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum IDLType {
15    PrimT(PrimType),
16    VarT(String),
17    FuncT(FuncType),
18    OptT(Box<IDLType>),
19    VecT(Box<IDLType>),
20    RecordT(Vec<TypeField>),
21    VariantT(Vec<TypeField>),
22    ServT(Vec<Binding>),
23    ClassT(Vec<IDLType>, Box<IDLType>),
24    PrincipalT,
25}
26
27impl IDLType {
28    pub fn is_tuple(&self) -> bool {
29        match self {
30            IDLType::RecordT(ref fs) => {
31                for (i, field) in fs.iter().enumerate() {
32                    if field.label.get_id() != (i as u32) {
33                        return false;
34                    }
35                }
36                true
37            }
38            _ => false,
39        }
40    }
41}
42
43impl std::str::FromStr for IDLType {
44    type Err = error::Error;
45    fn from_str(str: &str) -> error::Result<Self> {
46        let trivia = super::token::TriviaMap::default();
47        let lexer = super::token::Tokenizer::new_with_trivia(str, trivia.clone());
48        Ok(super::grammar::TypParser::new().parse(Some(&trivia), lexer)?)
49    }
50}
51
52#[derive(Debug, Clone)]
53pub struct IDLTypes {
54    pub args: Vec<IDLType>,
55}
56
57impl std::str::FromStr for IDLTypes {
58    type Err = error::Error;
59    fn from_str(str: &str) -> error::Result<Self> {
60        let trivia = super::token::TriviaMap::default();
61        let lexer = super::token::Tokenizer::new_with_trivia(str, trivia.clone());
62        Ok(super::grammar::TypsParser::new().parse(Some(&trivia), lexer)?)
63    }
64}
65
66macro_rules! enum_to_doc {
67    (pub enum $name:ident {
68        $($variant:ident),*,
69    }) => {
70        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
71        pub enum $name {
72            $($variant),*
73        }
74        impl $name {
75            pub fn str_to_enum(str: &str) -> Option<Self> {
76                $(if str == stringify!($variant).to_lowercase() {
77                    return Some($name::$variant);
78                });*
79                return None;
80            }
81        }
82    };
83}
84
85enum_to_doc! {
86pub enum PrimType {
87    Nat,
88    Nat8,
89    Nat16,
90    Nat32,
91    Nat64,
92    Int,
93    Int8,
94    Int16,
95    Int32,
96    Int64,
97    Float32,
98    Float64,
99    Bool,
100    Text,
101    Null,
102    Reserved,
103    Empty,
104}}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct FuncType {
108    pub modes: Vec<FuncMode>,
109    pub args: Vec<IDLType>,
110    pub rets: Vec<IDLType>,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct TypeField {
115    pub label: Label,
116    pub typ: IDLType,
117    pub docs: Vec<String>,
118}
119
120#[derive(Debug)]
121pub enum Dec {
122    TypD(Binding),
123    ImportType(String),
124    ImportServ(String),
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct Binding {
129    pub id: String,
130    pub typ: IDLType,
131    pub docs: Vec<String>,
132}
133
134#[derive(Debug, Clone)]
135pub struct IDLActorType {
136    pub typ: IDLType,
137    pub docs: Vec<String>,
138}
139
140#[derive(Debug)]
141pub struct IDLProg {
142    pub decs: Vec<Dec>,
143    pub actor: Option<IDLActorType>,
144}
145
146impl IDLProg {
147    pub fn typ_decs(decs: Vec<Dec>) -> impl Iterator<Item = Binding> {
148        decs.into_iter().filter_map(|d| {
149            if let Dec::TypD(bindings) = d {
150                Some(bindings)
151            } else {
152                None
153            }
154        })
155    }
156}
157
158impl std::str::FromStr for IDLProg {
159    type Err = error::Error;
160    fn from_str(str: &str) -> error::Result<Self> {
161        let trivia = super::token::TriviaMap::default();
162        let lexer = super::token::Tokenizer::new_with_trivia(str, trivia.clone());
163        Ok(super::grammar::IDLProgParser::new().parse(Some(&trivia), lexer)?)
164    }
165}
166
167#[derive(Debug)]
168pub struct IDLInitArgs {
169    pub decs: Vec<Dec>,
170    pub args: Vec<IDLType>,
171}
172
173impl std::str::FromStr for IDLInitArgs {
174    type Err = error::Error;
175    fn from_str(str: &str) -> error::Result<Self> {
176        let trivia = super::token::TriviaMap::default();
177        let lexer = super::token::Tokenizer::new_with_trivia(str, trivia.clone());
178        Ok(super::grammar::IDLInitArgsParser::new().parse(Some(&trivia), lexer)?)
179    }
180}
181
182#[derive(Debug)]
183pub struct IDLMergedProg {
184    typ_decs: Vec<Binding>,
185    main_actor: Option<IDLActorType>,
186    service_imports: Vec<(String, IDLActorType)>,
187}
188
189impl IDLMergedProg {
190    pub fn new(prog: IDLProg) -> IDLMergedProg {
191        IDLMergedProg {
192            typ_decs: IDLProg::typ_decs(prog.decs).collect(),
193            main_actor: prog.actor,
194            service_imports: vec![],
195        }
196    }
197
198    pub fn merge(&mut self, is_service_import: bool, name: String, prog: IDLProg) -> Result<()> {
199        self.typ_decs.extend(IDLProg::typ_decs(prog.decs));
200        if is_service_import {
201            let actor = prog
202                .actor
203                .with_context(|| format!("Imported service file \"{name}\" has no main service"))?;
204            self.service_imports.push((name, actor));
205        }
206        Ok(())
207    }
208
209    pub fn lookup(&self, id: &str) -> Option<&Binding> {
210        self.typ_decs.iter().find(|b| b.id == id)
211    }
212
213    pub fn decs(&self) -> Vec<Dec> {
214        self.typ_decs.iter().map(|b| Dec::TypD(b.clone())).collect()
215    }
216
217    pub fn bindings(&self) -> impl Iterator<Item = &Binding> {
218        self.typ_decs.iter()
219    }
220
221    pub fn resolve_actor(&self) -> Result<Option<IDLActorType>> {
222        let (init_args, top_level_docs, mut methods) = match &self.main_actor {
223            None => {
224                if self.service_imports.is_empty() {
225                    return Ok(None);
226                } else {
227                    (None, vec![], vec![])
228                }
229            }
230            Some(t) if self.service_imports.is_empty() => return Ok(Some(t.clone())),
231            Some(IDLActorType {
232                typ: IDLType::ClassT(args, inner),
233                docs,
234            }) => (
235                Some(args.clone()),
236                docs.clone(),
237                self.chase_service(*inner.clone(), None)?,
238            ),
239            Some(ty) => (
240                None,
241                ty.docs.clone(),
242                self.chase_service(ty.typ.clone(), None)?,
243            ),
244        };
245
246        for (name, typ) in &self.service_imports {
247            methods.extend(self.chase_service(typ.typ.clone(), Some(name))?);
248        }
249
250        let mut hashes: HashMap<u32, &str> = HashMap::new();
251        for method in &methods {
252            let name = &method.id;
253            if let Some(previous) = hashes.insert(idl_hash(name), name) {
254                bail!("Duplicate imported method name: label '{name}' hash collision with '{previous}'")
255            }
256        }
257
258        let typ = if let Some(args) = init_args {
259            IDLType::ClassT(args, Box::new(IDLType::ServT(methods)))
260        } else {
261            IDLType::ServT(methods)
262        };
263        Ok(Some(IDLActorType {
264            typ,
265            docs: top_level_docs,
266        }))
267    }
268
269    // NOTE: We don't worry about cyclic type definitions, as we rule those out earlier when checking the type decs
270    fn chase_service(&self, ty: IDLType, import_name: Option<&str>) -> Result<Vec<Binding>> {
271        match ty {
272            IDLType::VarT(v) => {
273                let resolved = self
274                    .typ_decs
275                    .iter()
276                    .find(|b| b.id == v)
277                    .with_context(|| format!("Unbound type identifier {v}"))?;
278                self.chase_service(resolved.typ.clone(), import_name)
279            }
280            IDLType::ServT(bindings) => Ok(bindings),
281            ty => Err(import_name
282                .map(|name| anyhow!("Imported service file \"{name}\" has a service constructor"))
283                .unwrap_or(anyhow!("not a service type: {:?}", ty))),
284        }
285    }
286}