Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

* Breaking changes:
+ `pp_args` and `pp_init_args` now require a `&[ArgType]` parameter. The `pp_rets` function has been added, with the signature of the old `pp_args`.
+ `pretty::candid::pp_label` now takes a `&Label` parameter, instead of a `&SharedLabel`.

* Non-breaking changes:
+ The following structs have been moved from the `candid_parser` crate to the `candid::types::syntax` module:
Expand Down
17 changes: 4 additions & 13 deletions rust/candid/src/pretty/candid.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::pretty::utils::*;
use crate::types::{
ArgType, Field, FuncMode, Function, Label, SharedLabel, Type, TypeEnv, TypeInner,
};
use crate::types::{ArgType, Field, FuncMode, Function, Label, Type, TypeEnv, TypeInner};
use pretty::RcDoc;

static KEYWORDS: [&str; 30] = [
Expand Down Expand Up @@ -128,8 +126,8 @@ pub fn pp_ty_inner(ty: &TypeInner) -> RcDoc {
}
}

pub fn pp_label(id: &SharedLabel) -> RcDoc {
match &**id {
pub fn pp_label(id: &Label) -> RcDoc {
Comment thread
ilbertt marked this conversation as resolved.
match id {
Label::Named(id) => pp_text(id),
Label::Id(_) | Label::Unnamed(_) => RcDoc::as_string(id),
}
Expand Down Expand Up @@ -238,7 +236,7 @@ pub fn compile(env: &TypeEnv, actor: &Option<Type>) -> String {
#[cfg_attr(docsrs, doc(cfg(feature = "value")))]
#[cfg(feature = "value")]
pub mod value {
use super::{ident_string, pp_text};
use super::{ident_string, pp_label};
use crate::pretty::utils::*;
use crate::types::value::{IDLArgs, IDLField, IDLValue};
use crate::types::Label;
Expand Down Expand Up @@ -440,13 +438,6 @@ pub mod value {
}
}

fn pp_label(id: &Label) -> RcDoc {
match id {
Label::Named(id) => pp_text(id),
Label::Id(_) | Label::Unnamed(_) => RcDoc::as_string(id),
}
}

fn pp_field(depth: usize, field: &IDLField, is_variant: bool) -> RcDoc {
let val_doc = if is_variant && field.val == IDLValue::Null {
RcDoc::nil()
Expand Down
1 change: 1 addition & 0 deletions rust/candid/src/pretty/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! pretty printer for Candid type and value

pub mod candid;
pub mod syntax;
pub mod utils;
177 changes: 177 additions & 0 deletions rust/candid/src/pretty/syntax.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use pretty::RcDoc;

use crate::{
pretty::{
candid::{pp_label, pp_modes, pp_text},
utils::{concat, enclose, enclose_space, ident, kwd, lines, str, INDENT_SPACE, LINE_WIDTH},
},
types::syntax::{
Binding, FuncType, IDLActorType, IDLArgType, IDLMergedProg, IDLType, PrimType, TypeField,
},
};

fn pp_ty(ty: &IDLType) -> RcDoc {
use IDLType::*;
match ty {
PrimT(PrimType::Null) => str("null"),
PrimT(PrimType::Bool) => str("bool"),
PrimT(PrimType::Nat) => str("nat"),
PrimT(PrimType::Int) => str("int"),
PrimT(PrimType::Nat8) => str("nat8"),
PrimT(PrimType::Nat16) => str("nat16"),
PrimT(PrimType::Nat32) => str("nat32"),
PrimT(PrimType::Nat64) => str("nat64"),
PrimT(PrimType::Int8) => str("int8"),
PrimT(PrimType::Int16) => str("int16"),
PrimT(PrimType::Int32) => str("int32"),
PrimT(PrimType::Int64) => str("int64"),
PrimT(PrimType::Float32) => str("float32"),
PrimT(PrimType::Float64) => str("float64"),
PrimT(PrimType::Text) => str("text"),
PrimT(PrimType::Reserved) => str("reserved"),
PrimT(PrimType::Empty) => str("empty"),
VarT(ref s) => str(s),
PrincipalT => str("principal"),
OptT(ref t) => pp_opt(t),
VecT(ref t) => pp_vec(t),
RecordT(ref fs) => pp_record(fs, ty.is_tuple()),
VariantT(ref fs) => pp_variant(fs),
FuncT(ref func) => pp_function(func),
ServT(ref serv) => pp_service(serv),
ClassT(ref args, ref t) => pp_class(args, t),
}
}

fn pp_field(field: &TypeField, is_variant: bool) -> RcDoc {
let docs = pp_docs(&field.docs);
let ty_doc = if is_variant && field.typ == IDLType::PrimT(PrimType::Null) {
RcDoc::nil()
} else {
kwd(" :").append(pp_ty(&field.typ))
};
docs.append(pp_label(&field.label)).append(ty_doc)
}

fn pp_fields(fs: &[TypeField], is_variant: bool) -> RcDoc {
let fields = fs.iter().map(|f| pp_field(f, is_variant));
enclose_space("{", concat(fields, ";"), "}")
}

fn pp_opt(ty: &IDLType) -> RcDoc {
kwd("opt").append(pp_ty(ty))
}

fn pp_vec(ty: &IDLType) -> RcDoc {
if matches!(ty, IDLType::PrimT(PrimType::Nat8)) {
str("blob")
} else {
kwd("vec").append(pp_ty(ty))
}
}

fn pp_record(fs: &[TypeField], is_tuple: bool) -> RcDoc {
if is_tuple {
let tuple = concat(fs.iter().map(|f| pp_ty(&f.typ)), ";");
kwd("record").append(enclose_space("{", tuple, "}"))
} else {
kwd("record").append(pp_fields(fs, false))
}
}

fn pp_variant(fs: &[TypeField]) -> RcDoc {
kwd("variant").append(pp_fields(fs, true))
}

fn pp_function(func: &FuncType) -> RcDoc {
kwd("func").append(pp_method(func))
}

fn pp_method(func: &FuncType) -> RcDoc {
let args = pp_args(&func.args);
let rets = pp_rets(&func.rets);
let modes = pp_modes(&func.modes);
args.append(" ->")
.append(RcDoc::space())
.append(rets.append(modes))
.nest(INDENT_SPACE)
}

fn pp_args(args: &[IDLArgType]) -> RcDoc {
let args = args.iter().map(|arg| {
if let Some(name) = &arg.name {
pp_text(name).append(kwd(" :")).append(pp_ty(&arg.typ))
} else {
pp_ty(&arg.typ)
}
});
let doc = concat(args, ",");
enclose("(", doc, ")")
}

fn pp_rets(rets: &[IDLType]) -> RcDoc {
let doc = concat(rets.iter().map(pp_ty), ",");
enclose("(", doc, ")")
}

fn pp_service(methods: &[Binding]) -> RcDoc {
kwd("service").append(pp_service_methods(methods))
}

fn pp_service_methods(methods: &[Binding]) -> RcDoc {
let methods = methods.iter().map(|b| {
let docs = pp_docs(&b.docs);
let func_doc = match b.typ {
IDLType::FuncT(ref f) => pp_method(f),
IDLType::VarT(_) => pp_ty(&b.typ),
_ => unreachable!(),
};
docs.append(pp_text(&b.id))
.append(kwd(" :"))
.append(func_doc)
});
let doc = concat(methods, ";");
enclose_space("{", doc, "}")
}

fn pp_class<'a>(args: &'a [IDLArgType], t: &'a IDLType) -> RcDoc<'a> {
let doc = pp_args(args).append(" ->").append(RcDoc::space());
match t {
IDLType::ServT(ref serv) => doc.append(pp_service_methods(serv)),
IDLType::VarT(ref s) => doc.append(s),
_ => unreachable!(),
}
}

fn pp_defs(prog: &IDLMergedProg) -> RcDoc {
lines(prog.bindings().map(|b| {
let docs = pp_docs(&b.docs);
docs.append(kwd("type"))
.append(ident(&b.id))
.append(kwd("="))
.append(pp_ty(&b.typ))
.append(";")
}))
}

fn pp_docs<'a>(docs: &'a [String]) -> RcDoc<'a> {
lines(docs.iter().map(|line| RcDoc::text("// ").append(line)))
}

fn pp_actor(actor: &IDLActorType) -> RcDoc {
let docs = pp_docs(&actor.docs);
let service_doc = match actor.typ {
IDLType::ServT(ref serv) => pp_service_methods(serv),
IDLType::VarT(_) | IDLType::ClassT(_, _) => pp_ty(&actor.typ),
_ => unreachable!(),
};
docs.append(kwd("service :")).append(service_doc)
}

pub fn pretty_print(prog: &IDLMergedProg) -> String {
let actor = prog.resolve_actor().ok().flatten();
let doc = match actor.as_ref() {
None => pp_defs(prog),
Some(actor) => pp_defs(prog).append(pp_actor(actor)),
};
doc.pretty(LINE_WIDTH).to_string()
}
20 changes: 20 additions & 0 deletions rust/candid/src/types/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ pub enum IDLType {
PrincipalT,
}

impl IDLType {
pub fn is_tuple(&self) -> bool {
match self {
IDLType::RecordT(ref fs) => {
for (i, field) in fs.iter().enumerate() {
if field.label.get_id() != (i as u32) {
return false;
}
}
true
}
_ => false,
}
}
}

#[derive(Debug, Clone)]
pub struct IDLTypes {
pub args: Vec<IDLType>,
Expand Down Expand Up @@ -181,6 +197,10 @@ impl IDLMergedProg {
self.typ_decs.iter().map(|b| Dec::TypD(b.clone())).collect()
}

pub fn bindings(&self) -> impl Iterator<Item = &Binding> {
self.typ_decs.iter()
}

pub fn resolve_actor(&self) -> Result<Option<IDLActorType>> {
let (init_args, top_level_docs, mut methods) = match &self.main_actor {
None => {
Expand Down
4 changes: 3 additions & 1 deletion rust/candid_parser/tests/assets/ok/class.did
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
type List = opt record { int; List };
type Profile = record { age : nat8; name : text };
type List = opt record { int; List };
// Doc comment for class service
service : (int, l : List, Profile) -> {
// Doc comment for get method in class service
get : () -> (List);
set : (List) -> (List);
}
2 changes: 2 additions & 0 deletions rust/candid_parser/tests/assets/ok/comment.did
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
// line comment
//
type id = nat8;

2 changes: 1 addition & 1 deletion rust/candid_parser/tests/assets/ok/cyclic.did
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type A = opt B;
type B = opt C;
type C = A;
type B = opt C;
type X = Y;
type Y = Z;
type Z = A;
Expand Down
Loading