1use std::collections::HashMap;
2
3use crate::pretty::utils::*;
4use crate::types::{Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner};
5use pretty::RcDoc;
6
7static KEYWORDS: [&str; 30] = [
8 "import",
9 "service",
10 "func",
11 "type",
12 "opt",
13 "vec",
14 "record",
15 "variant",
16 "blob",
17 "principal",
18 "nat",
19 "nat8",
20 "nat16",
21 "nat32",
22 "nat64",
23 "int",
24 "int8",
25 "int16",
26 "int32",
27 "int64",
28 "float32",
29 "float64",
30 "bool",
31 "text",
32 "null",
33 "reserved",
34 "empty",
35 "oneway",
36 "query",
37 "composite_query",
38];
39
40fn is_keyword(id: &str) -> bool {
41 KEYWORDS.contains(&id)
42}
43
44pub fn is_valid_as_id(id: &str) -> bool {
45 if id.is_empty() || !id.is_ascii() {
46 return false;
47 }
48 for (i, c) in id.char_indices() {
49 if i == 0 {
50 if !c.is_ascii_alphabetic() && c != '_' {
51 return false;
52 }
53 } else if !c.is_ascii_alphanumeric() && c != '_' {
54 return false;
55 }
56 }
57 true
58}
59
60fn needs_quote(id: &str) -> bool {
61 !is_valid_as_id(id) || is_keyword(id)
62}
63
64fn ident_string(id: &str) -> String {
65 if needs_quote(id) {
66 format!("\"{}\"", id.escape_debug())
67 } else {
68 id.to_string()
69 }
70}
71
72pub fn pp_text(id: &str) -> RcDoc<'_> {
73 RcDoc::text(ident_string(id))
74}
75
76pub fn pp_ty(ty: &Type) -> RcDoc<'_> {
77 pp_ty_inner(ty.as_ref())
78}
79
80pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc<'_> {
81 use TypeInner::*;
82 match ty {
83 Null => str("null"),
84 Bool => str("bool"),
85 Nat => str("nat"),
86 Int => str("int"),
87 Nat8 => str("nat8"),
88 Nat16 => str("nat16"),
89 Nat32 => str("nat32"),
90 Nat64 => str("nat64"),
91 Int8 => str("int8"),
92 Int16 => str("int16"),
93 Int32 => str("int32"),
94 Int64 => str("int64"),
95 Float32 => str("float32"),
96 Float64 => str("float64"),
97 Text => str("text"),
98 Reserved => str("reserved"),
99 Empty => str("empty"),
100 Var(ref s) => str(s),
101 Principal => str("principal"),
102 Opt(ref t) => kwd("opt").append(pp_ty(t)),
103 Vec(ref t) if matches!(t.as_ref(), Nat8) => str("blob"),
104 Vec(ref t) => kwd("vec").append(pp_ty(t)),
105 Record(ref fs) => {
106 let t = Type(ty.clone().into());
107 if t.is_tuple() {
108 let tuple = concat(fs.iter().map(|f| pp_ty(&f.ty)), ";");
109 kwd("record").append(enclose_space("{", tuple, "}"))
110 } else {
111 kwd("record").append(pp_fields(fs, false))
112 }
113 }
114 Variant(ref fs) => kwd("variant").append(pp_fields(fs, true)),
115 Func(ref func) => kwd("func").append(pp_function(func)),
116 Service(ref serv) => kwd("service").append(pp_service(serv, None)),
117 Class(ref args, ref t) => pp_class(args, t, None),
118 Knot(ref id) => RcDoc::text(format!("{id}")),
119 Unknown => str("unknown"),
120 Future => str("future"),
121 }
122}
123
124pub fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> {
125 lines(docs.iter().map(|line| RcDoc::text("// ").append(line)))
126}
127
128pub fn pp_label(id: &SharedLabel) -> RcDoc<'_> {
132 pp_label_raw(id.as_ref())
133}
134
135pub fn pp_label_raw(id: &Label) -> RcDoc<'_> {
136 match id {
137 Label::Named(id) => pp_text(id),
138 Label::Id(_) | Label::Unnamed(_) => RcDoc::as_string(id),
139 }
140}
141
142pub(crate) fn pp_field(field: &Field, is_variant: bool) -> RcDoc<'_> {
143 let ty_doc = if is_variant && *field.ty == TypeInner::Null {
144 RcDoc::nil()
145 } else {
146 kwd(" :").append(pp_ty(&field.ty))
147 };
148 pp_label_raw(&field.id).append(ty_doc)
149}
150
151fn pp_fields(fs: &[Field], is_variant: bool) -> RcDoc<'_> {
152 let fields = fs.iter().map(|f| pp_field(f, is_variant));
153 enclose_space("{", concat(fields, ";"), "}")
154}
155
156pub fn pp_function(func: &Function) -> RcDoc<'_> {
157 let args = pp_args(&func.args);
158 let rets = pp_rets(&func.rets);
159 let modes = pp_modes(&func.modes);
160 args.append(" ->")
161 .append(RcDoc::space())
162 .append(rets.append(modes))
163 .nest(INDENT_SPACE)
164}
165
166pub fn pp_args(args: &[Type]) -> RcDoc<'_> {
168 let doc = concat(args.iter().map(pp_ty), ",");
169 enclose("(", doc, ")")
170}
171
172pub fn pp_rets(args: &[Type]) -> RcDoc<'_> {
174 pp_args(args)
175}
176
177pub fn pp_mode(mode: &FuncMode) -> RcDoc<'_> {
178 match mode {
179 FuncMode::Oneway => RcDoc::text("oneway"),
180 FuncMode::Query => RcDoc::text("query"),
181 FuncMode::CompositeQuery => RcDoc::text("composite_query"),
182 }
183}
184pub fn pp_modes(modes: &[FuncMode]) -> RcDoc<'_> {
185 RcDoc::concat(modes.iter().map(|m| RcDoc::space().append(pp_mode(m))))
186}
187
188fn pp_service<'a>(serv: &'a [(String, Type)], docs: Option<&'a DocComments>) -> RcDoc<'a> {
189 let doc = concat(
190 serv.iter().map(|(id, func)| {
191 let doc = docs
192 .and_then(|docs| docs.lookup_service_method(id))
193 .map(|docs| pp_docs(docs))
194 .unwrap_or(RcDoc::nil());
195 let func_doc = match func.as_ref() {
196 TypeInner::Func(ref f) => pp_function(f),
197 TypeInner::Var(_) => pp_ty(func),
198 _ => unreachable!(),
199 };
200 doc.append(pp_text(id)).append(kwd(" :")).append(func_doc)
201 }),
202 ";",
203 );
204 enclose_space("{", doc, "}")
205}
206
207fn pp_defs(env: &TypeEnv) -> RcDoc<'_> {
208 lines(env.0.iter().map(|(id, ty)| {
209 kwd("type")
210 .append(ident(id))
211 .append(kwd("="))
212 .append(pp_ty(ty))
213 .append(";")
214 }))
215}
216
217fn pp_class<'a>(args: &'a [Type], t: &'a Type, docs: Option<&'a DocComments>) -> RcDoc<'a> {
218 let doc = pp_args(args).append(" ->").append(RcDoc::space());
219 match t.as_ref() {
220 TypeInner::Service(ref serv) => doc.append(pp_service(serv, docs)),
221 TypeInner::Var(ref s) => doc.append(s),
222 _ => unreachable!(),
223 }
224}
225
226fn pp_actor<'a>(ty: &'a Type, docs: &'a DocComments) -> RcDoc<'a> {
227 match ty.as_ref() {
228 TypeInner::Service(ref serv) => pp_service(serv, Some(docs)),
229 TypeInner::Class(ref args, ref t) => pp_class(args, t, Some(docs)),
230 TypeInner::Var(_) => pp_ty(ty),
231 _ => unreachable!(),
232 }
233}
234
235pub fn pp_init_args<'a>(env: &'a TypeEnv, args: &'a [Type]) -> RcDoc<'a> {
237 pp_defs(env).append(pp_args(args))
238}
239
240#[derive(Default, Debug)]
242pub struct DocComments {
243 service_methods: HashMap<String, Vec<String>>,
244}
245
246impl DocComments {
247 pub fn empty() -> Self {
248 Self::default()
249 }
250
251 pub fn add_service_method(&mut self, method: String, doc: Vec<String>) {
252 self.service_methods.insert(method, doc);
253 }
254
255 pub fn lookup_service_method(&self, method: &str) -> Option<&Vec<String>> {
256 self.service_methods.get(method)
257 }
258}
259
260pub fn compile(env: &TypeEnv, actor: &Option<Type>) -> String {
261 compile_with_docs(env, actor, &DocComments::empty())
262}
263
264pub fn compile_with_docs(env: &TypeEnv, actor: &Option<Type>, docs: &DocComments) -> String {
283 match actor {
284 None => pp_defs(env).pretty(LINE_WIDTH).to_string(),
285 Some(actor) => {
286 let defs = pp_defs(env);
287 let actor = kwd("service :").append(pp_actor(actor, docs));
288 let doc = defs.append(actor);
289 doc.pretty(LINE_WIDTH).to_string()
290 }
291 }
292}
293
294#[cfg_attr(docsrs, doc(cfg(feature = "value")))]
295#[cfg(feature = "value")]
296pub mod value {
297 use super::{ident_string, pp_label_raw};
298 use crate::pretty::utils::*;
299 use crate::types::value::{IDLArgs, IDLField, IDLValue};
300 use crate::types::Label;
301 use crate::utils::pp_num_str;
302 use std::fmt;
303
304 use ::pretty::RcDoc;
305
306 const MAX_ELEMENTS_FOR_PRETTY_PRINT: usize = 10;
308
309 impl fmt::Display for IDLArgs {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 write!(f, "{}", pp_args(self).pretty(80))
312 }
313 }
314
315 impl fmt::Display for IDLValue {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 write!(
318 f,
319 "{}",
320 pp_value(MAX_ELEMENTS_FOR_PRETTY_PRINT, self).pretty(80)
321 )
322 }
323 }
324
325 impl fmt::Debug for IDLArgs {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 if self.args.len() == 1 {
328 write!(f, "({:?})", self.args[0])
329 } else {
330 let mut tup = f.debug_tuple("");
331 for arg in self.args.iter() {
332 tup.field(arg);
333 }
334 tup.finish()
335 }
336 }
337 }
338 fn has_type_annotation(v: &IDLValue) -> bool {
339 use IDLValue::*;
340 matches!(
341 v,
342 Int(_)
343 | Nat(_)
344 | Nat8(_)
345 | Nat16(_)
346 | Nat32(_)
347 | Nat64(_)
348 | Int8(_)
349 | Int16(_)
350 | Int32(_)
351 | Int64(_)
352 | Float32(_)
353 | Float64(_)
354 | Null
355 | Reserved
356 )
357 }
358 pub fn number_to_string(v: &IDLValue) -> String {
359 use IDLValue::*;
360 match v {
361 Number(n) => n.to_string(),
362 Int(n) => n.to_string(),
363 Nat(n) => n.to_string(),
364 Nat8(n) => n.to_string(),
365 Nat16(n) => pp_num_str(&n.to_string()),
366 Nat32(n) => pp_num_str(&n.to_string()),
367 Nat64(n) => pp_num_str(&n.to_string()),
368 Int8(n) => n.to_string(),
369 Int16(n) => pp_num_str(&n.to_string()),
370 Int32(n) => pp_num_str(&n.to_string()),
371 Int64(n) => pp_num_str(&n.to_string()),
372 Float32(f) => {
373 if f.is_finite() && f.trunc() == *f {
374 format!("{f}.0")
375 } else {
376 f.to_string()
377 }
378 }
379 Float64(f) => {
380 if f.is_finite() && f.trunc() == *f {
381 format!("{f}.0")
382 } else {
383 f.to_string()
384 }
385 }
386 _ => unreachable!(),
387 }
388 }
389 impl fmt::Debug for IDLValue {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 use IDLValue::*;
392 match self {
393 Null => write!(f, "null : null"),
394 Bool(b) => write!(f, "{b}"),
395 Number(n) => write!(f, "{n}"),
396 Int(i) => write!(f, "{i} : int"),
397 Nat(n) => write!(f, "{n} : nat"),
398 Nat8(n) => write!(f, "{n} : nat8"),
399 Nat16(n) => write!(f, "{} : nat16", pp_num_str(&n.to_string())),
400 Nat32(n) => write!(f, "{} : nat32", pp_num_str(&n.to_string())),
401 Nat64(n) => write!(f, "{} : nat64", pp_num_str(&n.to_string())),
402 Int8(n) => write!(f, "{n} : int8"),
403 Int16(n) => write!(f, "{} : int16", pp_num_str(&n.to_string())),
404 Int32(n) => write!(f, "{} : int32", pp_num_str(&n.to_string())),
405 Int64(n) => write!(f, "{} : int64", pp_num_str(&n.to_string())),
406 Float32(_) => write!(f, "{} : float32", number_to_string(self)),
407 Float64(_) => write!(f, "{} : float64", number_to_string(self)),
408 Text(s) => write!(f, "{s:?}"),
409 None => write!(f, "null"),
410 Reserved => write!(f, "null : reserved"),
411 Principal(id) => write!(f, "principal \"{id}\""),
412 Service(id) => write!(f, "service \"{id}\""),
413 Func(id, meth) => write!(f, "func \"{}\".{}", id, ident_string(meth)),
414 Opt(v) if has_type_annotation(v) => write!(f, "opt ({v:?})"),
415 Opt(v) => write!(f, "opt {v:?}"),
416 Blob(b) => {
417 write!(f, "blob \"")?;
418 let is_ascii = b
419 .iter()
420 .all(|c| (0x20u8..=0x7eu8).contains(c) || [0x09, 0x0a, 0x0d].contains(c));
421 if is_ascii {
422 for v in b.iter() {
423 write!(f, "{}", pp_char(*v))?;
424 }
425 } else {
426 for v in b.iter() {
427 write!(f, "\\{v:02x}")?;
428 }
429 }
430 write!(f, "\"")
431 }
432 Vec(vs) => {
433 if let Some(Nat8(_)) = vs.first() {
434 write!(f, "blob \"")?;
435 for v in vs.iter() {
436 match v {
437 Nat8(v) => write!(f, "{}", &pp_char(*v))?,
439 _ => unreachable!(),
440 }
441 }
442 write!(f, "\"")
443 } else {
444 write!(f, "vec {{")?;
445 for v in vs.iter() {
446 write!(f, " {v:?};")?
447 }
448 write!(f, "}}")
449 }
450 }
451 Record(fs) => {
452 write!(f, "record {{")?;
453 for (i, e) in fs.iter().enumerate() {
454 if e.id.get_id() == i as u32 {
455 write!(f, " {:?};", e.val)?;
456 } else {
457 write!(f, " {e:?};")?;
458 }
459 }
460 write!(f, "}}")
461 }
462 Variant(v) => {
463 write!(f, "variant {{ ")?;
464 if v.0.val == Null {
465 write!(f, "{}", v.0.id)?;
466 } else {
467 write!(f, "{:?}", v.0)?;
468 }
469 write!(f, " }}")
470 }
471 }
472 }
473 }
474 impl fmt::Debug for IDLField {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 let lab = match &self.id {
477 Label::Named(id) => ident_string(id),
478 id => id.to_string(),
479 };
480 write!(f, "{} = {:?}", lab, self.val)
481 }
482 }
483
484 fn is_tuple(t: &IDLValue) -> bool {
486 match t {
487 IDLValue::Record(ref fs) => {
488 for (i, field) in fs.iter().enumerate() {
489 if field.id.get_id() != (i as u32) {
490 return false;
491 }
492 }
493 true
494 }
495 _ => false,
496 }
497 }
498
499 fn pp_field(depth: usize, field: &IDLField, is_variant: bool) -> RcDoc<'_> {
500 let val_doc = if is_variant && field.val == IDLValue::Null {
501 RcDoc::nil()
502 } else {
503 kwd(" =").append(pp_value(depth - 1, &field.val))
504 };
505 pp_label_raw(&field.id).append(val_doc)
506 }
507
508 fn pp_fields(depth: usize, fields: &[IDLField]) -> RcDoc<'_> {
509 let fs = concat(fields.iter().map(|f| pp_field(depth, f, false)), ";");
510 enclose_space("{", fs, "}")
511 }
512
513 pub fn pp_char(v: u8) -> String {
514 let is_ascii = (0x20u8..=0x7eu8).contains(&v);
515 if is_ascii && v != 0x22 && v != 0x27 && v != 0x60 && v != 0x5c {
516 std::char::from_u32(v as u32).unwrap().to_string()
517 } else {
518 format!("\\{v:02x}")
519 }
520 }
521 pub fn pp_value(depth: usize, v: &IDLValue) -> RcDoc<'_> {
522 use IDLValue::*;
523 if depth == 0 {
524 return RcDoc::as_string(format!("{v:?}"));
525 }
526 match v {
527 Text(ref s) => RcDoc::as_string(format!("\"{}\"", s.escape_debug())),
528 Opt(v) if has_type_annotation(v) => {
529 kwd("opt").append(enclose("(", pp_value(depth - 1, v), ")"))
530 }
531 Opt(v) => kwd("opt").append(pp_value(depth - 1, v)),
532 Vec(vs) => {
533 if matches!(vs.first(), Some(Nat8(_))) || vs.len() > MAX_ELEMENTS_FOR_PRETTY_PRINT {
534 RcDoc::as_string(format!("{v:?}"))
535 } else {
536 let values = vs.iter().map(|v| pp_value(depth - 1, v));
537 kwd("vec").append(enclose_space("{", concat(values, ";"), "}"))
538 }
539 }
540 Record(fields) => {
541 if is_tuple(v) {
542 let fields = fields.iter().map(|f| pp_value(depth - 1, &f.val));
543 kwd("record").append(enclose_space("{", concat(fields, ";"), "}"))
544 } else {
545 kwd("record").append(pp_fields(depth, fields))
546 }
547 }
548 Variant(v) => {
549 kwd("variant").append(enclose_space("{", pp_field(depth, &v.0, true), "}"))
550 }
551 _ => RcDoc::as_string(format!("{v:?}")),
552 }
553 }
554
555 pub fn pp_args(args: &IDLArgs) -> RcDoc<'_> {
556 let args = args
557 .args
558 .iter()
559 .map(|v| pp_value(MAX_ELEMENTS_FOR_PRETTY_PRINT, v));
560 enclose("(", concat(args, ","), ")")
561 }
562}