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 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}