From eba78b94b29bfbdedfb8694ae796cdc59c88468a Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 14:32:39 +0100 Subject: [PATCH 01/18] [wit-component] add `push_keyword`, `push_doc` --- crates/wit-component/src/printing.rs | 186 +++++++++++++++++++-------- 1 file changed, 129 insertions(+), 57 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 13e03ce0c6..a8ad9c4f3a 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -65,10 +65,19 @@ impl WitPrinter { Ok(std::mem::take(&mut self.output).into()) } - fn print_package(&mut self, resolve: &Resolve, pkg: PackageId, is_main: bool) -> Result<()> { + /// Prints the specified `pkg`. + /// + /// If `is_main` is not set, nested package notation is used. + pub fn print_package( + &mut self, + resolve: &Resolve, + pkg: PackageId, + is_main: bool, + ) -> Result<()> { let pkg = &resolve.packages[pkg]; self.print_docs(&pkg.docs); - self.output.push_str("package "); + self.output.push_keyword("package"); + self.output.push_str(" "); self.print_name(&pkg.name.namespace); self.output.push_str(":"); self.print_name(&pkg.name.name); @@ -86,7 +95,8 @@ impl WitPrinter { for (name, id) in pkg.interfaces.iter() { self.print_docs(&resolve.interfaces[*id].docs); self.print_stability(&resolve.interfaces[*id].stability); - self.output.push_str("interface "); + self.output.push_keyword("interface"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" {\n"); self.print_interface(resolve, *id)?; @@ -100,7 +110,8 @@ impl WitPrinter { for (name, id) in pkg.worlds.iter() { self.print_docs(&resolve.worlds[*id].docs); self.print_stability(&resolve.worlds[*id].stability); - self.output.push_str("world "); + self.output.push_keyword("world"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" {\n"); self.print_world(resolve, *id)?; @@ -215,7 +226,8 @@ impl WitPrinter { for (owner, stability, tys) in types_to_import { self.any_items = true; self.print_stability(stability); - write!(&mut self.output, "use ")?; + self.output.push_keyword("use"); + self.output.push_str(" "); let id = match owner { TypeOwner::Interface(id) => id, // it's only possible to import types from interfaces at @@ -232,7 +244,9 @@ impl WitPrinter { self.print_name(my_name); } else { self.print_name(other_name); - self.output.push_str(" as "); + self.output.push_str(" "); + self.output.push_keyword("as"); + self.output.push_str(" "); self.print_name(my_name); } } @@ -260,7 +274,8 @@ impl WitPrinter { fn print_resource(&mut self, resolve: &Resolve, id: TypeId, funcs: &[&Function]) -> Result<()> { let ty = &resolve.types[id]; - self.output.push_str("resource "); + self.output.push_keyword("resource"); + self.output.push_str(" "); self.print_name(ty.name.as_ref().expect("resources must be named")); if funcs.is_empty() { self.print_semicolon(); @@ -281,7 +296,8 @@ impl WitPrinter { FunctionKind::Static(_) => { self.print_name(func.item_name()); self.output.push_str(": "); - self.output.push_str("static "); + self.output.push_keyword("static"); + self.output.push_str(" "); } FunctionKind::Freestanding => unreachable!(), } @@ -297,8 +313,14 @@ impl WitPrinter { fn print_function(&mut self, resolve: &Resolve, func: &Function) -> Result<()> { // Constructors are named slightly differently. match &func.kind { - FunctionKind::Constructor(_) => self.output.push_str("constructor("), - _ => self.output.push_str("func("), + FunctionKind::Constructor(_) => { + self.output.push_keyword("constructor"); + self.output.push_str("("); + } + _ => { + self.output.push_keyword("func"); + self.output.push_str("("); + } } // Methods don't print their `self` argument @@ -394,7 +416,7 @@ impl WitPrinter { name: &WorldKey, item: &WorldItem, cur_pkg: PackageId, - desc: &str, + import_or_export_keyword: &str, ) -> Result<()> { // Print inline item docs if matches!(name, WorldKey::Name(_)) { @@ -407,7 +429,7 @@ impl WitPrinter { } self.print_stability(item.stability(resolve)); - self.output.push_str(desc); + self.output.push_keyword(import_or_export_keyword); self.output.push_str(" "); match name { WorldKey::Name(name) => { @@ -416,9 +438,10 @@ impl WitPrinter { match item { WorldItem::Interface { id, .. } => { assert!(resolve.interfaces[*id].name.is_none()); - writeln!(self.output, "interface {{")?; + self.output.push_keyword("interface"); + self.output.push_str(" {\n"); self.print_interface(resolve, *id)?; - writeln!(self.output, "}}")?; + self.output.push_str("}\n"); } WorldItem::Function(f) => { self.print_function(resolve, f)?; @@ -465,33 +488,34 @@ impl WitPrinter { Ok(()) } - fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { + /// Print the name of type `ty`. + pub fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { - Type::Bool => self.output.push_str("bool"), - Type::U8 => self.output.push_str("u8"), - Type::U16 => self.output.push_str("u16"), - Type::U32 => self.output.push_str("u32"), - Type::U64 => self.output.push_str("u64"), - Type::S8 => self.output.push_str("s8"), - Type::S16 => self.output.push_str("s16"), - Type::S32 => self.output.push_str("s32"), - Type::S64 => self.output.push_str("s64"), + Type::Bool => self.output.push_keyword("bool"), + Type::U8 => self.output.push_keyword("u8"), + Type::U16 => self.output.push_keyword("u16"), + Type::U32 => self.output.push_keyword("u32"), + Type::U64 => self.output.push_keyword("u64"), + Type::S8 => self.output.push_keyword("s8"), + Type::S16 => self.output.push_keyword("s16"), + Type::S32 => self.output.push_keyword("s32"), + Type::S64 => self.output.push_keyword("s64"), Type::F32 => { if self.print_f32_f64 { - self.output.push_str("f32") + self.output.push_keyword("f32") } else { - self.output.push_str("f32") + self.output.push_keyword("f32") } } Type::F64 => { if self.print_f32_f64 { - self.output.push_str("f64") + self.output.push_keyword("f64") } else { - self.output.push_str("f64") + self.output.push_keyword("f64") } } - Type::Char => self.output.push_str("char"), - Type::String => self.output.push_str("string"), + Type::Char => self.output.push_keyword("char"), + Type::String => self.output.push_keyword("string"), Type::Id(id) => { let ty = &resolve.types[*id]; @@ -529,7 +553,8 @@ impl WitPrinter { bail!("resolve has unnamed variant type") } TypeDefKind::List(ty) => { - self.output.push_str("list<"); + self.output.push_keyword("list"); + self.output.push_str("<"); self.print_type_name(resolve, ty)?; self.output.push_str(">"); } @@ -558,7 +583,8 @@ impl WitPrinter { Handle::Own(ty) => { let ty = &resolve.types[*ty]; if force_handle_type_printed { - self.output.push_str("own<"); + self.output.push_keyword("own"); + self.output.push_str("<"); } self.print_name( ty.name @@ -571,7 +597,8 @@ impl WitPrinter { } Handle::Borrow(ty) => { - self.output.push_str("borrow<"); + self.output.push_keyword("borrow"); + self.output.push_str("<"); let ty = &resolve.types[*ty]; self.print_name( ty.name @@ -586,7 +613,8 @@ impl WitPrinter { } fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { - self.output.push_str("tuple<"); + self.output.push_keyword("tuple"); + self.output.push_str("<"); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { self.output.push_str(", "); @@ -599,7 +627,8 @@ impl WitPrinter { } fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { - self.output.push_str("option<"); + self.output.push_keyword("option"); + self.output.push_str("<"); self.print_type_name(resolve, payload)?; self.output.push_str(">"); Ok(()) @@ -611,7 +640,8 @@ impl WitPrinter { ok: Some(ok), err: Some(err), } => { - self.output.push_str("result<"); + self.output.push_keyword("result"); + self.output.push_str("<"); self.print_type_name(resolve, ok)?; self.output.push_str(", "); self.print_type_name(resolve, err)?; @@ -621,7 +651,8 @@ impl WitPrinter { ok: None, err: Some(err), } => { - self.output.push_str("result<_, "); + self.output.push_keyword("result"); + self.output.push_str("<_, "); self.print_type_name(resolve, err)?; self.output.push_str(">"); } @@ -629,7 +660,8 @@ impl WitPrinter { ok: Some(ok), err: None, } => { - self.output.push_str("result<"); + self.output.push_keyword("result"); + self.output.push_str("<"); self.print_type_name(resolve, ok)?; self.output.push_str(">"); } @@ -637,7 +669,7 @@ impl WitPrinter { ok: None, err: None, } => { - self.output.push_str("result"); + self.output.push_keyword("result"); } } Ok(()) @@ -686,7 +718,8 @@ impl WitPrinter { } TypeDefKind::Type(inner) => match ty.name.as_deref() { Some(name) => { - self.output.push_str("type "); + self.output.push_keyword("type"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" = "); self.print_type_name(resolve, inner)?; @@ -712,7 +745,8 @@ impl WitPrinter { ) -> Result<()> { match name { Some(name) => { - self.output.push_str("type "); + self.output.push_keyword("type"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" = "); // Note that the `true` here forces owned handles to be printed @@ -738,7 +772,8 @@ impl WitPrinter { ) -> Result<()> { match name { Some(name) => { - self.output.push_str("record "); + self.output.push_keyword("record"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" {\n"); for field in &record.fields { @@ -762,7 +797,8 @@ impl WitPrinter { tuple: &Tuple, ) -> Result<()> { if let Some(name) = name { - self.output.push_str("type "); + self.output.push_keyword("type"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" = "); self.print_tuple_type(resolve, tuple)?; @@ -775,7 +811,8 @@ impl WitPrinter { fn declare_flags(&mut self, name: Option<&str>, flags: &Flags) -> Result<()> { match name { Some(name) => { - self.output.push_str("flags "); + self.output.push_keyword("flags"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" {\n"); for flag in &flags.flags { @@ -800,7 +837,8 @@ impl WitPrinter { Some(name) => name, None => bail!("document has unnamed variant type"), }; - self.output.push_str("variant "); + self.output.push_keyword("variant"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" {\n"); for case in &variant.cases { @@ -824,7 +862,8 @@ impl WitPrinter { payload: &Type, ) -> Result<()> { if let Some(name) = name { - self.output.push_str("type "); + self.output.push_keyword("type"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" = "); self.print_option_type(resolve, payload)?; @@ -841,7 +880,8 @@ impl WitPrinter { result: &Result_, ) -> Result<()> { if let Some(name) = name { - self.output.push_str("type "); + self.output.push_keyword("type"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" = "); self.print_result_type(resolve, result)?; @@ -856,7 +896,8 @@ impl WitPrinter { Some(name) => name, None => bail!("document has unnamed enum type"), }; - self.output.push_str("enum "); + self.output.push_keyword("enum"); + self.output.push_str(" "); self.print_name(name); self.output.push_str(" {\n"); for case in &enum_.cases { @@ -870,9 +911,12 @@ impl WitPrinter { fn declare_list(&mut self, resolve: &Resolve, name: Option<&str>, ty: &Type) -> Result<()> { if let Some(name) = name { - self.output.push_str("type "); + self.output.push_keyword("type"); + self.output.push_str(" "); self.print_name(name); - self.output.push_str(" = list<"); + self.output.push_str(" = "); + self.output.push_keyword("list"); + self.output.push_str("<"); self.print_type_name(resolve, ty)?; self.output.push_str(">"); self.print_semicolon(); @@ -894,9 +938,7 @@ impl WitPrinter { if self.emit_docs { if let Some(contents) = &docs.contents { for line in contents.lines() { - self.output.push_str("/// "); - self.output.push_str(line); - self.output.push_str("\n"); + self.output.push_doc(line); } } } @@ -906,11 +948,17 @@ impl WitPrinter { match stability { Stability::Unknown => {} Stability::Stable { since, deprecated } => { - self.output.push_str("@since(version = "); + self.output.push_keyword("@since"); + self.output.push_str("("); + self.output.push_keyword("version"); + self.output.push_str(" = "); self.output.push_str(&since.to_string()); self.output.push_str(")\n"); if let Some(version) = deprecated { - self.output.push_str("@deprecated(version = "); + self.output.push_keyword("@deprecated"); + self.output.push_str("("); + self.output.push_keyword("version"); + self.output.push_str(" = "); self.output.push_str(&version.to_string()); self.output.push_str(")\n"); } @@ -919,11 +967,17 @@ impl WitPrinter { feature, deprecated, } => { - self.output.push_str("@unstable(feature = "); + self.output.push_keyword("@unstable"); + self.output.push_str("("); + self.output.push_keyword("feature"); + self.output.push_str(" = "); self.output.push_str(feature); self.output.push_str(")\n"); if let Some(version) = deprecated { - self.output.push_str("@deprecated(version = "); + self.output.push_keyword("@deprecated"); + self.output.push_str("("); + self.output.push_keyword("version"); + self.output.push_str(" = "); self.output.push_str(&version.to_string()); self.output.push_str(")\n"); } @@ -999,6 +1053,18 @@ struct Output { } impl Output { + fn push_keyword(&mut self, src: &str) { + assert!(!src.contains('\n')); + assert_eq!(src, src.trim()); + self.push_str(src); + } + + fn push_doc(&mut self, doc: &str) { + self.push_str("/// "); + self.push_str(doc); + self.push_str("\n"); + } + fn push_str(&mut self, src: &str) { let lines = src.lines().collect::>(); for (i, line) in lines.iter().enumerate() { @@ -1053,3 +1119,9 @@ impl From for String { output.output } } + +impl From for String { + fn from(value: WitPrinter) -> String { + value.output.output + } +} From 3bf459df1d223cbc7a87beb9f4af2ff20be6b319 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 14:37:58 +0100 Subject: [PATCH 02/18] [wit-component] merge printing semicolon and newline --- crates/wit-component/src/printing.rs | 50 +++++++++++----------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index a8ad9c4f3a..6131e3c9a7 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -86,10 +86,11 @@ impl WitPrinter { } if is_main { - self.print_semicolon(); - self.output.push_str("\n\n"); + self.output.semicolon(); + self.output.push_str("\n"); } else { self.output.push_str(" {\n"); + //self.output.indent_start(); } for (name, id) in pkg.interfaces.iter() { @@ -123,10 +124,6 @@ impl WitPrinter { Ok(()) } - fn print_semicolon(&mut self) { - self.output.push_str(";"); - } - fn new_item(&mut self) { if self.any_items { self.output.push_str("\n"); @@ -166,8 +163,7 @@ impl WitPrinter { self.print_name(name); self.output.push_str(": "); self.print_function(resolve, func)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } self.any_items = prev_items; @@ -251,8 +247,7 @@ impl WitPrinter { } } write!(&mut self.output, "}}")?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } for id in types_to_declare { @@ -278,8 +273,7 @@ impl WitPrinter { self.output.push_str(" "); self.print_name(ty.name.as_ref().expect("resources must be named")); if funcs.is_empty() { - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); return Ok(()); } self.output.push_str(" {\n"); @@ -302,8 +296,7 @@ impl WitPrinter { FunctionKind::Freestanding => unreachable!(), } self.print_function(resolve, func)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } self.output.push_str("}\n"); @@ -445,8 +438,7 @@ impl WitPrinter { } WorldItem::Function(f) => { self.print_function(resolve, f)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } // Types are handled separately WorldItem::Type(_) => unreachable!(), @@ -458,8 +450,7 @@ impl WitPrinter { _ => unreachable!(), } self.print_path_to_interface(resolve, *id, cur_pkg)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } } Ok(()) @@ -723,8 +714,7 @@ impl WitPrinter { self.print_name(name); self.output.push_str(" = "); self.print_type_name(resolve, inner)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } None => bail!("unnamed type in document"), }, @@ -755,8 +745,7 @@ impl WitPrinter { // own`. By forcing a handle to be printed here it's staying // true to what's in the WIT document. self.print_handle_type(resolve, handle, true)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); Ok(()) } @@ -802,8 +791,7 @@ impl WitPrinter { self.print_name(name); self.output.push_str(" = "); self.print_tuple_type(resolve, tuple)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } Ok(()) } @@ -867,8 +855,7 @@ impl WitPrinter { self.print_name(name); self.output.push_str(" = "); self.print_option_type(resolve, payload)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } Ok(()) } @@ -885,8 +872,7 @@ impl WitPrinter { self.print_name(name); self.output.push_str(" = "); self.print_result_type(resolve, result)?; - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); } Ok(()) } @@ -919,8 +905,7 @@ impl WitPrinter { self.output.push_str("<"); self.print_type_name(resolve, ty)?; self.output.push_str(">"); - self.print_semicolon(); - self.output.push_str("\n"); + self.output.semicolon(); return Ok(()); } @@ -1065,6 +1050,11 @@ impl Output { self.push_str("\n"); } + fn semicolon(&mut self) { + self.push_str(";"); + self.push_str("\n"); + } + fn push_str(&mut self, src: &str) { let lines = src.lines().collect::>(); for (i, line) in lines.iter().enumerate() { From a11a6fef9ff1ee07623169dcf13b330e6e070979 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 14:45:50 +0100 Subject: [PATCH 03/18] [wit-component] add `newline`, `indent_start`, `indent_end` --- crates/wit-component/src/printing.rs | 80 ++++++++++++++++++---------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 6131e3c9a7..ae5a48a037 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -57,7 +57,8 @@ impl WitPrinter { self.print_package(resolve, pkg, true)?; for (i, pkg_id) in nested.iter().enumerate() { if i > 0 { - self.output.push_str("\n\n"); + self.output.newline(); + self.output.newline(); } self.print_package(resolve, *pkg_id, false)?; } @@ -87,10 +88,9 @@ impl WitPrinter { if is_main { self.output.semicolon(); - self.output.push_str("\n"); + self.output.newline(); } else { - self.output.push_str(" {\n"); - //self.output.indent_start(); + self.output.indent_start(); } for (name, id) in pkg.interfaces.iter() { @@ -99,7 +99,7 @@ impl WitPrinter { self.output.push_keyword("interface"); self.output.push_str(" "); self.print_name(name); - self.output.push_str(" {\n"); + self.output.indent_start(); self.print_interface(resolve, *id)?; if is_main { writeln!(&mut self.output, "}}\n")?; @@ -114,7 +114,7 @@ impl WitPrinter { self.output.push_keyword("world"); self.output.push_str(" "); self.print_name(name); - self.output.push_str(" {\n"); + self.output.indent_start(); self.print_world(resolve, *id)?; writeln!(&mut self.output, "}}")?; } @@ -126,7 +126,7 @@ impl WitPrinter { fn new_item(&mut self) { if self.any_items { - self.output.push_str("\n"); + self.output.newline(); } self.any_items = true; } @@ -276,7 +276,7 @@ impl WitPrinter { self.output.semicolon(); return Ok(()); } - self.output.push_str(" {\n"); + self.output.indent_start(); for func in funcs { self.print_docs(&func.docs); self.print_stability(&func.stability); @@ -298,7 +298,7 @@ impl WitPrinter { self.print_function(resolve, func)?; self.output.semicolon(); } - self.output.push_str("}\n"); + self.output.indent_end(); Ok(()) } @@ -432,9 +432,9 @@ impl WitPrinter { WorldItem::Interface { id, .. } => { assert!(resolve.interfaces[*id].name.is_none()); self.output.push_keyword("interface"); - self.output.push_str(" {\n"); + self.output.indent_start(); self.print_interface(resolve, *id)?; - self.output.push_str("}\n"); + self.output.indent_end(); } WorldItem::Function(f) => { self.print_function(resolve, f)?; @@ -764,15 +764,16 @@ impl WitPrinter { self.output.push_keyword("record"); self.output.push_str(" "); self.print_name(name); - self.output.push_str(" {\n"); + self.output.indent_start(); for field in &record.fields { self.print_docs(&field.docs); self.print_name(&field.name); self.output.push_str(": "); self.print_type_name(resolve, &field.ty)?; - self.output.push_str(",\n"); + self.output.push_str(","); + self.output.newline(); } - self.output.push_str("}\n"); + self.output.indent_end(); Ok(()) } None => bail!("document has unnamed record type"), @@ -802,13 +803,14 @@ impl WitPrinter { self.output.push_keyword("flags"); self.output.push_str(" "); self.print_name(name); - self.output.push_str(" {\n"); + self.output.indent_start(); for flag in &flags.flags { self.print_docs(&flag.docs); self.print_name(&flag.name); - self.output.push_str(",\n"); + self.output.push_str(","); + self.output.newline(); } - self.output.push_str("}\n"); + self.output.indent_end(); } None => bail!("document has unnamed flags type"), } @@ -828,7 +830,7 @@ impl WitPrinter { self.output.push_keyword("variant"); self.output.push_str(" "); self.print_name(name); - self.output.push_str(" {\n"); + self.output.indent_start(); for case in &variant.cases { self.print_docs(&case.docs); self.print_name(&case.name); @@ -837,9 +839,10 @@ impl WitPrinter { self.print_type_name(resolve, &ty)?; self.output.push_str(")"); } - self.output.push_str(",\n"); + self.output.push_str(","); + self.output.newline(); } - self.output.push_str("}\n"); + self.output.indent_end(); Ok(()) } @@ -885,13 +888,14 @@ impl WitPrinter { self.output.push_keyword("enum"); self.output.push_str(" "); self.print_name(name); - self.output.push_str(" {\n"); + self.output.indent_start(); for case in &enum_.cases { self.print_docs(&case.docs); self.print_name(&case.name); - self.output.push_str(",\n"); + self.output.push_str(","); + self.output.newline(); } - self.output.push_str("}\n"); + self.output.indent_end(); Ok(()) } @@ -938,14 +942,16 @@ impl WitPrinter { self.output.push_keyword("version"); self.output.push_str(" = "); self.output.push_str(&since.to_string()); - self.output.push_str(")\n"); + self.output.push_str(")"); + self.output.newline(); if let Some(version) = deprecated { self.output.push_keyword("@deprecated"); self.output.push_str("("); self.output.push_keyword("version"); self.output.push_str(" = "); self.output.push_str(&version.to_string()); - self.output.push_str(")\n"); + self.output.push_str(")"); + self.output.newline(); } } Stability::Unstable { @@ -957,14 +963,16 @@ impl WitPrinter { self.output.push_keyword("feature"); self.output.push_str(" = "); self.output.push_str(feature); - self.output.push_str(")\n"); + self.output.push_str(")"); + self.output.newline(); if let Some(version) = deprecated { self.output.push_keyword("@deprecated"); self.output.push_str("("); self.output.push_keyword("version"); self.output.push_str(" = "); self.output.push_str(&version.to_string()); - self.output.push_str(")\n"); + self.output.push_str(")"); + self.output.newline(); } } } @@ -1038,6 +1046,10 @@ struct Output { } impl Output { + fn newline(&mut self) { + self.push_str("\n"); + } + fn push_keyword(&mut self, src: &str) { assert!(!src.contains('\n')); assert_eq!(src, src.trim()); @@ -1047,12 +1059,22 @@ impl Output { fn push_doc(&mut self, doc: &str) { self.push_str("/// "); self.push_str(doc); - self.push_str("\n"); + self.newline(); } fn semicolon(&mut self) { self.push_str(";"); - self.push_str("\n"); + self.newline(); + } + + fn indent_start(&mut self) { + self.push_str(" {"); + self.newline(); + } + + fn indent_end(&mut self) { + self.push_str("}"); + self.newline(); } fn push_str(&mut self, src: &str) { From 3d92552e3a95035274844d808f448211e96dd65e Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 15:04:55 +0100 Subject: [PATCH 04/18] [wit-component] simplify `push_str` not accepting `\n` --- crates/wit-component/src/printing.rs | 90 ++++++++++++---------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index ae5a48a037..7747a5ee45 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, bail, Result}; use std::collections::HashMap; -use std::fmt::{self, Write}; use std::mem; use wit_parser::*; @@ -102,9 +101,10 @@ impl WitPrinter { self.output.indent_start(); self.print_interface(resolve, *id)?; if is_main { - writeln!(&mut self.output, "}}\n")?; + self.output.indent_end(); + self.output.newline(); } else { - writeln!(&mut self.output, "}}")?; + self.output.indent_end(); } } @@ -116,10 +116,10 @@ impl WitPrinter { self.print_name(name); self.output.indent_start(); self.print_world(resolve, *id)?; - writeln!(&mut self.output, "}}")?; + self.output.indent_end(); } if !is_main { - writeln!(&mut self.output, "}}")?; + self.output.indent_end(); } Ok(()) } @@ -231,10 +231,10 @@ impl WitPrinter { _ => unreachable!(), }; self.print_path_to_interface(resolve, id, my_pkg)?; - write!(&mut self.output, ".{{")?; + self.output.push_str(".{"); // Note: not changing the indentation. for (i, (my_name, other_name)) in tys.into_iter().enumerate() { if i > 0 { - write!(&mut self.output, ", ")?; + self.output.push_str(", "); } if my_name == other_name { self.print_name(my_name); @@ -246,7 +246,7 @@ impl WitPrinter { self.print_name(my_name); } } - write!(&mut self.output, "}}")?; + self.output.push_str("}"); self.output.semicolon(); } @@ -1047,7 +1047,19 @@ struct Output { impl Output { fn newline(&mut self) { - self.push_str("\n"); + // Trim trailing whitespace, if any, then push an indented + // newline + while let Some(c) = self.output.chars().next_back() { + if c.is_whitespace() && c != '\n' { + self.output.pop(); + } else { + break; + } + } + self.output.push('\n'); + for _ in 0..self.indent { + self.output.push_str(" "); + } } fn push_keyword(&mut self, src: &str) { @@ -1078,51 +1090,23 @@ impl Output { } fn push_str(&mut self, src: &str) { - let lines = src.lines().collect::>(); - for (i, line) in lines.iter().enumerate() { - let trimmed = line.trim(); - if trimmed.starts_with('}') && self.output.ends_with(" ") { - self.output.pop(); - self.output.pop(); - } - self.output.push_str(if lines.len() == 1 { - line - } else { - line.trim_start() - }); - if trimmed.ends_with('{') { - self.indent += 1; - } - if trimmed.starts_with('}') { - // Note that a `saturating_sub` is used here to prevent a panic - // here in the case of invalid code being generated in debug - // mode. It's typically easier to debug those issues through - // looking at the source code rather than getting a panic. - self.indent = self.indent.saturating_sub(1); - } - if i != lines.len() - 1 || src.ends_with('\n') { - // Trim trailing whitespace, if any, then push an indented - // newline - while let Some(c) = self.output.chars().next_back() { - if c.is_whitespace() && c != '\n' { - self.output.pop(); - } else { - break; - } - } - self.output.push('\n'); - for _ in 0..self.indent { - self.output.push_str(" "); - } - } + assert!(!src.contains('\n')); + let trimmed = src.trim(); + if trimmed.starts_with('}') && self.output.ends_with(" ") { + self.output.pop(); + self.output.pop(); + } + self.output.push_str(src); + if trimmed.ends_with('{') { + self.indent += 1; + } + if trimmed.starts_with('}') { + // Note that a `saturating_sub` is used here to prevent a panic + // here in the case of invalid code being generated in debug + // mode. It's typically easier to debug those issues through + // looking at the source code rather than getting a panic. + self.indent = self.indent.saturating_sub(1); } - } -} - -impl Write for Output { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.push_str(s); - Ok(()) } } From 5d0b2f30d71d684b2c87bb19cb0a633096874dde Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 15:07:46 +0100 Subject: [PATCH 05/18] [wit-component] remove `push_` prefix from `Output` fns --- crates/wit-component/src/printing.rs | 312 +++++++++++++-------------- 1 file changed, 156 insertions(+), 156 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 7747a5ee45..154b0238cb 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -76,13 +76,13 @@ impl WitPrinter { ) -> Result<()> { let pkg = &resolve.packages[pkg]; self.print_docs(&pkg.docs); - self.output.push_keyword("package"); - self.output.push_str(" "); + self.output.keyword("package"); + self.output.str(" "); self.print_name(&pkg.name.namespace); - self.output.push_str(":"); + self.output.str(":"); self.print_name(&pkg.name.name); if let Some(version) = &pkg.name.version { - self.output.push_str(&format!("@{version}")); + self.output.str(&format!("@{version}")); } if is_main { @@ -95,8 +95,8 @@ impl WitPrinter { for (name, id) in pkg.interfaces.iter() { self.print_docs(&resolve.interfaces[*id].docs); self.print_stability(&resolve.interfaces[*id].stability); - self.output.push_keyword("interface"); - self.output.push_str(" "); + self.output.keyword("interface"); + self.output.str(" "); self.print_name(name); self.output.indent_start(); self.print_interface(resolve, *id)?; @@ -111,8 +111,8 @@ impl WitPrinter { for (name, id) in pkg.worlds.iter() { self.print_docs(&resolve.worlds[*id].docs); self.print_stability(&resolve.worlds[*id].stability); - self.output.push_keyword("world"); - self.output.push_str(" "); + self.output.keyword("world"); + self.output.str(" "); self.print_name(name); self.output.indent_start(); self.print_world(resolve, *id)?; @@ -161,7 +161,7 @@ impl WitPrinter { self.print_docs(&func.docs); self.print_stability(&func.stability); self.print_name(name); - self.output.push_str(": "); + self.output.str(": "); self.print_function(resolve, func)?; self.output.semicolon(); } @@ -222,8 +222,8 @@ impl WitPrinter { for (owner, stability, tys) in types_to_import { self.any_items = true; self.print_stability(stability); - self.output.push_keyword("use"); - self.output.push_str(" "); + self.output.keyword("use"); + self.output.str(" "); let id = match owner { TypeOwner::Interface(id) => id, // it's only possible to import types from interfaces at @@ -231,22 +231,22 @@ impl WitPrinter { _ => unreachable!(), }; self.print_path_to_interface(resolve, id, my_pkg)?; - self.output.push_str(".{"); // Note: not changing the indentation. + self.output.str(".{"); // Note: not changing the indentation. for (i, (my_name, other_name)) in tys.into_iter().enumerate() { if i > 0 { - self.output.push_str(", "); + self.output.str(", "); } if my_name == other_name { self.print_name(my_name); } else { self.print_name(other_name); - self.output.push_str(" "); - self.output.push_keyword("as"); - self.output.push_str(" "); + self.output.str(" "); + self.output.keyword("as"); + self.output.str(" "); self.print_name(my_name); } } - self.output.push_str("}"); + self.output.str("}"); self.output.semicolon(); } @@ -269,8 +269,8 @@ impl WitPrinter { fn print_resource(&mut self, resolve: &Resolve, id: TypeId, funcs: &[&Function]) -> Result<()> { let ty = &resolve.types[id]; - self.output.push_keyword("resource"); - self.output.push_str(" "); + self.output.keyword("resource"); + self.output.str(" "); self.print_name(ty.name.as_ref().expect("resources must be named")); if funcs.is_empty() { self.output.semicolon(); @@ -285,13 +285,13 @@ impl WitPrinter { FunctionKind::Constructor(_) => {} FunctionKind::Method(_) => { self.print_name(func.item_name()); - self.output.push_str(": "); + self.output.str(": "); } FunctionKind::Static(_) => { self.print_name(func.item_name()); - self.output.push_str(": "); - self.output.push_keyword("static"); - self.output.push_str(" "); + self.output.str(": "); + self.output.keyword("static"); + self.output.str(" "); } FunctionKind::Freestanding => unreachable!(), } @@ -307,12 +307,12 @@ impl WitPrinter { // Constructors are named slightly differently. match &func.kind { FunctionKind::Constructor(_) => { - self.output.push_keyword("constructor"); - self.output.push_str("("); + self.output.keyword("constructor"); + self.output.str("("); } _ => { - self.output.push_keyword("func"); - self.output.push_str("("); + self.output.keyword("func"); + self.output.str("("); } } @@ -323,13 +323,13 @@ impl WitPrinter { }; for (i, (name, ty)) in func.params.iter().skip(params_to_skip).enumerate() { if i > 0 { - self.output.push_str(", "); + self.output.str(", "); } self.print_name(name); - self.output.push_str(": "); + self.output.str(": "); self.print_type_name(resolve, ty)?; } - self.output.push_str(")"); + self.output.str(")"); // constructors don't have their results printed if let FunctionKind::Constructor(_) = func.kind { @@ -340,20 +340,20 @@ impl WitPrinter { Results::Named(rs) => match rs.len() { 0 => (), _ => { - self.output.push_str(" -> ("); + self.output.str(" -> ("); for (i, (name, ty)) in rs.iter().enumerate() { if i > 0 { - self.output.push_str(", "); + self.output.str(", "); } self.print_name(name); - self.output.push_str(": "); + self.output.str(": "); self.print_type_name(resolve, ty)?; } - self.output.push_str(")"); + self.output.str(")"); } }, Results::Anon(ty) => { - self.output.push_str(" -> "); + self.output.str(" -> "); self.print_type_name(resolve, ty)?; } } @@ -422,16 +422,16 @@ impl WitPrinter { } self.print_stability(item.stability(resolve)); - self.output.push_keyword(import_or_export_keyword); - self.output.push_str(" "); + self.output.keyword(import_or_export_keyword); + self.output.str(" "); match name { WorldKey::Name(name) => { self.print_name(name); - self.output.push_str(": "); + self.output.str(": "); match item { WorldItem::Interface { id, .. } => { assert!(resolve.interfaces[*id].name.is_none()); - self.output.push_keyword("interface"); + self.output.keyword("interface"); self.output.indent_start(); self.print_interface(resolve, *id)?; self.output.indent_end(); @@ -468,12 +468,12 @@ impl WitPrinter { } else { let pkg = &resolve.packages[iface.package.unwrap()].name; self.print_name(&pkg.namespace); - self.output.push_str(":"); + self.output.str(":"); self.print_name(&pkg.name); - self.output.push_str("/"); + self.output.str("/"); self.print_name(iface.name.as_ref().unwrap()); if let Some(version) = &pkg.version { - self.output.push_str(&format!("@{version}")); + self.output.str(&format!("@{version}")); } } Ok(()) @@ -482,31 +482,31 @@ impl WitPrinter { /// Print the name of type `ty`. pub fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { - Type::Bool => self.output.push_keyword("bool"), - Type::U8 => self.output.push_keyword("u8"), - Type::U16 => self.output.push_keyword("u16"), - Type::U32 => self.output.push_keyword("u32"), - Type::U64 => self.output.push_keyword("u64"), - Type::S8 => self.output.push_keyword("s8"), - Type::S16 => self.output.push_keyword("s16"), - Type::S32 => self.output.push_keyword("s32"), - Type::S64 => self.output.push_keyword("s64"), + Type::Bool => self.output.keyword("bool"), + Type::U8 => self.output.keyword("u8"), + Type::U16 => self.output.keyword("u16"), + Type::U32 => self.output.keyword("u32"), + Type::U64 => self.output.keyword("u64"), + Type::S8 => self.output.keyword("s8"), + Type::S16 => self.output.keyword("s16"), + Type::S32 => self.output.keyword("s32"), + Type::S64 => self.output.keyword("s64"), Type::F32 => { if self.print_f32_f64 { - self.output.push_keyword("f32") + self.output.keyword("f32") } else { - self.output.push_keyword("f32") + self.output.keyword("f32") } } Type::F64 => { if self.print_f32_f64 { - self.output.push_keyword("f64") + self.output.keyword("f64") } else { - self.output.push_keyword("f64") + self.output.keyword("f64") } } - Type::Char => self.output.push_keyword("char"), - Type::String => self.output.push_keyword("string"), + Type::Char => self.output.keyword("char"), + Type::String => self.output.keyword("string"), Type::Id(id) => { let ty = &resolve.types[*id]; @@ -544,10 +544,10 @@ impl WitPrinter { bail!("resolve has unnamed variant type") } TypeDefKind::List(ty) => { - self.output.push_keyword("list"); - self.output.push_str("<"); + self.output.keyword("list"); + self.output.str("<"); self.print_type_name(resolve, ty)?; - self.output.push_str(">"); + self.output.str(">"); } TypeDefKind::Type(ty) => self.print_type_name(resolve, ty)?, TypeDefKind::Future(_) => { @@ -574,8 +574,8 @@ impl WitPrinter { Handle::Own(ty) => { let ty = &resolve.types[*ty]; if force_handle_type_printed { - self.output.push_keyword("own"); - self.output.push_str("<"); + self.output.keyword("own"); + self.output.str("<"); } self.print_name( ty.name @@ -583,20 +583,20 @@ impl WitPrinter { .ok_or_else(|| anyhow!("unnamed resource type"))?, ); if force_handle_type_printed { - self.output.push_str(">"); + self.output.str(">"); } } Handle::Borrow(ty) => { - self.output.push_keyword("borrow"); - self.output.push_str("<"); + self.output.keyword("borrow"); + self.output.str("<"); let ty = &resolve.types[*ty]; self.print_name( ty.name .as_ref() .ok_or_else(|| anyhow!("unnamed resource type"))?, ); - self.output.push_str(">"); + self.output.str(">"); } } @@ -604,24 +604,24 @@ impl WitPrinter { } fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { - self.output.push_keyword("tuple"); - self.output.push_str("<"); + self.output.keyword("tuple"); + self.output.str("<"); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { - self.output.push_str(", "); + self.output.str(", "); } self.print_type_name(resolve, ty)?; } - self.output.push_str(">"); + self.output.str(">"); Ok(()) } fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { - self.output.push_keyword("option"); - self.output.push_str("<"); + self.output.keyword("option"); + self.output.str("<"); self.print_type_name(resolve, payload)?; - self.output.push_str(">"); + self.output.str(">"); Ok(()) } @@ -631,36 +631,36 @@ impl WitPrinter { ok: Some(ok), err: Some(err), } => { - self.output.push_keyword("result"); - self.output.push_str("<"); + self.output.keyword("result"); + self.output.str("<"); self.print_type_name(resolve, ok)?; - self.output.push_str(", "); + self.output.str(", "); self.print_type_name(resolve, err)?; - self.output.push_str(">"); + self.output.str(">"); } Result_ { ok: None, err: Some(err), } => { - self.output.push_keyword("result"); - self.output.push_str("<_, "); + self.output.keyword("result"); + self.output.str("<_, "); self.print_type_name(resolve, err)?; - self.output.push_str(">"); + self.output.str(">"); } Result_ { ok: Some(ok), err: None, } => { - self.output.push_keyword("result"); - self.output.push_str("<"); + self.output.keyword("result"); + self.output.str("<"); self.print_type_name(resolve, ok)?; - self.output.push_str(">"); + self.output.str(">"); } Result_ { ok: None, err: None, } => { - self.output.push_keyword("result"); + self.output.keyword("result"); } } Ok(()) @@ -709,10 +709,10 @@ impl WitPrinter { } TypeDefKind::Type(inner) => match ty.name.as_deref() { Some(name) => { - self.output.push_keyword("type"); - self.output.push_str(" "); + self.output.keyword("type"); + self.output.str(" "); self.print_name(name); - self.output.push_str(" = "); + self.output.str(" = "); self.print_type_name(resolve, inner)?; self.output.semicolon(); } @@ -735,10 +735,10 @@ impl WitPrinter { ) -> Result<()> { match name { Some(name) => { - self.output.push_keyword("type"); - self.output.push_str(" "); + self.output.keyword("type"); + self.output.str(" "); self.print_name(name); - self.output.push_str(" = "); + self.output.str(" = "); // Note that the `true` here forces owned handles to be printed // as `own`. The purpose of this is because `type a = b`, if // `b` is a resource, is encoded differently as `type a = @@ -761,16 +761,16 @@ impl WitPrinter { ) -> Result<()> { match name { Some(name) => { - self.output.push_keyword("record"); - self.output.push_str(" "); + self.output.keyword("record"); + self.output.str(" "); self.print_name(name); self.output.indent_start(); for field in &record.fields { self.print_docs(&field.docs); self.print_name(&field.name); - self.output.push_str(": "); + self.output.str(": "); self.print_type_name(resolve, &field.ty)?; - self.output.push_str(","); + self.output.str(","); self.output.newline(); } self.output.indent_end(); @@ -787,10 +787,10 @@ impl WitPrinter { tuple: &Tuple, ) -> Result<()> { if let Some(name) = name { - self.output.push_keyword("type"); - self.output.push_str(" "); + self.output.keyword("type"); + self.output.str(" "); self.print_name(name); - self.output.push_str(" = "); + self.output.str(" = "); self.print_tuple_type(resolve, tuple)?; self.output.semicolon(); } @@ -800,14 +800,14 @@ impl WitPrinter { fn declare_flags(&mut self, name: Option<&str>, flags: &Flags) -> Result<()> { match name { Some(name) => { - self.output.push_keyword("flags"); - self.output.push_str(" "); + self.output.keyword("flags"); + self.output.str(" "); self.print_name(name); self.output.indent_start(); for flag in &flags.flags { self.print_docs(&flag.docs); self.print_name(&flag.name); - self.output.push_str(","); + self.output.str(","); self.output.newline(); } self.output.indent_end(); @@ -827,19 +827,19 @@ impl WitPrinter { Some(name) => name, None => bail!("document has unnamed variant type"), }; - self.output.push_keyword("variant"); - self.output.push_str(" "); + self.output.keyword("variant"); + self.output.str(" "); self.print_name(name); self.output.indent_start(); for case in &variant.cases { self.print_docs(&case.docs); self.print_name(&case.name); if let Some(ty) = case.ty { - self.output.push_str("("); + self.output.str("("); self.print_type_name(resolve, &ty)?; - self.output.push_str(")"); + self.output.str(")"); } - self.output.push_str(","); + self.output.str(","); self.output.newline(); } self.output.indent_end(); @@ -853,10 +853,10 @@ impl WitPrinter { payload: &Type, ) -> Result<()> { if let Some(name) = name { - self.output.push_keyword("type"); - self.output.push_str(" "); + self.output.keyword("type"); + self.output.str(" "); self.print_name(name); - self.output.push_str(" = "); + self.output.str(" = "); self.print_option_type(resolve, payload)?; self.output.semicolon(); } @@ -870,10 +870,10 @@ impl WitPrinter { result: &Result_, ) -> Result<()> { if let Some(name) = name { - self.output.push_keyword("type"); - self.output.push_str(" "); + self.output.keyword("type"); + self.output.str(" "); self.print_name(name); - self.output.push_str(" = "); + self.output.str(" = "); self.print_result_type(resolve, result)?; self.output.semicolon(); } @@ -885,14 +885,14 @@ impl WitPrinter { Some(name) => name, None => bail!("document has unnamed enum type"), }; - self.output.push_keyword("enum"); - self.output.push_str(" "); + self.output.keyword("enum"); + self.output.str(" "); self.print_name(name); self.output.indent_start(); for case in &enum_.cases { self.print_docs(&case.docs); self.print_name(&case.name); - self.output.push_str(","); + self.output.str(","); self.output.newline(); } self.output.indent_end(); @@ -901,14 +901,14 @@ impl WitPrinter { fn declare_list(&mut self, resolve: &Resolve, name: Option<&str>, ty: &Type) -> Result<()> { if let Some(name) = name { - self.output.push_keyword("type"); - self.output.push_str(" "); + self.output.keyword("type"); + self.output.str(" "); self.print_name(name); - self.output.push_str(" = "); - self.output.push_keyword("list"); - self.output.push_str("<"); + self.output.str(" = "); + self.output.keyword("list"); + self.output.str("<"); self.print_type_name(resolve, ty)?; - self.output.push_str(">"); + self.output.str(">"); self.output.semicolon(); return Ok(()); } @@ -918,16 +918,16 @@ impl WitPrinter { fn print_name(&mut self, name: &str) { if is_keyword(name) { - self.output.push_str("%"); + self.output.str("%"); } - self.output.push_str(name); + self.output.str(name); } fn print_docs(&mut self, docs: &Docs) { if self.emit_docs { if let Some(contents) = &docs.contents { for line in contents.lines() { - self.output.push_doc(line); + self.output.doc(line); } } } @@ -937,20 +937,20 @@ impl WitPrinter { match stability { Stability::Unknown => {} Stability::Stable { since, deprecated } => { - self.output.push_keyword("@since"); - self.output.push_str("("); - self.output.push_keyword("version"); - self.output.push_str(" = "); - self.output.push_str(&since.to_string()); - self.output.push_str(")"); + self.output.keyword("@since"); + self.output.str("("); + self.output.keyword("version"); + self.output.str(" = "); + self.output.str(&since.to_string()); + self.output.str(")"); self.output.newline(); if let Some(version) = deprecated { - self.output.push_keyword("@deprecated"); - self.output.push_str("("); - self.output.push_keyword("version"); - self.output.push_str(" = "); - self.output.push_str(&version.to_string()); - self.output.push_str(")"); + self.output.keyword("@deprecated"); + self.output.str("("); + self.output.keyword("version"); + self.output.str(" = "); + self.output.str(&version.to_string()); + self.output.str(")"); self.output.newline(); } } @@ -958,20 +958,20 @@ impl WitPrinter { feature, deprecated, } => { - self.output.push_keyword("@unstable"); - self.output.push_str("("); - self.output.push_keyword("feature"); - self.output.push_str(" = "); - self.output.push_str(feature); - self.output.push_str(")"); + self.output.keyword("@unstable"); + self.output.str("("); + self.output.keyword("feature"); + self.output.str(" = "); + self.output.str(feature); + self.output.str(")"); self.output.newline(); if let Some(version) = deprecated { - self.output.push_keyword("@deprecated"); - self.output.push_str("("); - self.output.push_keyword("version"); - self.output.push_str(" = "); - self.output.push_str(&version.to_string()); - self.output.push_str(")"); + self.output.keyword("@deprecated"); + self.output.str("("); + self.output.keyword("version"); + self.output.str(" = "); + self.output.str(&version.to_string()); + self.output.str(")"); self.output.newline(); } } @@ -1062,34 +1062,34 @@ impl Output { } } - fn push_keyword(&mut self, src: &str) { + fn keyword(&mut self, src: &str) { assert!(!src.contains('\n')); assert_eq!(src, src.trim()); - self.push_str(src); + self.str(src); } - fn push_doc(&mut self, doc: &str) { - self.push_str("/// "); - self.push_str(doc); + fn doc(&mut self, doc: &str) { + self.str("/// "); + self.str(doc); self.newline(); } fn semicolon(&mut self) { - self.push_str(";"); + self.str(";"); self.newline(); } fn indent_start(&mut self) { - self.push_str(" {"); + self.str(" {"); self.newline(); } fn indent_end(&mut self) { - self.push_str("}"); + self.str("}"); self.newline(); } - fn push_str(&mut self, src: &str) { + fn str(&mut self, src: &str) { assert!(!src.contains('\n')); let trimmed = src.trim(); if trimmed.starts_with('}') && self.output.ends_with(" ") { From 5dfefc66fd2ce2730d9ed351712615b317e06fe3 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 15:22:34 +0100 Subject: [PATCH 06/18] [wit-component] extract the indentation logic --- crates/wit-component/src/printing.rs | 40 +++++++++++++--------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 154b0238cb..10292c1895 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -246,7 +246,7 @@ impl WitPrinter { self.print_name(my_name); } } - self.output.str("}"); + self.output.str("}"); // Note: not changing the indentation. self.output.semicolon(); } @@ -1065,48 +1065,44 @@ impl Output { fn keyword(&mut self, src: &str) { assert!(!src.contains('\n')); assert_eq!(src, src.trim()); - self.str(src); + self.output.push_str(src); } fn doc(&mut self, doc: &str) { - self.str("/// "); - self.str(doc); + assert!(!doc.contains('\n')); + self.output.push_str("/// "); + self.output.push_str(doc); self.newline(); } fn semicolon(&mut self) { - self.str(";"); + self.output.push_str(";"); self.newline(); } fn indent_start(&mut self) { - self.str(" {"); + self.output.push_str(" {"); + self.indent += 1; self.newline(); } fn indent_end(&mut self) { - self.str("}"); + if self.output.ends_with(" ") { + self.output.pop(); + self.output.pop(); + } + self.output.push_str("}"); + // Note that a `saturating_sub` is used here to prevent a panic + // here in the case of invalid code being generated in debug + // mode. It's typically easier to debug those issues through + // looking at the source code rather than getting a panic. + self.indent = self.indent.saturating_sub(1); self.newline(); } fn str(&mut self, src: &str) { assert!(!src.contains('\n')); - let trimmed = src.trim(); - if trimmed.starts_with('}') && self.output.ends_with(" ") { - self.output.pop(); - self.output.pop(); - } self.output.push_str(src); - if trimmed.ends_with('{') { - self.indent += 1; - } - if trimmed.starts_with('}') { - // Note that a `saturating_sub` is used here to prevent a panic - // here in the case of invalid code being generated in debug - // mode. It's typically easier to debug those issues through - // looking at the source code rather than getting a panic. - self.indent = self.indent.saturating_sub(1); - } } } From 48c077e08cf0019d7d69edd5655487a7b57a76fc Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 17:59:20 +0100 Subject: [PATCH 07/18] [wit-component] indent only when needed --- crates/wit-component/src/printing.rs | 47 +++++++++++++++++----------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 10292c1895..858b2dc016 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1043,65 +1043,76 @@ fn is_keyword(name: &str) -> bool { struct Output { indent: usize, output: String, + // set to true after newline, then to false after first item is indented. + needs_indent: bool, } impl Output { - fn newline(&mut self) { - // Trim trailing whitespace, if any, then push an indented - // newline - while let Some(c) = self.output.chars().next_back() { - if c.is_whitespace() && c != '\n' { - self.output.pop(); - } else { - break; + fn indent_if_needed(&mut self) { + if self.needs_indent { + for _ in 0..self.indent { + self.output.push_str(" "); } + self.needs_indent = false; } + } + + fn newline(&mut self) { self.output.push('\n'); - for _ in 0..self.indent { - self.output.push_str(" "); - } + self.needs_indent = true; } fn keyword(&mut self, src: &str) { assert!(!src.contains('\n')); assert_eq!(src, src.trim()); + self.indent_if_needed(); self.output.push_str(src); } fn doc(&mut self, doc: &str) { assert!(!doc.contains('\n')); - self.output.push_str("/// "); - self.output.push_str(doc); + self.indent_if_needed(); + self.output.push_str("///"); + if !doc.is_empty() { + self.output.push_str(" "); + self.output.push_str(doc); + } self.newline(); } fn semicolon(&mut self) { + assert!( + !self.needs_indent, + "`semicolon` is never called after newline" + ); self.output.push_str(";"); self.newline(); } fn indent_start(&mut self) { + assert!( + !self.needs_indent, + "`indent_start` is never called after newline" + ); self.output.push_str(" {"); self.indent += 1; self.newline(); } fn indent_end(&mut self) { - if self.output.ends_with(" ") { - self.output.pop(); - self.output.pop(); - } - self.output.push_str("}"); // Note that a `saturating_sub` is used here to prevent a panic // here in the case of invalid code being generated in debug // mode. It's typically easier to debug those issues through // looking at the source code rather than getting a panic. self.indent = self.indent.saturating_sub(1); + self.indent_if_needed(); + self.output.push_str("}"); self.newline(); } fn str(&mut self, src: &str) { assert!(!src.contains('\n')); + self.indent_if_needed(); self.output.push_str(src); } } From ffb1e569637463d9767edc4a2bd28e3bf4ebd5ed Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 11 Dec 2024 18:58:40 +0100 Subject: [PATCH 08/18] [wit-component] turn `Output` into a trait --- crates/wit-component/src/printing.rs | 108 +++++++++++++++++++-------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 858b2dc016..19d78b0238 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -7,8 +7,9 @@ use wit_parser::*; const PRINT_F32_F64_DEFAULT: bool = true; /// A utility for printing WebAssembly interface definitions to a string. -pub struct WitPrinter { - output: Output, +pub struct WitPrinter { + /// Visitor that holds the WIT document being printed. + pub output: O, // Count of how many items in this current block have been printed to print // a blank line between each item, but not the first item. @@ -22,8 +23,33 @@ pub struct WitPrinter { impl Default for WitPrinter { fn default() -> Self { + Self::new() + } +} + +impl WitPrinter +where + String: From, +{ + /// Prints the specified `pkg` which is located in `resolve` to a string. + /// + /// The `nested` list of packages are other packages to include at the end + /// of the output in `package ... { ... }` syntax. + pub fn print( + &mut self, + resolve: &Resolve, + pkg: PackageId, + nested: &[PackageId], + ) -> Result { + self.print_all(resolve, pkg, nested).map(String::from) + } +} + +impl WitPrinter { + /// Craete new instance. + pub fn new() -> Self { Self { - output: Default::default(), + output: O::default(), any_items: false, emit_docs: true, print_f32_f64: match std::env::var("WIT_REQUIRE_F32_F64") { @@ -32,27 +58,17 @@ impl Default for WitPrinter { }, } } -} - -impl WitPrinter { - /// Configure whether doc comments will be printed. - /// - /// Defaults to true. - pub fn emit_docs(&mut self, enabled: bool) -> &mut Self { - self.emit_docs = enabled; - self - } - /// Prints the specified `pkg` which is located in `resolve` to a string. + /// Prints the specified `pkg` which is located in `resolve` to `O`. /// /// The `nested` list of packages are other packages to include at the end /// of the output in `package ... { ... }` syntax. - pub fn print( + pub fn print_all( &mut self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId], - ) -> Result { + ) -> Result { self.print_package(resolve, pkg, true)?; for (i, pkg_id) in nested.iter().enumerate() { if i > 0 { @@ -62,7 +78,15 @@ impl WitPrinter { self.print_package(resolve, *pkg_id, false)?; } - Ok(std::mem::take(&mut self.output).into()) + Ok(std::mem::take(&mut self.output)) + } + + /// Configure whether doc comments will be printed. + /// + /// Defaults to true. + pub fn emit_docs(&mut self, enabled: bool) -> &mut Self { + self.emit_docs = enabled; + self } /// Prints the specified `pkg`. @@ -1037,26 +1061,54 @@ fn is_keyword(name: &str) -> bool { ) } +/// A visitor that receives tokens emitted by `WitPrinter`. +pub trait Output: Default { + /// A newline is added. + fn newline(&mut self); + /// A keyword is added. Keywords are hardcoded strings from `[a-z]`, but can start with `@` + /// when printing a [Feature Gate](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#feature-gates) + fn keyword(&mut self, src: &str); + /// Called when a single documentation line is added. + /// The `doc` parameter can be an empty string. + fn doc(&mut self, doc: &str); + /// A semicolon is added. + fn semicolon(&mut self); + /// Start of indentation. + fn indent_start(&mut self); + /// End of indentation. + fn indent_end(&mut self); + /// Any string is added. + /// Parameter `src` can contain punctation characters, and must be escaped + /// when outputing to languages like HTML. Does not contain: + /// * newline characters + /// * keywords + /// * documentation comments + fn str(&mut self, src: &str); +} + /// Helper structure to help maintain an indentation level when printing source, -/// modeled after the support in `wit-bindgen-core`. +/// modeled after the support in `wit-bindgen-core`. Indentation is set to two spaces. #[derive(Default)] -struct Output { +pub struct OutputToString { indent: usize, output: String, // set to true after newline, then to false after first item is indented. needs_indent: bool, } -impl Output { +impl OutputToString { fn indent_if_needed(&mut self) { if self.needs_indent { for _ in 0..self.indent { + // Indenting by two spaces. self.output.push_str(" "); } self.needs_indent = false; } } +} +impl Output for OutputToString { fn newline(&mut self) { self.output.push('\n'); self.needs_indent = true; @@ -1074,7 +1126,7 @@ impl Output { self.indent_if_needed(); self.output.push_str("///"); if !doc.is_empty() { - self.output.push_str(" "); + self.output.push(' '); self.output.push_str(doc); } self.newline(); @@ -1085,7 +1137,7 @@ impl Output { !self.needs_indent, "`semicolon` is never called after newline" ); - self.output.push_str(";"); + self.output.push(';'); self.newline(); } @@ -1106,7 +1158,7 @@ impl Output { // looking at the source code rather than getting a panic. self.indent = self.indent.saturating_sub(1); self.indent_if_needed(); - self.output.push_str("}"); + self.output.push('}'); self.newline(); } @@ -1117,14 +1169,8 @@ impl Output { } } -impl From for String { - fn from(output: Output) -> String { +impl From for String { + fn from(output: OutputToString) -> String { output.output } } - -impl From for String { - fn from(value: WitPrinter) -> String { - value.output.output - } -} From 694b9aba013a1e3f43e35e90a9e6e31d76510ca7 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Thu, 12 Dec 2024 10:51:39 +0100 Subject: [PATCH 09/18] [wit-component] add `type`,`param`,`case` to `Output` --- crates/wit-component/src/printing.rs | 235 +++++++++++++++++---------- 1 file changed, 148 insertions(+), 87 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 19d78b0238..b798a137dd 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,6 +1,8 @@ use anyhow::{anyhow, bail, Result}; +use std::borrow::Cow; use std::collections::HashMap; use std::mem; +use std::ops::Deref; use wit_parser::*; // NB: keep in sync with `crates/wit-parser/src/ast/lex.rs` @@ -102,9 +104,9 @@ impl WitPrinter { self.print_docs(&pkg.docs); self.output.keyword("package"); self.output.str(" "); - self.print_name(&pkg.name.namespace); + self.print_name_type(&pkg.name.namespace); self.output.str(":"); - self.print_name(&pkg.name.name); + self.print_name_type(&pkg.name.name); if let Some(version) = &pkg.name.version { self.output.str(&format!("@{version}")); } @@ -121,7 +123,7 @@ impl WitPrinter { self.print_stability(&resolve.interfaces[*id].stability); self.output.keyword("interface"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.indent_start(); self.print_interface(resolve, *id)?; if is_main { @@ -137,7 +139,7 @@ impl WitPrinter { self.print_stability(&resolve.worlds[*id].stability); self.output.keyword("world"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.indent_start(); self.print_world(resolve, *id)?; self.output.indent_end(); @@ -184,7 +186,7 @@ impl WitPrinter { self.new_item(); self.print_docs(&func.docs); self.print_stability(&func.stability); - self.print_name(name); + self.print_name_type(name); self.output.str(": "); self.print_function(resolve, func)?; self.output.semicolon(); @@ -261,13 +263,13 @@ impl WitPrinter { self.output.str(", "); } if my_name == other_name { - self.print_name(my_name); + self.print_name_type(my_name); } else { - self.print_name(other_name); + self.print_name_type(other_name); self.output.str(" "); self.output.keyword("as"); self.output.str(" "); - self.print_name(my_name); + self.print_name_type(my_name); } } self.output.str("}"); // Note: not changing the indentation. @@ -293,9 +295,9 @@ impl WitPrinter { fn print_resource(&mut self, resolve: &Resolve, id: TypeId, funcs: &[&Function]) -> Result<()> { let ty = &resolve.types[id]; - self.output.keyword("resource"); + self.output.r#type("resource"); self.output.str(" "); - self.print_name(ty.name.as_ref().expect("resources must be named")); + self.print_name_type(ty.name.as_ref().expect("resources must be named")); if funcs.is_empty() { self.output.semicolon(); return Ok(()); @@ -308,11 +310,11 @@ impl WitPrinter { match &func.kind { FunctionKind::Constructor(_) => {} FunctionKind::Method(_) => { - self.print_name(func.item_name()); + self.print_name_type(func.item_name()); self.output.str(": "); } FunctionKind::Static(_) => { - self.print_name(func.item_name()); + self.print_name_type(func.item_name()); self.output.str(": "); self.output.keyword("static"); self.output.str(" "); @@ -349,7 +351,7 @@ impl WitPrinter { if i > 0 { self.output.str(", "); } - self.print_name(name); + self.print_name_param(name); self.output.str(": "); self.print_type_name(resolve, ty)?; } @@ -369,7 +371,7 @@ impl WitPrinter { if i > 0 { self.output.str(", "); } - self.print_name(name); + self.print_name_param(name); self.output.str(": "); self.print_type_name(resolve, ty)?; } @@ -450,7 +452,7 @@ impl WitPrinter { self.output.str(" "); match name { WorldKey::Name(name) => { - self.print_name(name); + self.print_name_type(name); self.output.str(": "); match item { WorldItem::Interface { id, .. } => { @@ -488,14 +490,14 @@ impl WitPrinter { ) -> Result<()> { let iface = &resolve.interfaces[interface]; if iface.package == Some(cur_pkg) { - self.print_name(iface.name.as_ref().unwrap()); + self.print_name_type(iface.name.as_ref().unwrap()); } else { let pkg = &resolve.packages[iface.package.unwrap()].name; - self.print_name(&pkg.namespace); + self.print_name_type(&pkg.namespace); self.output.str(":"); - self.print_name(&pkg.name); + self.print_name_type(&pkg.name); self.output.str("/"); - self.print_name(iface.name.as_ref().unwrap()); + self.print_name_type(iface.name.as_ref().unwrap()); if let Some(version) = &pkg.version { self.output.str(&format!("@{version}")); } @@ -506,36 +508,36 @@ impl WitPrinter { /// Print the name of type `ty`. pub fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { - Type::Bool => self.output.keyword("bool"), - Type::U8 => self.output.keyword("u8"), - Type::U16 => self.output.keyword("u16"), - Type::U32 => self.output.keyword("u32"), - Type::U64 => self.output.keyword("u64"), - Type::S8 => self.output.keyword("s8"), - Type::S16 => self.output.keyword("s16"), - Type::S32 => self.output.keyword("s32"), - Type::S64 => self.output.keyword("s64"), + Type::Bool => self.output.r#type("bool"), + Type::U8 => self.output.r#type("u8"), + Type::U16 => self.output.r#type("u16"), + Type::U32 => self.output.r#type("u32"), + Type::U64 => self.output.r#type("u64"), + Type::S8 => self.output.r#type("s8"), + Type::S16 => self.output.r#type("s16"), + Type::S32 => self.output.r#type("s32"), + Type::S64 => self.output.r#type("s64"), Type::F32 => { if self.print_f32_f64 { - self.output.keyword("f32") + self.output.r#type("f32") } else { - self.output.keyword("f32") + self.output.r#type("f32") } } Type::F64 => { if self.print_f32_f64 { - self.output.keyword("f64") + self.output.r#type("f64") } else { - self.output.keyword("f64") + self.output.r#type("f64") } } - Type::Char => self.output.keyword("char"), - Type::String => self.output.keyword("string"), + Type::Char => self.output.r#type("char"), + Type::String => self.output.r#type("string"), Type::Id(id) => { let ty = &resolve.types[*id]; if let Some(name) = &ty.name { - self.print_name(name); + self.print_name_type(name); return Ok(()); } @@ -568,10 +570,10 @@ impl WitPrinter { bail!("resolve has unnamed variant type") } TypeDefKind::List(ty) => { - self.output.keyword("list"); - self.output.str("<"); + self.output.r#type("list"); + self.output.generic_args_start(); self.print_type_name(resolve, ty)?; - self.output.str(">"); + self.output.generic_args_end(); } TypeDefKind::Type(ty) => self.print_type_name(resolve, ty)?, TypeDefKind::Future(_) => { @@ -598,29 +600,29 @@ impl WitPrinter { Handle::Own(ty) => { let ty = &resolve.types[*ty]; if force_handle_type_printed { - self.output.keyword("own"); - self.output.str("<"); + self.output.r#type("own"); + self.output.generic_args_start(); } - self.print_name( + self.print_name_type( ty.name .as_ref() .ok_or_else(|| anyhow!("unnamed resource type"))?, ); if force_handle_type_printed { - self.output.str(">"); + self.output.generic_args_end(); } } Handle::Borrow(ty) => { - self.output.keyword("borrow"); - self.output.str("<"); + self.output.r#type("borrow"); + self.output.generic_args_start(); let ty = &resolve.types[*ty]; - self.print_name( + self.print_name_type( ty.name .as_ref() .ok_or_else(|| anyhow!("unnamed resource type"))?, ); - self.output.str(">"); + self.output.generic_args_end(); } } @@ -628,24 +630,24 @@ impl WitPrinter { } fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { - self.output.keyword("tuple"); - self.output.str("<"); + self.output.r#type("tuple"); + self.output.generic_args_start(); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { self.output.str(", "); } self.print_type_name(resolve, ty)?; } - self.output.str(">"); + self.output.generic_args_end(); Ok(()) } fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { - self.output.keyword("option"); - self.output.str("<"); + self.output.r#type("option"); + self.output.generic_args_start(); self.print_type_name(resolve, payload)?; - self.output.str(">"); + self.output.generic_args_end(); Ok(()) } @@ -655,36 +657,37 @@ impl WitPrinter { ok: Some(ok), err: Some(err), } => { - self.output.keyword("result"); - self.output.str("<"); + self.output.r#type("result"); + self.output.generic_args_start(); self.print_type_name(resolve, ok)?; self.output.str(", "); self.print_type_name(resolve, err)?; - self.output.str(">"); + self.output.generic_args_end(); } Result_ { ok: None, err: Some(err), } => { - self.output.keyword("result"); - self.output.str("<_, "); + self.output.r#type("result"); + self.output.generic_args_start(); + self.output.str("_, "); self.print_type_name(resolve, err)?; - self.output.str(">"); + self.output.generic_args_end(); } Result_ { ok: Some(ok), err: None, } => { - self.output.keyword("result"); - self.output.str("<"); + self.output.r#type("result"); + self.output.generic_args_start(); self.print_type_name(resolve, ok)?; - self.output.str(">"); + self.output.generic_args_end(); } Result_ { ok: None, err: None, } => { - self.output.keyword("result"); + self.output.r#type("result"); } } Ok(()) @@ -735,7 +738,7 @@ impl WitPrinter { Some(name) => { self.output.keyword("type"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.str(" = "); self.print_type_name(resolve, inner)?; self.output.semicolon(); @@ -761,7 +764,7 @@ impl WitPrinter { Some(name) => { self.output.keyword("type"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.str(" = "); // Note that the `true` here forces owned handles to be printed // as `own`. The purpose of this is because `type a = b`, if @@ -787,11 +790,11 @@ impl WitPrinter { Some(name) => { self.output.keyword("record"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.indent_start(); for field in &record.fields { self.print_docs(&field.docs); - self.print_name(&field.name); + self.print_name_param(&field.name); self.output.str(": "); self.print_type_name(resolve, &field.ty)?; self.output.str(","); @@ -813,7 +816,7 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.str(" = "); self.print_tuple_type(resolve, tuple)?; self.output.semicolon(); @@ -826,11 +829,11 @@ impl WitPrinter { Some(name) => { self.output.keyword("flags"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.indent_start(); for flag in &flags.flags { self.print_docs(&flag.docs); - self.print_name(&flag.name); + self.print_name_case(&flag.name); self.output.str(","); self.output.newline(); } @@ -853,11 +856,11 @@ impl WitPrinter { }; self.output.keyword("variant"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.indent_start(); for case in &variant.cases { self.print_docs(&case.docs); - self.print_name(&case.name); + self.print_name_case(&case.name); if let Some(ty) = case.ty { self.output.str("("); self.print_type_name(resolve, &ty)?; @@ -879,7 +882,7 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.str(" = "); self.print_option_type(resolve, payload)?; self.output.semicolon(); @@ -896,7 +899,7 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.str(" = "); self.print_result_type(resolve, result)?; self.output.semicolon(); @@ -911,11 +914,11 @@ impl WitPrinter { }; self.output.keyword("enum"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.indent_start(); for case in &enum_.cases { self.print_docs(&case.docs); - self.print_name(&case.name); + self.print_name_case(&case.name); self.output.str(","); self.output.newline(); } @@ -927,9 +930,9 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name(name); + self.print_name_type(name); self.output.str(" = "); - self.output.keyword("list"); + self.output.r#type("list"); self.output.str("<"); self.print_type_name(resolve, ty)?; self.output.str(">"); @@ -940,11 +943,24 @@ impl WitPrinter { Ok(()) } - fn print_name(&mut self, name: &str) { + fn escape_name(name: &str) -> Cow { if is_keyword(name) { - self.output.str("%"); + Cow::Owned(format!("%{name}")) + } else { + Cow::Borrowed(name) } - self.output.str(name); + } + + fn print_name_type(&mut self, name: &str) { + self.output.r#type(Self::escape_name(name).deref()); + } + + fn print_name_param(&mut self, name: &str) { + self.output.param(Self::escape_name(name).deref()); + } + + fn print_name_case(&mut self, name: &str) { + self.output.case(Self::escape_name(name).deref()); } fn print_docs(&mut self, docs: &Docs) { @@ -1068,6 +1084,16 @@ pub trait Output: Default { /// A keyword is added. Keywords are hardcoded strings from `[a-z]`, but can start with `@` /// when printing a [Feature Gate](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#feature-gates) fn keyword(&mut self, src: &str); + /// A type is added. + fn r#type(&mut self, src: &str); + /// A parameter name of a record or named return is added. + fn param(&mut self, src: &str); + /// A case belonging to a variant, enum or flags is added. + fn case(&mut self, src: &str); + /// Generic argument section starts. + fn generic_args_start(&mut self); + /// Generic argument section ends. + fn generic_args_end(&mut self); /// Called when a single documentation line is added. /// The `doc` parameter can be an empty string. fn doc(&mut self, doc: &str); @@ -1106,6 +1132,18 @@ impl OutputToString { self.needs_indent = false; } } + + fn indent_and_print(&mut self, src: &str) { + assert!(!src.contains('\n')); + if src.starts_with(' ') { + assert!( + !self.needs_indent, + "cannot add a space at the begining of a line" + ); + } + self.indent_if_needed(); + self.output.push_str(src); + } } impl Output for OutputToString { @@ -1115,10 +1153,35 @@ impl Output for OutputToString { } fn keyword(&mut self, src: &str) { - assert!(!src.contains('\n')); - assert_eq!(src, src.trim()); - self.indent_if_needed(); - self.output.push_str(src); + self.indent_and_print(src); + } + + fn r#type(&mut self, src: &str) { + self.indent_and_print(src); + } + + fn param(&mut self, src: &str) { + self.indent_and_print(src); + } + + fn case(&mut self, src: &str) { + self.indent_and_print(src); + } + + fn generic_args_start(&mut self) { + assert!( + !self.needs_indent, + "`subtype_start` is never called after newline" + ); + self.output.push('<'); + } + + fn generic_args_end(&mut self) { + assert!( + !self.needs_indent, + "`subtype_end` is never called after newline" + ); + self.output.push('>'); } fn doc(&mut self, doc: &str) { @@ -1163,9 +1226,7 @@ impl Output for OutputToString { } fn str(&mut self, src: &str) { - assert!(!src.contains('\n')); - self.indent_if_needed(); - self.output.push_str(src); + self.indent_and_print(src); } } From ce06f4d67c9e5aed84f4746c272c46f15bc75a4a Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Thu, 12 Dec 2024 11:49:03 +0100 Subject: [PATCH 10/18] [wit-component] add `version` to `Output` --- crates/wit-component/src/printing.rs | 41 ++++++++++++++++++---------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index b798a137dd..ec9de025a6 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -108,7 +108,7 @@ impl WitPrinter { self.output.str(":"); self.print_name_type(&pkg.name.name); if let Some(version) = &pkg.name.version { - self.output.str(&format!("@{version}")); + self.output.version(&version.to_string(), true); } if is_main { @@ -499,7 +499,7 @@ impl WitPrinter { self.output.str("/"); self.print_name_type(iface.name.as_ref().unwrap()); if let Some(version) = &pkg.version { - self.output.str(&format!("@{version}")); + self.output.version(&version.to_string(), true); } } Ok(()) @@ -981,7 +981,7 @@ impl WitPrinter { self.output.str("("); self.output.keyword("version"); self.output.str(" = "); - self.output.str(&since.to_string()); + self.output.version(&since.to_string(), false); self.output.str(")"); self.output.newline(); if let Some(version) = deprecated { @@ -989,7 +989,7 @@ impl WitPrinter { self.output.str("("); self.output.keyword("version"); self.output.str(" = "); - self.output.str(&version.to_string()); + self.output.version(&version.to_string(), false); self.output.str(")"); self.output.newline(); } @@ -1010,7 +1010,7 @@ impl WitPrinter { self.output.str("("); self.output.keyword("version"); self.output.str(" = "); - self.output.str(&version.to_string()); + self.output.version(&version.to_string(), false); self.output.str(")"); self.output.newline(); } @@ -1090,25 +1090,27 @@ pub trait Output: Default { fn param(&mut self, src: &str); /// A case belonging to a variant, enum or flags is added. fn case(&mut self, src: &str); - /// Generic argument section starts. + /// Generic argument section starts. In WIT this represents the `<` character. fn generic_args_start(&mut self); - /// Generic argument section ends. + /// Generic argument section ends. In WIT this represents the '>' character. fn generic_args_end(&mut self); /// Called when a single documentation line is added. - /// The `doc` parameter can be an empty string. + /// The `doc` parameter starts with `///` omitted, and can be an empty string. fn doc(&mut self, doc: &str); + /// A version is added. + /// + /// Parameter `src` never starts with the `@` prefix. + /// Parameter `at_sign` signals whether the `@` sign should be printed or not. + fn version(&mut self, src: &str, at_sign: bool); /// A semicolon is added. fn semicolon(&mut self); - /// Start of indentation. + /// Start of indentation. In WIT this represents ` {\n`. fn indent_start(&mut self); - /// End of indentation. + /// End of indentation. In WIT this represents `}\n`. fn indent_end(&mut self); - /// Any string is added. + /// Any string that does not have a specialized function is added. /// Parameter `src` can contain punctation characters, and must be escaped - /// when outputing to languages like HTML. Does not contain: - /// * newline characters - /// * keywords - /// * documentation comments + /// when outputing to languages like HTML. fn str(&mut self, src: &str); } @@ -1195,6 +1197,15 @@ impl Output for OutputToString { self.newline(); } + fn version(&mut self, src: &str, at_sign: bool) { + assert!(!src.starts_with('@')); + assert!(!src.contains('\n')); + if at_sign { + self.output.push('@'); + } + self.output.push_str(src); + } + fn semicolon(&mut self) { assert!( !self.needs_indent, From f6d88ad0fa4b4bef44b9e590d6cc1309942dab2e Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Thu, 12 Dec 2024 15:24:42 +0100 Subject: [PATCH 11/18] [wit-component] extract `print_package_outer`, `print_interface_outer` --- crates/wit-component/src/printing.rs | 58 ++++++++++++++++++---------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index ec9de025a6..b1397787cb 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -101,15 +101,7 @@ impl WitPrinter { is_main: bool, ) -> Result<()> { let pkg = &resolve.packages[pkg]; - self.print_docs(&pkg.docs); - self.output.keyword("package"); - self.output.str(" "); - self.print_name_type(&pkg.name.namespace); - self.output.str(":"); - self.print_name_type(&pkg.name.name); - if let Some(version) = &pkg.name.version { - self.output.version(&version.to_string(), true); - } + self.print_package_outer(pkg)?; if is_main { self.output.semicolon(); @@ -119,18 +111,12 @@ impl WitPrinter { } for (name, id) in pkg.interfaces.iter() { - self.print_docs(&resolve.interfaces[*id].docs); - self.print_stability(&resolve.interfaces[*id].stability); - self.output.keyword("interface"); - self.output.str(" "); - self.print_name_type(name); + self.print_interface_outer(resolve, *id, name)?; self.output.indent_start(); self.print_interface(resolve, *id)?; + self.output.indent_end(); if is_main { - self.output.indent_end(); self.output.newline(); - } else { - self.output.indent_end(); } } @@ -150,6 +136,21 @@ impl WitPrinter { Ok(()) } + /// Print the specified package without its content. + /// Does not print the semicolon nor starts the indentation. + pub fn print_package_outer(&mut self, pkg: &Package) -> Result<()> { + self.print_docs(&pkg.docs); + self.output.keyword("package"); + self.output.str(" "); + self.print_name_type(&pkg.name.namespace); + self.output.str(":"); + self.print_name_type(&pkg.name.name); + if let Some(version) = &pkg.name.version { + self.output.version(&version.to_string(), true); + } + Ok(()) + } + fn new_item(&mut self) { if self.any_items { self.output.newline(); @@ -157,8 +158,24 @@ impl WitPrinter { self.any_items = true; } - /// Print the given WebAssembly interface to a string. - fn print_interface(&mut self, resolve: &Resolve, id: InterfaceId) -> Result<()> { + /// Print the given WebAssembly interface without its content. + /// Does not print the semicolon nor starts the indentation. + pub fn print_interface_outer( + &mut self, + resolve: &Resolve, + id: InterfaceId, + name: &str, + ) -> Result<()> { + self.print_docs(&resolve.interfaces[id].docs); + self.print_stability(&resolve.interfaces[id].stability); + self.output.keyword("interface"); + self.output.str(" "); + self.print_name_type(name); + Ok(()) + } + + /// Print the inner content of a given WebAssembly interface. + pub fn print_interface(&mut self, resolve: &Resolve, id: InterfaceId) -> Result<()> { let prev_items = mem::replace(&mut self.any_items, false); let interface = &resolve.interfaces[id]; @@ -197,7 +214,8 @@ impl WitPrinter { Ok(()) } - fn print_types<'a>( + /// Print types of an interface. + pub fn print_types<'a>( &mut self, resolve: &Resolve, owner: TypeOwner, From 5625b636410ffb118e17b8981ed7c3edcdaf0f1a Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Fri, 13 Dec 2024 10:44:55 +0100 Subject: [PATCH 12/18] [wit-component] add `TypeKind` for syntax highlighting --- crates/wit-component/src/printing.rs | 210 +++++++++++++++++---------- 1 file changed, 133 insertions(+), 77 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index b1397787cb..6fa60a5bae 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -125,7 +125,7 @@ impl WitPrinter { self.print_stability(&resolve.worlds[*id].stability); self.output.keyword("world"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::WorldDeclaration); self.output.indent_start(); self.print_world(resolve, *id)?; self.output.indent_end(); @@ -142,11 +142,11 @@ impl WitPrinter { self.print_docs(&pkg.docs); self.output.keyword("package"); self.output.str(" "); - self.print_name_type(&pkg.name.namespace); + self.print_name_type(&pkg.name.namespace, TypeKind::NamespaceDeclaration); self.output.str(":"); - self.print_name_type(&pkg.name.name); + self.print_name_type(&pkg.name.name, TypeKind::PackageNameDeclaration); if let Some(version) = &pkg.name.version { - self.output.version(&version.to_string(), true); + self.print_name_type(&format!("@{version}"), TypeKind::VersionDeclaration); } Ok(()) } @@ -170,7 +170,7 @@ impl WitPrinter { self.print_stability(&resolve.interfaces[id].stability); self.output.keyword("interface"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::InterfaceDeclaration); Ok(()) } @@ -203,7 +203,7 @@ impl WitPrinter { self.new_item(); self.print_docs(&func.docs); self.print_stability(&func.stability); - self.print_name_type(name); + self.print_name_type(name, TypeKind::FunctionFreestanding); self.output.str(": "); self.print_function(resolve, func)?; self.output.semicolon(); @@ -281,13 +281,13 @@ impl WitPrinter { self.output.str(", "); } if my_name == other_name { - self.print_name_type(my_name); + self.print_name_type(my_name, TypeKind::TypeImport); } else { - self.print_name_type(other_name); + self.print_name_type(other_name, TypeKind::TypeImport); self.output.str(" "); self.output.keyword("as"); self.output.str(" "); - self.print_name_type(my_name); + self.print_name_type(my_name, TypeKind::TypeAlias); } } self.output.str("}"); // Note: not changing the indentation. @@ -313,9 +313,12 @@ impl WitPrinter { fn print_resource(&mut self, resolve: &Resolve, id: TypeId, funcs: &[&Function]) -> Result<()> { let ty = &resolve.types[id]; - self.output.r#type("resource"); + self.output.r#type("resource", TypeKind::BuiltIn); self.output.str(" "); - self.print_name_type(ty.name.as_ref().expect("resources must be named")); + self.print_name_type( + ty.name.as_ref().expect("resources must be named"), + TypeKind::Resource, + ); if funcs.is_empty() { self.output.semicolon(); return Ok(()); @@ -328,11 +331,11 @@ impl WitPrinter { match &func.kind { FunctionKind::Constructor(_) => {} FunctionKind::Method(_) => { - self.print_name_type(func.item_name()); + self.print_name_type(func.item_name(), TypeKind::FunctionMethod); self.output.str(": "); } FunctionKind::Static(_) => { - self.print_name_type(func.item_name()); + self.print_name_type(func.item_name(), TypeKind::FunctionStatic); self.output.str(": "); self.output.keyword("static"); self.output.str(" "); @@ -470,7 +473,7 @@ impl WitPrinter { self.output.str(" "); match name { WorldKey::Name(name) => { - self.print_name_type(name); + self.print_name_type(name, TypeKind::Other); self.output.str(": "); match item { WorldItem::Interface { id, .. } => { @@ -508,16 +511,16 @@ impl WitPrinter { ) -> Result<()> { let iface = &resolve.interfaces[interface]; if iface.package == Some(cur_pkg) { - self.print_name_type(iface.name.as_ref().unwrap()); + self.print_name_type(iface.name.as_ref().unwrap(), TypeKind::InterfacePath); } else { let pkg = &resolve.packages[iface.package.unwrap()].name; - self.print_name_type(&pkg.namespace); + self.print_name_type(&pkg.namespace, TypeKind::NamespacePath); self.output.str(":"); - self.print_name_type(&pkg.name); + self.print_name_type(&pkg.name, TypeKind::PackageNamePath); self.output.str("/"); - self.print_name_type(iface.name.as_ref().unwrap()); + self.print_name_type(iface.name.as_ref().unwrap(), TypeKind::InterfacePath); if let Some(version) = &pkg.version { - self.output.version(&version.to_string(), true); + self.print_name_type(&format!("@{version}"), TypeKind::VersionPath); } } Ok(()) @@ -526,36 +529,36 @@ impl WitPrinter { /// Print the name of type `ty`. pub fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { - Type::Bool => self.output.r#type("bool"), - Type::U8 => self.output.r#type("u8"), - Type::U16 => self.output.r#type("u16"), - Type::U32 => self.output.r#type("u32"), - Type::U64 => self.output.r#type("u64"), - Type::S8 => self.output.r#type("s8"), - Type::S16 => self.output.r#type("s16"), - Type::S32 => self.output.r#type("s32"), - Type::S64 => self.output.r#type("s64"), + Type::Bool => self.output.r#type("bool", TypeKind::BuiltIn), + Type::U8 => self.output.r#type("u8", TypeKind::BuiltIn), + Type::U16 => self.output.r#type("u16", TypeKind::BuiltIn), + Type::U32 => self.output.r#type("u32", TypeKind::BuiltIn), + Type::U64 => self.output.r#type("u64", TypeKind::BuiltIn), + Type::S8 => self.output.r#type("s8", TypeKind::BuiltIn), + Type::S16 => self.output.r#type("s16", TypeKind::BuiltIn), + Type::S32 => self.output.r#type("s32", TypeKind::BuiltIn), + Type::S64 => self.output.r#type("s64", TypeKind::BuiltIn), Type::F32 => { if self.print_f32_f64 { - self.output.r#type("f32") + self.output.r#type("f32", TypeKind::BuiltIn) } else { - self.output.r#type("f32") + self.output.r#type("f32", TypeKind::BuiltIn) } } Type::F64 => { if self.print_f32_f64 { - self.output.r#type("f64") + self.output.r#type("f64", TypeKind::BuiltIn) } else { - self.output.r#type("f64") + self.output.r#type("f64", TypeKind::BuiltIn) } } - Type::Char => self.output.r#type("char"), - Type::String => self.output.r#type("string"), + Type::Char => self.output.r#type("char", TypeKind::BuiltIn), + Type::String => self.output.r#type("string", TypeKind::BuiltIn), Type::Id(id) => { let ty = &resolve.types[*id]; if let Some(name) = &ty.name { - self.print_name_type(name); + self.print_name_type(name, TypeKind::Other); return Ok(()); } @@ -588,7 +591,7 @@ impl WitPrinter { bail!("resolve has unnamed variant type") } TypeDefKind::List(ty) => { - self.output.r#type("list"); + self.output.r#type("list", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, ty)?; self.output.generic_args_end(); @@ -618,13 +621,14 @@ impl WitPrinter { Handle::Own(ty) => { let ty = &resolve.types[*ty]; if force_handle_type_printed { - self.output.r#type("own"); + self.output.r#type("own", TypeKind::BuiltIn); self.output.generic_args_start(); } self.print_name_type( ty.name .as_ref() .ok_or_else(|| anyhow!("unnamed resource type"))?, + TypeKind::Resource, ); if force_handle_type_printed { self.output.generic_args_end(); @@ -632,13 +636,14 @@ impl WitPrinter { } Handle::Borrow(ty) => { - self.output.r#type("borrow"); + self.output.r#type("borrow", TypeKind::BuiltIn); self.output.generic_args_start(); let ty = &resolve.types[*ty]; self.print_name_type( ty.name .as_ref() .ok_or_else(|| anyhow!("unnamed resource type"))?, + TypeKind::Resource, ); self.output.generic_args_end(); } @@ -648,7 +653,7 @@ impl WitPrinter { } fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { - self.output.r#type("tuple"); + self.output.r#type("tuple", TypeKind::BuiltIn); self.output.generic_args_start(); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { @@ -662,7 +667,7 @@ impl WitPrinter { } fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { - self.output.r#type("option"); + self.output.r#type("option", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, payload)?; self.output.generic_args_end(); @@ -675,7 +680,7 @@ impl WitPrinter { ok: Some(ok), err: Some(err), } => { - self.output.r#type("result"); + self.output.r#type("result", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, ok)?; self.output.str(", "); @@ -686,7 +691,7 @@ impl WitPrinter { ok: None, err: Some(err), } => { - self.output.r#type("result"); + self.output.r#type("result", TypeKind::BuiltIn); self.output.generic_args_start(); self.output.str("_, "); self.print_type_name(resolve, err)?; @@ -696,7 +701,7 @@ impl WitPrinter { ok: Some(ok), err: None, } => { - self.output.r#type("result"); + self.output.r#type("result", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, ok)?; self.output.generic_args_end(); @@ -705,7 +710,7 @@ impl WitPrinter { ok: None, err: None, } => { - self.output.r#type("result"); + self.output.r#type("result", TypeKind::BuiltIn); } } Ok(()) @@ -756,7 +761,7 @@ impl WitPrinter { Some(name) => { self.output.keyword("type"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::TypeName); self.output.str(" = "); self.print_type_name(resolve, inner)?; self.output.semicolon(); @@ -782,7 +787,7 @@ impl WitPrinter { Some(name) => { self.output.keyword("type"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Resource); self.output.str(" = "); // Note that the `true` here forces owned handles to be printed // as `own`. The purpose of this is because `type a = b`, if @@ -808,7 +813,7 @@ impl WitPrinter { Some(name) => { self.output.keyword("record"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Record); self.output.indent_start(); for field in &record.fields { self.print_docs(&field.docs); @@ -834,7 +839,7 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Tuple); self.output.str(" = "); self.print_tuple_type(resolve, tuple)?; self.output.semicolon(); @@ -847,7 +852,7 @@ impl WitPrinter { Some(name) => { self.output.keyword("flags"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Flags); self.output.indent_start(); for flag in &flags.flags { self.print_docs(&flag.docs); @@ -874,7 +879,7 @@ impl WitPrinter { }; self.output.keyword("variant"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Variant); self.output.indent_start(); for case in &variant.cases { self.print_docs(&case.docs); @@ -900,7 +905,7 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Option); self.output.str(" = "); self.print_option_type(resolve, payload)?; self.output.semicolon(); @@ -917,7 +922,7 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Result); self.output.str(" = "); self.print_result_type(resolve, result)?; self.output.semicolon(); @@ -932,7 +937,7 @@ impl WitPrinter { }; self.output.keyword("enum"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::Enum); self.output.indent_start(); for case in &enum_.cases { self.print_docs(&case.docs); @@ -948,9 +953,9 @@ impl WitPrinter { if let Some(name) = name { self.output.keyword("type"); self.output.str(" "); - self.print_name_type(name); + self.print_name_type(name, TypeKind::List); self.output.str(" = "); - self.output.r#type("list"); + self.output.r#type("list", TypeKind::BuiltIn); self.output.str("<"); self.print_type_name(resolve, ty)?; self.output.str(">"); @@ -969,8 +974,8 @@ impl WitPrinter { } } - fn print_name_type(&mut self, name: &str) { - self.output.r#type(Self::escape_name(name).deref()); + fn print_name_type(&mut self, name: &str, kind: TypeKind) { + self.output.r#type(Self::escape_name(name).deref(), kind); } fn print_name_param(&mut self, name: &str) { @@ -999,7 +1004,7 @@ impl WitPrinter { self.output.str("("); self.output.keyword("version"); self.output.str(" = "); - self.output.version(&since.to_string(), false); + self.print_name_type(&since.to_string(), TypeKind::VersionAnnotation); self.output.str(")"); self.output.newline(); if let Some(version) = deprecated { @@ -1007,7 +1012,7 @@ impl WitPrinter { self.output.str("("); self.output.keyword("version"); self.output.str(" = "); - self.output.version(&version.to_string(), false); + self.print_name_type(&version.to_string(), TypeKind::VersionAnnotation); self.output.str(")"); self.output.newline(); } @@ -1028,7 +1033,7 @@ impl WitPrinter { self.output.str("("); self.output.keyword("version"); self.output.str(" = "); - self.output.version(&version.to_string(), false); + self.print_name_type(&version.to_string(), TypeKind::VersionAnnotation); self.output.str(")"); self.output.newline(); } @@ -1103,8 +1108,8 @@ pub trait Output: Default { /// when printing a [Feature Gate](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#feature-gates) fn keyword(&mut self, src: &str); /// A type is added. - fn r#type(&mut self, src: &str); - /// A parameter name of a record or named return is added. + fn r#type(&mut self, src: &str, kind: TypeKind); + /// A parameter name of a function, record or a named return is added. fn param(&mut self, src: &str); /// A case belonging to a variant, enum or flags is added. fn case(&mut self, src: &str); @@ -1115,11 +1120,6 @@ pub trait Output: Default { /// Called when a single documentation line is added. /// The `doc` parameter starts with `///` omitted, and can be an empty string. fn doc(&mut self, doc: &str); - /// A version is added. - /// - /// Parameter `src` never starts with the `@` prefix. - /// Parameter `at_sign` signals whether the `@` sign should be printed or not. - fn version(&mut self, src: &str, at_sign: bool); /// A semicolon is added. fn semicolon(&mut self); /// Start of indentation. In WIT this represents ` {\n`. @@ -1132,6 +1132,71 @@ pub trait Output: Default { fn str(&mut self, src: &str); } +/// Represents the different kinds of types that can be encountered while +/// visiting a WIT file. +/// +/// Each variant refers to the name of the respective element (e.g., function, type, or namespace), +/// not the entire declaration. +#[non_exhaustive] +#[derive(Clone, Copy, Debug)] +pub enum TypeKind { + /// A built-in type, such as "list" or "option". + BuiltIn, + /// An enumeration type name. + Enum, + /// A flags type name. + Flags, + /// A freestanding function name, not associated with any specific type or namespace. + /// For example, "myfunc" in `myfunc: func() -> string;`. + FunctionFreestanding, + /// A method, associated with a resource. + FunctionMethod, + /// A static function, associated with a resource. + FunctionStatic, + /// An interface declaration name. + InterfaceDeclaration, + /// An interface name when printing a path, for example in `use`. + InterfacePath, + /// A list type name. + List, + /// A namespace declaration. + NamespaceDeclaration, + /// A namespace when printing a path, for example in `use`. + NamespacePath, + /// An option type name. + Option, + /// A package name declaration. + PackageNameDeclaration, + /// A package name when printing a path, for example in `use`. + PackageNamePath, + /// A record type name. + Record, + /// A resource type name. + Resource, + /// A result type name. + Result, + /// A tuple type name. + Tuple, + /// A type alias. + TypeAlias, + /// An imported type name. + TypeImport, + /// A user-defined type name. + TypeName, + /// A variant type name. + Variant, + /// A version declaration. + VersionDeclaration, + /// A version when printing a path, for example in `use`. + VersionPath, + /// A version when printing stability annotations, for example in `@since` + VersionAnnotation, + /// A world declaration name. + WorldDeclaration, + /// A fallback for types that do not fit into any other category. + Other, +} + /// Helper structure to help maintain an indentation level when printing source, /// modeled after the support in `wit-bindgen-core`. Indentation is set to two spaces. #[derive(Default)] @@ -1176,7 +1241,7 @@ impl Output for OutputToString { self.indent_and_print(src); } - fn r#type(&mut self, src: &str) { + fn r#type(&mut self, src: &str, _kind: TypeKind) { self.indent_and_print(src); } @@ -1215,15 +1280,6 @@ impl Output for OutputToString { self.newline(); } - fn version(&mut self, src: &str, at_sign: bool) { - assert!(!src.starts_with('@')); - assert!(!src.contains('\n')); - if at_sign { - self.output.push('@'); - } - self.output.push_str(src); - } - fn semicolon(&mut self) { assert!( !self.needs_indent, From c23ad57612915dbf61f9966532da77e5e142dd56 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Fri, 13 Dec 2024 14:29:34 +0100 Subject: [PATCH 13/18] [wit-component] Introduce `WitPrinterExt` Extract the parametrized `Output` from `WitPrinter` in order to remove `Default` from the `Output` trait. `WitPrinter` is kept for backwards compatibility. --- crates/wit-component/src/printing.rs | 63 +++++++++++++++++----------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 6fa60a5bae..523c49c92b 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -9,30 +9,19 @@ use wit_parser::*; const PRINT_F32_F64_DEFAULT: bool = true; /// A utility for printing WebAssembly interface definitions to a string. -pub struct WitPrinter { - /// Visitor that holds the WIT document being printed. - pub output: O, - - // Count of how many items in this current block have been printed to print - // a blank line between each item, but not the first item. - any_items: bool, - - // Whether to print doc comments. - emit_docs: bool, - - print_f32_f64: bool, +pub struct WitPrinter { + ext: WitPrinterExt, } impl Default for WitPrinter { fn default() -> Self { - Self::new() + Self { + ext: WitPrinterExt::new(OutputToString::default()), + } } } -impl WitPrinter -where - String: From, -{ +impl WitPrinter { /// Prints the specified `pkg` which is located in `resolve` to a string. /// /// The `nested` list of packages are other packages to include at the end @@ -43,15 +32,41 @@ where pkg: PackageId, nested: &[PackageId], ) -> Result { - self.print_all(resolve, pkg, nested).map(String::from) + let old_ext = mem::replace(&mut self.ext, WitPrinterExt::new(OutputToString::default())); + old_ext + .print_all(resolve, pkg, nested) + .map(|output| output.output) + } + + /// Configure whether doc comments will be printed. + /// + /// Defaults to true. + pub fn emit_docs(&mut self, enabled: bool) -> &mut Self { + self.ext.emit_docs = enabled; + self } } -impl WitPrinter { +/// An extensible utility for printing WebAssembly interface definitions to a parametrized `Output`. +pub struct WitPrinterExt { + /// Visitor that holds the WIT document being printed. + pub output: O, + + // Count of how many items in this current block have been printed to print + // a blank line between each item, but not the first item. + any_items: bool, + + // Whether to print doc comments. + emit_docs: bool, + + print_f32_f64: bool, +} + +impl WitPrinterExt { /// Craete new instance. - pub fn new() -> Self { + pub fn new(output: O) -> Self { Self { - output: O::default(), + output, any_items: false, emit_docs: true, print_f32_f64: match std::env::var("WIT_REQUIRE_F32_F64") { @@ -66,7 +81,7 @@ impl WitPrinter { /// The `nested` list of packages are other packages to include at the end /// of the output in `package ... { ... }` syntax. pub fn print_all( - &mut self, + mut self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId], @@ -80,7 +95,7 @@ impl WitPrinter { self.print_package(resolve, *pkg_id, false)?; } - Ok(std::mem::take(&mut self.output)) + Ok(self.output) } /// Configure whether doc comments will be printed. @@ -1101,7 +1116,7 @@ fn is_keyword(name: &str) -> bool { } /// A visitor that receives tokens emitted by `WitPrinter`. -pub trait Output: Default { +pub trait Output { /// A newline is added. fn newline(&mut self); /// A keyword is added. Keywords are hardcoded strings from `[a-z]`, but can start with `@` From 90e333dd5d72f9c671f35ce0206df0cf872b7dd8 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 18 Dec 2024 11:46:55 +0100 Subject: [PATCH 14/18] Revert "[wit-component] Introduce `WitPrinterExt`" This reverts commit c23ad57612915dbf61f9966532da77e5e142dd56. --- crates/wit-component/src/printing.rs | 63 +++++++++++----------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 523c49c92b..6fa60a5bae 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -9,19 +9,30 @@ use wit_parser::*; const PRINT_F32_F64_DEFAULT: bool = true; /// A utility for printing WebAssembly interface definitions to a string. -pub struct WitPrinter { - ext: WitPrinterExt, +pub struct WitPrinter { + /// Visitor that holds the WIT document being printed. + pub output: O, + + // Count of how many items in this current block have been printed to print + // a blank line between each item, but not the first item. + any_items: bool, + + // Whether to print doc comments. + emit_docs: bool, + + print_f32_f64: bool, } impl Default for WitPrinter { fn default() -> Self { - Self { - ext: WitPrinterExt::new(OutputToString::default()), - } + Self::new() } } -impl WitPrinter { +impl WitPrinter +where + String: From, +{ /// Prints the specified `pkg` which is located in `resolve` to a string. /// /// The `nested` list of packages are other packages to include at the end @@ -32,41 +43,15 @@ impl WitPrinter { pkg: PackageId, nested: &[PackageId], ) -> Result { - let old_ext = mem::replace(&mut self.ext, WitPrinterExt::new(OutputToString::default())); - old_ext - .print_all(resolve, pkg, nested) - .map(|output| output.output) - } - - /// Configure whether doc comments will be printed. - /// - /// Defaults to true. - pub fn emit_docs(&mut self, enabled: bool) -> &mut Self { - self.ext.emit_docs = enabled; - self + self.print_all(resolve, pkg, nested).map(String::from) } } -/// An extensible utility for printing WebAssembly interface definitions to a parametrized `Output`. -pub struct WitPrinterExt { - /// Visitor that holds the WIT document being printed. - pub output: O, - - // Count of how many items in this current block have been printed to print - // a blank line between each item, but not the first item. - any_items: bool, - - // Whether to print doc comments. - emit_docs: bool, - - print_f32_f64: bool, -} - -impl WitPrinterExt { +impl WitPrinter { /// Craete new instance. - pub fn new(output: O) -> Self { + pub fn new() -> Self { Self { - output, + output: O::default(), any_items: false, emit_docs: true, print_f32_f64: match std::env::var("WIT_REQUIRE_F32_F64") { @@ -81,7 +66,7 @@ impl WitPrinterExt { /// The `nested` list of packages are other packages to include at the end /// of the output in `package ... { ... }` syntax. pub fn print_all( - mut self, + &mut self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId], @@ -95,7 +80,7 @@ impl WitPrinterExt { self.print_package(resolve, *pkg_id, false)?; } - Ok(self.output) + Ok(std::mem::take(&mut self.output)) } /// Configure whether doc comments will be printed. @@ -1116,7 +1101,7 @@ fn is_keyword(name: &str) -> bool { } /// A visitor that receives tokens emitted by `WitPrinter`. -pub trait Output { +pub trait Output: Default { /// A newline is added. fn newline(&mut self); /// A keyword is added. Keywords are hardcoded strings from `[a-z]`, but can start with `@` From aa5024ede64aac2517f52c1f26edeb4fc3122422 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 18 Dec 2024 12:17:59 +0100 Subject: [PATCH 15/18] [wit-component] drop `Default` from `Output`, take `self` in `print` and `print_all` --- crates/wit-component/src/printing.rs | 21 ++++++++------------- src/bin/wasm-tools/component.rs | 11 +++++++---- src/lib.rs | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 6fa60a5bae..076425aba0 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -25,7 +25,7 @@ pub struct WitPrinter { impl Default for WitPrinter { fn default() -> Self { - Self::new() + Self::new(OutputToString::default()) } } @@ -33,25 +33,20 @@ impl WitPrinter where String: From, { - /// Prints the specified `pkg` which is located in `resolve` to a string. + /// Convenience wrapper around [`print_all`](WitPrinter::print_all) that prints the specified `pkg` which is located in `resolve` to a string. /// /// The `nested` list of packages are other packages to include at the end /// of the output in `package ... { ... }` syntax. - pub fn print( - &mut self, - resolve: &Resolve, - pkg: PackageId, - nested: &[PackageId], - ) -> Result { + pub fn print(self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId]) -> Result { self.print_all(resolve, pkg, nested).map(String::from) } } impl WitPrinter { /// Craete new instance. - pub fn new() -> Self { + pub fn new(output: O) -> Self { Self { - output: O::default(), + output, any_items: false, emit_docs: true, print_f32_f64: match std::env::var("WIT_REQUIRE_F32_F64") { @@ -66,7 +61,7 @@ impl WitPrinter { /// The `nested` list of packages are other packages to include at the end /// of the output in `package ... { ... }` syntax. pub fn print_all( - &mut self, + mut self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId], @@ -80,7 +75,7 @@ impl WitPrinter { self.print_package(resolve, *pkg_id, false)?; } - Ok(std::mem::take(&mut self.output)) + Ok(self.output) } /// Configure whether doc comments will be printed. @@ -1101,7 +1096,7 @@ fn is_keyword(name: &str) -> bool { } /// A visitor that receives tokens emitted by `WitPrinter`. -pub trait Output: Default { +pub trait Output { /// A newline is added. fn newline(&mut self); /// A keyword is added. Keywords are hardcoded strings from `[a-z]`, but can start with `@` diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 9a1b4cd711..906d10013e 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -801,8 +801,11 @@ impl WitOpts { let resolve = decoded.resolve(); - let mut printer = WitPrinter::default(); - printer.emit_docs(!self.no_docs); + let configure_printer = || { + let mut wit_printer = WitPrinter::default(); + wit_printer.emit_docs(!self.no_docs); + wit_printer + }; match &self.out_dir { Some(dir) => { @@ -825,7 +828,7 @@ impl WitOpts { let main = decoded.package(); for (id, pkg) in resolve.packages.iter() { let is_main = id == main; - let output = printer.print(resolve, id, &[])?; + let output = configure_printer().print(resolve, id, &[])?; let out_dir = if is_main { dir.clone() } else { @@ -864,7 +867,7 @@ impl WitOpts { &self.general, Output::Wit { wit: &decoded, - printer, + printer: configure_printer(), }, )?; } diff --git a/src/lib.rs b/src/lib.rs index e4caf81c0a..473591e2e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -236,7 +236,7 @@ impl OutputArg { } Output::Json(s) => self.output_str(s), #[cfg(feature = "component")] - Output::Wit { wit, mut printer } => { + Output::Wit { wit, printer } => { let resolve = wit.resolve(); let ids = resolve .packages From 41e9a4ad24b515abbc10681d0094c0bc93a5e5d0 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 18 Dec 2024 12:23:04 +0100 Subject: [PATCH 16/18] [wit-component]` rename `Output::type` to `ty` --- crates/wit-component/src/printing.rs | 58 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 076425aba0..1d85149ac8 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -308,7 +308,7 @@ impl WitPrinter { fn print_resource(&mut self, resolve: &Resolve, id: TypeId, funcs: &[&Function]) -> Result<()> { let ty = &resolve.types[id]; - self.output.r#type("resource", TypeKind::BuiltIn); + self.output.ty("resource", TypeKind::BuiltIn); self.output.str(" "); self.print_name_type( ty.name.as_ref().expect("resources must be named"), @@ -524,31 +524,31 @@ impl WitPrinter { /// Print the name of type `ty`. pub fn print_type_name(&mut self, resolve: &Resolve, ty: &Type) -> Result<()> { match ty { - Type::Bool => self.output.r#type("bool", TypeKind::BuiltIn), - Type::U8 => self.output.r#type("u8", TypeKind::BuiltIn), - Type::U16 => self.output.r#type("u16", TypeKind::BuiltIn), - Type::U32 => self.output.r#type("u32", TypeKind::BuiltIn), - Type::U64 => self.output.r#type("u64", TypeKind::BuiltIn), - Type::S8 => self.output.r#type("s8", TypeKind::BuiltIn), - Type::S16 => self.output.r#type("s16", TypeKind::BuiltIn), - Type::S32 => self.output.r#type("s32", TypeKind::BuiltIn), - Type::S64 => self.output.r#type("s64", TypeKind::BuiltIn), + Type::Bool => self.output.ty("bool", TypeKind::BuiltIn), + Type::U8 => self.output.ty("u8", TypeKind::BuiltIn), + Type::U16 => self.output.ty("u16", TypeKind::BuiltIn), + Type::U32 => self.output.ty("u32", TypeKind::BuiltIn), + Type::U64 => self.output.ty("u64", TypeKind::BuiltIn), + Type::S8 => self.output.ty("s8", TypeKind::BuiltIn), + Type::S16 => self.output.ty("s16", TypeKind::BuiltIn), + Type::S32 => self.output.ty("s32", TypeKind::BuiltIn), + Type::S64 => self.output.ty("s64", TypeKind::BuiltIn), Type::F32 => { if self.print_f32_f64 { - self.output.r#type("f32", TypeKind::BuiltIn) + self.output.ty("f32", TypeKind::BuiltIn) } else { - self.output.r#type("f32", TypeKind::BuiltIn) + self.output.ty("f32", TypeKind::BuiltIn) } } Type::F64 => { if self.print_f32_f64 { - self.output.r#type("f64", TypeKind::BuiltIn) + self.output.ty("f64", TypeKind::BuiltIn) } else { - self.output.r#type("f64", TypeKind::BuiltIn) + self.output.ty("f64", TypeKind::BuiltIn) } } - Type::Char => self.output.r#type("char", TypeKind::BuiltIn), - Type::String => self.output.r#type("string", TypeKind::BuiltIn), + Type::Char => self.output.ty("char", TypeKind::BuiltIn), + Type::String => self.output.ty("string", TypeKind::BuiltIn), Type::Id(id) => { let ty = &resolve.types[*id]; @@ -586,7 +586,7 @@ impl WitPrinter { bail!("resolve has unnamed variant type") } TypeDefKind::List(ty) => { - self.output.r#type("list", TypeKind::BuiltIn); + self.output.ty("list", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, ty)?; self.output.generic_args_end(); @@ -616,7 +616,7 @@ impl WitPrinter { Handle::Own(ty) => { let ty = &resolve.types[*ty]; if force_handle_type_printed { - self.output.r#type("own", TypeKind::BuiltIn); + self.output.ty("own", TypeKind::BuiltIn); self.output.generic_args_start(); } self.print_name_type( @@ -631,7 +631,7 @@ impl WitPrinter { } Handle::Borrow(ty) => { - self.output.r#type("borrow", TypeKind::BuiltIn); + self.output.ty("borrow", TypeKind::BuiltIn); self.output.generic_args_start(); let ty = &resolve.types[*ty]; self.print_name_type( @@ -648,7 +648,7 @@ impl WitPrinter { } fn print_tuple_type(&mut self, resolve: &Resolve, tuple: &Tuple) -> Result<()> { - self.output.r#type("tuple", TypeKind::BuiltIn); + self.output.ty("tuple", TypeKind::BuiltIn); self.output.generic_args_start(); for (i, ty) in tuple.types.iter().enumerate() { if i > 0 { @@ -662,7 +662,7 @@ impl WitPrinter { } fn print_option_type(&mut self, resolve: &Resolve, payload: &Type) -> Result<()> { - self.output.r#type("option", TypeKind::BuiltIn); + self.output.ty("option", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, payload)?; self.output.generic_args_end(); @@ -675,7 +675,7 @@ impl WitPrinter { ok: Some(ok), err: Some(err), } => { - self.output.r#type("result", TypeKind::BuiltIn); + self.output.ty("result", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, ok)?; self.output.str(", "); @@ -686,7 +686,7 @@ impl WitPrinter { ok: None, err: Some(err), } => { - self.output.r#type("result", TypeKind::BuiltIn); + self.output.ty("result", TypeKind::BuiltIn); self.output.generic_args_start(); self.output.str("_, "); self.print_type_name(resolve, err)?; @@ -696,7 +696,7 @@ impl WitPrinter { ok: Some(ok), err: None, } => { - self.output.r#type("result", TypeKind::BuiltIn); + self.output.ty("result", TypeKind::BuiltIn); self.output.generic_args_start(); self.print_type_name(resolve, ok)?; self.output.generic_args_end(); @@ -705,7 +705,7 @@ impl WitPrinter { ok: None, err: None, } => { - self.output.r#type("result", TypeKind::BuiltIn); + self.output.ty("result", TypeKind::BuiltIn); } } Ok(()) @@ -950,7 +950,7 @@ impl WitPrinter { self.output.str(" "); self.print_name_type(name, TypeKind::List); self.output.str(" = "); - self.output.r#type("list", TypeKind::BuiltIn); + self.output.ty("list", TypeKind::BuiltIn); self.output.str("<"); self.print_type_name(resolve, ty)?; self.output.str(">"); @@ -970,7 +970,7 @@ impl WitPrinter { } fn print_name_type(&mut self, name: &str, kind: TypeKind) { - self.output.r#type(Self::escape_name(name).deref(), kind); + self.output.ty(Self::escape_name(name).deref(), kind); } fn print_name_param(&mut self, name: &str) { @@ -1103,7 +1103,7 @@ pub trait Output { /// when printing a [Feature Gate](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#feature-gates) fn keyword(&mut self, src: &str); /// A type is added. - fn r#type(&mut self, src: &str, kind: TypeKind); + fn ty(&mut self, src: &str, kind: TypeKind); /// A parameter name of a function, record or a named return is added. fn param(&mut self, src: &str); /// A case belonging to a variant, enum or flags is added. @@ -1236,7 +1236,7 @@ impl Output for OutputToString { self.indent_and_print(src); } - fn r#type(&mut self, src: &str, _kind: TypeKind) { + fn ty(&mut self, src: &str, _kind: TypeKind) { self.indent_and_print(src); } From 69b8715b72bd14f12d5acf9f2348575ffd66605f Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 18 Dec 2024 13:57:19 +0100 Subject: [PATCH 17/18] [wit-component] extract most of `OutputToString` fns to the `Output` trait All methods that do not have to directly manipulate the state have now a default implementation. --- crates/wit-component/src/printing.rs | 195 +++++++++++++++------------ 1 file changed, 106 insertions(+), 89 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 1d85149ac8..e975b3bb3d 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1095,36 +1095,116 @@ fn is_keyword(name: &str) -> bool { ) } -/// A visitor that receives tokens emitted by `WitPrinter`. +/// Trait defining visitor methods driven by [`WitPrinter`](WitPrinter). +/// +/// Some methods in this trait have default implementations. These default +/// implementations may rely on helper functions that are not +/// invoked directly by `WitPrinter`. pub trait Output { + /// Push a string slice into a buffer or an output. + /// + /// Parameter `src` can contain punctation characters, and must be escaped + /// when outputing to languages like HTML. + /// Helper function used exclusively by the default implementations of trait methods. + /// This function is not called directly by `WitPrinter`. + /// When overriding all the trait methods, users do not need to handle this function. + fn push_str(&mut self, src: &str); + + /// Set the appropriate indentation. + /// + /// Helper function used exclusively by the default implementations of trait methods. + /// This function is not called directly by `WitPrinter`. + /// When overriding all the trait methods, users do not need to handle this function. + fn indent_if_needed(&mut self) -> bool; + + /// Start of indentation. In WIT this represents ` {\n`. + fn indent_start(&mut self); + + /// End of indentation. In WIT this represents `}\n`. + fn indent_end(&mut self); + + /// This method is designed to be used only by the default methods of this trait. + /// Called only from the default implementation functions of this trait. + fn indent_and_print(&mut self, src: &str) { + assert!(!src.contains('\n')); + let idented = self.indent_if_needed(); + if idented && src.starts_with(' ') { + panic!("cannot add a space at the begining of a line"); + } + self.push_str(src); + } + /// A newline is added. fn newline(&mut self); + /// A keyword is added. Keywords are hardcoded strings from `[a-z]`, but can start with `@` /// when printing a [Feature Gate](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#feature-gates) - fn keyword(&mut self, src: &str); + fn keyword(&mut self, src: &str) { + self.indent_and_print(src); + } + /// A type is added. - fn ty(&mut self, src: &str, kind: TypeKind); + fn ty(&mut self, src: &str, _kind: TypeKind) { + self.indent_and_print(src); + } + /// A parameter name of a function, record or a named return is added. - fn param(&mut self, src: &str); + fn param(&mut self, src: &str) { + self.indent_and_print(src); + } + /// A case belonging to a variant, enum or flags is added. - fn case(&mut self, src: &str); + fn case(&mut self, src: &str) { + self.indent_and_print(src); + } + /// Generic argument section starts. In WIT this represents the `<` character. - fn generic_args_start(&mut self); + fn generic_args_start(&mut self) { + assert!( + !self.indent_if_needed(), + "`generic_args_start` is never called after newline" + ); + self.push_str("<"); + } + /// Generic argument section ends. In WIT this represents the '>' character. - fn generic_args_end(&mut self); + fn generic_args_end(&mut self) { + assert!( + !self.indent_if_needed(), + "`generic_args_end` is never called after newline" + ); + self.push_str(">"); + } + /// Called when a single documentation line is added. /// The `doc` parameter starts with `///` omitted, and can be an empty string. - fn doc(&mut self, doc: &str); + fn doc(&mut self, doc: &str) { + assert!(!doc.contains('\n')); + self.indent_if_needed(); + self.push_str("///"); + if !doc.is_empty() { + self.push_str(" "); + self.push_str(doc); + } + self.newline(); + } + /// A semicolon is added. - fn semicolon(&mut self); - /// Start of indentation. In WIT this represents ` {\n`. - fn indent_start(&mut self); - /// End of indentation. In WIT this represents `}\n`. - fn indent_end(&mut self); + fn semicolon(&mut self) { + assert!( + !self.indent_if_needed(), + "`semicolon` is never called after newline" + ); + self.push_str(";"); + self.newline(); + } + /// Any string that does not have a specialized function is added. /// Parameter `src` can contain punctation characters, and must be escaped /// when outputing to languages like HTML. - fn str(&mut self, src: &str); + fn str(&mut self, src: &str) { + self.indent_and_print(src); + } } /// Represents the different kinds of types that can be encountered while @@ -1202,88 +1282,24 @@ pub struct OutputToString { needs_indent: bool, } -impl OutputToString { - fn indent_if_needed(&mut self) { +impl Output for OutputToString { + fn push_str(&mut self, src: &str) { + self.output.push_str(src); + } + + fn indent_if_needed(&mut self) -> bool { if self.needs_indent { for _ in 0..self.indent { // Indenting by two spaces. self.output.push_str(" "); } self.needs_indent = false; + true + } else { + false } } - fn indent_and_print(&mut self, src: &str) { - assert!(!src.contains('\n')); - if src.starts_with(' ') { - assert!( - !self.needs_indent, - "cannot add a space at the begining of a line" - ); - } - self.indent_if_needed(); - self.output.push_str(src); - } -} - -impl Output for OutputToString { - fn newline(&mut self) { - self.output.push('\n'); - self.needs_indent = true; - } - - fn keyword(&mut self, src: &str) { - self.indent_and_print(src); - } - - fn ty(&mut self, src: &str, _kind: TypeKind) { - self.indent_and_print(src); - } - - fn param(&mut self, src: &str) { - self.indent_and_print(src); - } - - fn case(&mut self, src: &str) { - self.indent_and_print(src); - } - - fn generic_args_start(&mut self) { - assert!( - !self.needs_indent, - "`subtype_start` is never called after newline" - ); - self.output.push('<'); - } - - fn generic_args_end(&mut self) { - assert!( - !self.needs_indent, - "`subtype_end` is never called after newline" - ); - self.output.push('>'); - } - - fn doc(&mut self, doc: &str) { - assert!(!doc.contains('\n')); - self.indent_if_needed(); - self.output.push_str("///"); - if !doc.is_empty() { - self.output.push(' '); - self.output.push_str(doc); - } - self.newline(); - } - - fn semicolon(&mut self) { - assert!( - !self.needs_indent, - "`semicolon` is never called after newline" - ); - self.output.push(';'); - self.newline(); - } - fn indent_start(&mut self) { assert!( !self.needs_indent, @@ -1305,8 +1321,9 @@ impl Output for OutputToString { self.newline(); } - fn str(&mut self, src: &str) { - self.indent_and_print(src); + fn newline(&mut self) { + self.output.push('\n'); + self.needs_indent = true; } } From 4fc165602dde1fa00a69dc4fbfc7727015858490 Mon Sep 17 00:00:00 2001 From: Tomas Olvecky Date: Wed, 18 Dec 2024 21:33:46 +0100 Subject: [PATCH 18/18] [wit-component] make `WitPrinter::print` return `Result<()>` As per PR request, merge `print` with `print_all`. --- crates/wit-component/src/printing.rs | 30 +++++++----------------- crates/wit-component/tests/components.rs | 4 +++- crates/wit-component/tests/interfaces.rs | 4 +++- crates/wit-component/tests/merge.rs | 4 +++- fuzz/src/roundtrip_wit.rs | 6 ++--- src/bin/wasm-tools/component.rs | 4 +++- src/lib.rs | 5 ++-- 7 files changed, 27 insertions(+), 30 deletions(-) diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index e975b3bb3d..8fb85d3e1f 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, bail, Result}; use std::borrow::Cow; use std::collections::HashMap; +use std::fmt::Display; use std::mem; use std::ops::Deref; use wit_parser::*; @@ -29,19 +30,6 @@ impl Default for WitPrinter { } } -impl WitPrinter -where - String: From, -{ - /// Convenience wrapper around [`print_all`](WitPrinter::print_all) that prints the specified `pkg` which is located in `resolve` to a string. - /// - /// The `nested` list of packages are other packages to include at the end - /// of the output in `package ... { ... }` syntax. - pub fn print(self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId]) -> Result { - self.print_all(resolve, pkg, nested).map(String::from) - } -} - impl WitPrinter { /// Craete new instance. pub fn new(output: O) -> Self { @@ -60,12 +48,7 @@ impl WitPrinter { /// /// The `nested` list of packages are other packages to include at the end /// of the output in `package ... { ... }` syntax. - pub fn print_all( - mut self, - resolve: &Resolve, - pkg: PackageId, - nested: &[PackageId], - ) -> Result { + pub fn print(&mut self, resolve: &Resolve, pkg: PackageId, nested: &[PackageId]) -> Result<()> { self.print_package(resolve, pkg, true)?; for (i, pkg_id) in nested.iter().enumerate() { if i > 0 { @@ -74,8 +57,7 @@ impl WitPrinter { } self.print_package(resolve, *pkg_id, false)?; } - - Ok(self.output) + Ok(()) } /// Configure whether doc comments will be printed. @@ -1332,3 +1314,9 @@ impl From for String { output.output } } + +impl Display for OutputToString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.output.fmt(f) + } +} diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 4a335bbbff..84fc1ff693 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -159,9 +159,11 @@ fn run_test(path: &Path) -> Result<()> { DecodedWasm::WitPackage(..) => unreachable!(), DecodedWasm::Component(resolve, world) => (resolve.worlds[world].package.unwrap(), resolve), }; - let wit = WitPrinter::default() + let mut printer = WitPrinter::default(); + printer .print(&resolve, pkg, &[]) .context("failed to print WIT")?; + let wit = printer.output.to_string(); assert_output(&wit, &component_wit_path)?; UnresolvedPackageGroup::parse(&component_wit_path, &wit) diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 6e0f70d767..2345986557 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -86,7 +86,9 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> { } fn assert_print(resolve: &Resolve, pkg_id: PackageId, path: &Path, is_dir: bool) -> Result<()> { - let output = WitPrinter::default().print(resolve, pkg_id, &[])?; + let mut printer = WitPrinter::default(); + printer.print(resolve, pkg_id, &[])?; + let output = printer.output.to_string(); let pkg = &resolve.packages[pkg_id]; let expected = if is_dir { path.join(format!("{}.wit.print", &pkg.name.name)) diff --git a/crates/wit-component/tests/merge.rs b/crates/wit-component/tests/merge.rs index f7f72f2f65..e1cca07c56 100644 --- a/crates/wit-component/tests/merge.rs +++ b/crates/wit-component/tests/merge.rs @@ -46,7 +46,9 @@ fn merging() -> Result<()> { .join("merge") .join(&pkg.name.name) .with_extension("wit"); - let output = WitPrinter::default().print(&into, id, &[])?; + let mut printer = WitPrinter::default(); + printer.print(&into, id, &[])?; + let output = printer.output.to_string(); assert_output(&expected, &output)?; } } diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index f94d52dd2c..79dbc6e2d9 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -110,9 +110,9 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, pkg: PackageId, was .map(|p| p.0) .filter(|k| *k != pkg) .collect::>(); - let doc = WitPrinter::default() - .print(resolve, pkg, &package_deps) - .unwrap(); + let mut printer = WitPrinter::default(); + printer.print(resolve, pkg, &package_deps).unwrap(); + let doc = printer.output.to_string(); let new_pkg = new_resolve .push_str(&format!("printed-{file}.wit"), &doc) .unwrap(); diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 906d10013e..f62731b5fc 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -828,7 +828,9 @@ impl WitOpts { let main = decoded.package(); for (id, pkg) in resolve.packages.iter() { let is_main = id == main; - let output = configure_printer().print(resolve, id, &[])?; + let mut printer = configure_printer(); + printer.print(resolve, id, &[])?; + let output = printer.output.to_string(); let out_dir = if is_main { dir.clone() } else { diff --git a/src/lib.rs b/src/lib.rs index 473591e2e9..c5e33c0f9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -236,7 +236,7 @@ impl OutputArg { } Output::Json(s) => self.output_str(s), #[cfg(feature = "component")] - Output::Wit { wit, printer } => { + Output::Wit { wit, mut printer } => { let resolve = wit.resolve(); let ids = resolve .packages @@ -244,7 +244,8 @@ impl OutputArg { .map(|(id, _)| id) .filter(|id| *id != wit.package()) .collect::>(); - let output = printer.print(resolve, wit.package(), &ids)?; + printer.print(resolve, wit.package(), &ids)?; + let output = printer.output.to_string(); self.output_str(&output) } }