From 268f3f0ff5d80544ca21d565354eae6d3e29fb91 Mon Sep 17 00:00:00 2001 From: Corey Richardson Date: Thu, 15 Aug 2013 16:28:54 -0400 Subject: [PATCH] Add rustdoc_ng --- Makefile.in | 5 + mk/clean.mk | 4 + mk/install.mk | 2 + mk/tools.mk | 13 + src/rustdoc_ng/.gitignore | 2 + src/rustdoc_ng/clean.rs | 1081 +++++++++++++++++++++++++++++++++++ src/rustdoc_ng/core.rs | 68 +++ src/rustdoc_ng/doctree.rs | 158 +++++ src/rustdoc_ng/fold.rs | 101 ++++ src/rustdoc_ng/lib.rs | 27 + src/rustdoc_ng/main.rs | 94 +++ src/rustdoc_ng/passes.rs | 193 +++++++ src/rustdoc_ng/plugins.rs | 80 +++ src/rustdoc_ng/visit_ast.rs | 173 ++++++ 14 files changed, 2001 insertions(+) create mode 100644 src/rustdoc_ng/.gitignore create mode 100644 src/rustdoc_ng/clean.rs create mode 100644 src/rustdoc_ng/core.rs create mode 100644 src/rustdoc_ng/doctree.rs create mode 100644 src/rustdoc_ng/fold.rs create mode 100644 src/rustdoc_ng/lib.rs create mode 100644 src/rustdoc_ng/main.rs create mode 100644 src/rustdoc_ng/passes.rs create mode 100644 src/rustdoc_ng/plugins.rs create mode 100644 src/rustdoc_ng/visit_ast.rs diff --git a/Makefile.in b/Makefile.in index ecccddb0cfa..171ce8f1925 100644 --- a/Makefile.in +++ b/Makefile.in @@ -214,6 +214,7 @@ CFG_LIBRUSTC_$(1) :=$(call CFG_LIB_NAME_$(1),rustc) CFG_LIBSYNTAX_$(1) :=$(call CFG_LIB_NAME_$(1),syntax) CFG_LIBRUSTPKG_$(1) :=$(call CFG_LIB_NAME_$(1),rustpkg) CFG_LIBRUSTDOC_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc) +CFG_LIBRUSTDOCNG_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc_ng) CFG_LIBRUSTI_$(1) :=$(call CFG_LIB_NAME_$(1),rusti) CFG_LIBRUST_$(1) :=$(call CFG_LIB_NAME_$(1),rust) @@ -223,6 +224,7 @@ LIBRUSTC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustc) LIBSYNTAX_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),syntax) LIBRUSTPKG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustpkg) LIBRUSTDOC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc) +LIBRUSTDOCNG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc_ng) LIBRUSTI_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rusti) LIBRUST_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rust) EXTRALIB_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),extra) @@ -231,6 +233,7 @@ LIBRUSTC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustc) LIBSYNTAX_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),syntax) LIBRUSTPKG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustpkg) LIBRUSTDOC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc) +LIBRUSTDOCNG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc_ng) LIBRUSTI_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rusti) LIBRUST_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rust) @@ -443,6 +446,7 @@ CSREQ$(1)_T_$(2)_H_$(3) = \ $$(HBIN$(1)_H_$(3))/rust$$(X_$(3)) \ $$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTPKG_$(3)) \ $$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTDOC_$(3)) \ + $$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(3)) \ $$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTI_$(3)) \ $$(HLIB$(1)_H_$(3))/$(CFG_LIBRUST_$(3)) \ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) \ @@ -451,6 +455,7 @@ CSREQ$(1)_T_$(2)_H_$(3) = \ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(2)) \ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2)) \ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2)) \ + $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(2)) \ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTI_$(2)) \ $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUST_$(2)) diff --git a/mk/clean.mk b/mk/clean.mk index cb8861f6597..5e8d9835db2 100644 --- a/mk/clean.mk +++ b/mk/clean.mk @@ -72,6 +72,7 @@ clean$(1)_H_$(2): $(Q)rm -f $$(HBIN$(1)_H_$(2))/rust$(X_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTPKG_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTDOC_$(2)) + $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTDOCNG_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_RUNTIME_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_STDLIB_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_EXTRALIB_$(2)) @@ -85,6 +86,7 @@ clean$(1)_H_$(2): $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBSYNTAX_GLOB_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTPKG_GLOB_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTDOC_GLOB_$(2)) + $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTDOCNG_GLOB_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTI_GLOB_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUST_GLOB_$(2)) $(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_RUSTLLVM_$(2)) @@ -107,6 +109,7 @@ clean$(1)_T_$(2)_H_$(3): $(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rust$(X_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2)) + $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_RUNTIME_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_EXTRALIB_$(2)) @@ -120,6 +123,7 @@ clean$(1)_T_$(2)_H_$(3): $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBSYNTAX_GLOB_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTPKG_GLOB_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTDOC_GLOB_$(2)) + $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTDOCNG_GLOB_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTI_GLOB_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUST_GLOB_$(2)) $(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_RUSTLLVM_$(2)) diff --git a/mk/install.mk b/mk/install.mk index 4b50c5aa796..bc4633b8225 100644 --- a/mk/install.mk +++ b/mk/install.mk @@ -104,6 +104,7 @@ install-target-$(1)-host-$(2): $$(CSREQ$$(ISTAGE)_T_$(1)_H_$(2)) $$(Q)$$(call INSTALL_LIB,$$(LIBSYNTAX_GLOB_$(1))) $$(Q)$$(call INSTALL_LIB,$$(LIBRUSTPKG_GLOB_$(1))) $$(Q)$$(call INSTALL_LIB,$$(LIBRUSTDOC_GLOB_$(1))) + $$(Q)$$(call INSTALL_LIB,$$(LIBRUSTDOCNG_GLOB_$(1))) $$(Q)$$(call INSTALL_LIB,$$(LIBRUSTI_GLOB_$(1))) $$(Q)$$(call INSTALL_LIB,$$(LIBRUST_GLOB_$(1))) $$(Q)$$(call INSTALL_LIB,libmorestack.a) @@ -149,6 +150,7 @@ install-host: $(CSREQ$(ISTAGE)_T_$(CFG_BUILD_TRIPLE)_H_$(CFG_BUILD_TRIPLE)) $(Q)$(call INSTALL_LIB,$(LIBRUST_GLOB_$(CFG_BUILD_TRIPLE))) $(Q)$(call INSTALL_LIB,$(LIBRUSTPKG_GLOB_$(CFG_BUILD_TRIPLE))) $(Q)$(call INSTALL_LIB,$(LIBRUSTDOC_GLOB_$(CFG_BUILD_TRIPLE))) + $(Q)$(call INSTALL_LIB,$(LIBRUSTDOCNG_GLOB_$(CFG_BUILD_TRIPLE))) $(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUNTIME_$(CFG_BUILD_TRIPLE))) $(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUSTLLVM_$(CFG_BUILD_TRIPLE))) $(Q)$(call INSTALL,$(S)/man, $(PREFIX_ROOT)/share/man/man1,rust.1) diff --git a/mk/tools.mk b/mk/tools.mk index 09c3de01478..b5e413804ec 100644 --- a/mk/tools.mk +++ b/mk/tools.mk @@ -23,6 +23,11 @@ RUSTPKG_INPUTS := $(wildcard $(S)src/librustpkg/*.rs) RUSTDOC_LIB := $(S)src/librustdoc/rustdoc.rs RUSTDOC_INPUTS := $(wildcard $(S)src/librustdoc/*.rs) +# rustdoc_ng, the next generation documentation tool + +RUSTDOCNG_LIB := $(S)src/rustdoc/lib.rs +RUSTDOCNG_INPUTS := $(wildcard $(S)src/rustdoc/*.rs) + # Rusti, the JIT REPL RUSTI_LIB := $(S)src/librusti/rusti.rs RUSTI_INPUTS := $(wildcard $(S)src/librusti/*.rs) @@ -78,6 +83,14 @@ $$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc$$(X_$(4)): \ @$$(call E, compile_and_link: $$@) $$(STAGE$(1)_T_$(4)_H_$(3)) --cfg rustdoc -o $$@ $$< +$$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc_ng$$(X_$(4)): \ + $$(DRIVER_CRATE) \ + $$(TSREQ$(1)_T_$(4)_H_$(3)) \ + $$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(4)) \ + | $$(TBIN$(1)_T_$(4)_H_$(3))/ + @$$(call E, compile_and_link: $$@) + $$(STAGE$(1)_T_$(4)_H_$(3)) --cfg rustdoc_ng -o $$@ $$< + $$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTI_$(4)): \ $$(RUSTI_LIB) $$(RUSTI_INPUTS) \ $$(SREQ$(1)_T_$(4)_H_$(3)) \ diff --git a/src/rustdoc_ng/.gitignore b/src/rustdoc_ng/.gitignore new file mode 100644 index 00000000000..62f94a1a44e --- /dev/null +++ b/src/rustdoc_ng/.gitignore @@ -0,0 +1,2 @@ +*.swp +main diff --git a/src/rustdoc_ng/clean.rs b/src/rustdoc_ng/clean.rs new file mode 100644 index 00000000000..ed18fc04fad --- /dev/null +++ b/src/rustdoc_ng/clean.rs @@ -0,0 +1,1081 @@ +//! This module contains the "cleaned" pieces of the AST, and the functions +//! that clean them. + +use its = syntax::parse::token::ident_to_str; + +use rustc::metadata::{csearch,decoder,cstore}; +use syntax; +use syntax::ast; + +use std; +use doctree; +use visit_ast; +use std::local_data; + +pub trait Clean { + fn clean(&self) -> T; +} + +impl, U> Clean<~[U]> for ~[T] { + fn clean(&self) -> ~[U] { + self.iter().map(|x| x.clean()).collect() + } +} +impl, U> Clean for @T { + fn clean(&self) -> U { + (**self).clean() + } +} + +impl, U> Clean> for Option { + fn clean(&self) -> Option { + match self { + &None => None, + &Some(ref v) => Some(v.clean()) + } + } +} + +impl, U> Clean<~[U]> for syntax::opt_vec::OptVec { + fn clean(&self) -> ~[U] { + match self { + &syntax::opt_vec::Empty => ~[], + &syntax::opt_vec::Vec(ref v) => v.clean() + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Crate { + name: ~str, + module: Option, +} + +impl Clean for visit_ast::RustdocVisitor { + fn clean(&self) -> Crate { + use syntax::attr::{find_linkage_metas, last_meta_item_value_str_by_name}; + let maybe_meta = last_meta_item_value_str_by_name(find_linkage_metas(self.attrs), "name"); + + Crate { + name: match maybe_meta { + Some(x) => x.to_owned(), + None => fail!("rustdoc_ng requires a #[link(name=\"foo\")] crate attribute"), + }, + module: Some(self.module.clean()), + } + } +} + +/// Anything with a source location and set of attributes and, optionally, a +/// name. That is, anything that can be documented. This doesn't correspond +/// directly to the AST's concept of an item; it's a strict superset. +#[deriving(Clone, Encodable, Decodable)] +pub struct Item { + /// Stringified span + source: ~str, + /// Not everything has a name. E.g., impls + name: Option<~str>, + attrs: ~[Attribute], + inner: ItemEnum, + visibility: Option, + id: ast::NodeId, +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum ItemEnum { + StructItem(Struct), + EnumItem(Enum), + FunctionItem(Function), + ModuleItem(Module), + TypedefItem(Typedef), + StaticItem(Static), + TraitItem(Trait), + ImplItem(Impl), + ViewItemItem(ViewItem), + TyMethodItem(TyMethod), + MethodItem(Method), + StructFieldItem(StructField), + VariantItem(Variant), +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Module { + items: ~[Item], +} + +impl Clean for doctree::Module { + fn clean(&self) -> Item { + let name = if self.name.is_some() { + self.name.unwrap().clean() + } else { + ~"" + }; + Item { + name: Some(name), + attrs: self.attrs.clean(), + source: self.where.clean(), + visibility: self.vis.clean(), + id: self.id, + inner: ModuleItem(Module { + items: std::vec::concat(&[self.structs.clean(), + self.enums.clean(), self.fns.clean(), + self.mods.clean(), self.typedefs.clean(), + self.statics.clean(), self.traits.clean(), + self.impls.clean(), self.view_items.clean()]) + }) + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum Attribute { + Word(~str), + List(~str, ~[Attribute]), + NameValue(~str, ~str) +} + +impl Clean for ast::MetaItem { + fn clean(&self) -> Attribute { + match self.node { + ast::MetaWord(s) => Word(s.to_owned()), + ast::MetaList(ref s, ref l) => List(s.to_owned(), l.clean()), + ast::MetaNameValue(s, ref v) => NameValue(s.to_owned(), lit_to_str(v)) + } + } +} + +impl Clean for ast::Attribute { + fn clean(&self) -> Attribute { + self.node.value.clean() + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct TyParam { + name: ~str, + id: ast::NodeId, + bounds: ~[TyParamBound] +} + +impl Clean for ast::TyParam { + fn clean(&self) -> TyParam { + TyParam { + name: self.ident.clean(), + id: self.id, + bounds: self.bounds.clean(), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum TyParamBound { + RegionBound, + TraitBound(Type) +} + +impl Clean for ast::TyParamBound { + fn clean(&self) -> TyParamBound { + match *self { + ast::RegionTyParamBound => RegionBound, + ast::TraitTyParamBound(ref t) => TraitBound(t.clean()), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Lifetime(~str); + +impl Clean for ast::Lifetime { + fn clean(&self) -> Lifetime { + Lifetime(self.ident.clean()) + } +} + +// maybe use a Generic enum and use ~[Generic]? +#[deriving(Clone, Encodable, Decodable)] +pub struct Generics { + lifetimes: ~[Lifetime], + type_params: ~[TyParam] +} + +impl Generics { + fn new() -> Generics { + Generics { + lifetimes: ~[], + type_params: ~[] + } + } +} + +impl Clean for ast::Generics { + fn clean(&self) -> Generics { + Generics { + lifetimes: self.lifetimes.clean(), + type_params: self.ty_params.clean(), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Method { + generics: Generics, + self_: SelfTy, + purity: ast::purity, + decl: FnDecl, +} + +impl Clean for ast::method { + fn clean(&self) -> Item { + Item { + name: Some(self.ident.clean()), + attrs: self.attrs.clean(), + source: self.span.clean(), + id: self.self_id.clone(), + visibility: None, + inner: MethodItem(Method { + generics: self.generics.clean(), + self_: self.explicit_self.clean(), + purity: self.purity.clone(), + decl: self.decl.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct TyMethod { + purity: ast::purity, + decl: FnDecl, + generics: Generics, + self_: SelfTy, +} + +impl Clean for ast::TypeMethod { + fn clean(&self) -> Item { + Item { + name: Some(self.ident.clean()), + attrs: self.attrs.clean(), + source: self.span.clean(), + id: self.id, + visibility: None, + inner: TyMethodItem(TyMethod { + purity: self.purity.clone(), + decl: self.decl.clean(), + self_: self.explicit_self.clean(), + generics: self.generics.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum SelfTy { + SelfStatic, + SelfValue, + SelfBorrowed(Option, Mutability), + SelfManaged(Mutability), + SelfOwned, +} + +impl Clean for ast::explicit_self { + fn clean(&self) -> SelfTy { + match self.node { + ast::sty_static => SelfStatic, + ast::sty_value => SelfValue, + ast::sty_uniq => SelfOwned, + ast::sty_region(lt, mt) => SelfBorrowed(lt.clean(), mt.clean()), + ast::sty_box(mt) => SelfManaged(mt.clean()), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Function { + decl: FnDecl, + generics: Generics, +} + +impl Clean for doctree::Function { + fn clean(&self) -> Item { + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + visibility: self.vis.clean(), + id: self.id, + inner: FunctionItem(Function { + decl: self.decl.clean(), + generics: self.generics.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct ClosureDecl { + sigil: ast::Sigil, + region: Option, + lifetimes: ~[Lifetime], + decl: FnDecl, + onceness: ast::Onceness, + purity: ast::purity, + bounds: ~[TyParamBound] +} + +impl Clean for ast::TyClosure { + fn clean(&self) -> ClosureDecl { + ClosureDecl { + sigil: self.sigil, + region: self.region.clean(), + lifetimes: self.lifetimes.clean(), + decl: self.decl.clean(), + onceness: self.onceness, + purity: self.purity, + bounds: match self.bounds { + Some(ref x) => x.clean(), + None => ~[] + }, + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct FnDecl { + inputs: ~[Argument], + output: Type, + cf: RetStyle, + attrs: ~[Attribute] +} + +impl Clean for ast::fn_decl { + fn clean(&self) -> FnDecl { + FnDecl { + inputs: self.inputs.iter().map(|x| x.clean()).collect(), + output: (self.output.clean()), + cf: self.cf.clean(), + attrs: ~[] + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Argument { + type_: Type, + name: ~str, + id: ast::NodeId +} + +impl Clean for ast::arg { + fn clean(&self) -> Argument { + Argument { + name: name_from_pat(self.pat), + type_: (self.ty.clean()), + id: self.id + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum RetStyle { + NoReturn, + Return +} + +impl Clean for ast::ret_style { + fn clean(&self) -> RetStyle { + match *self { + ast::return_val => Return, + ast::noreturn => NoReturn + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Trait { + methods: ~[TraitMethod], + generics: Generics, + parents: ~[Type], +} + +impl Clean for doctree::Trait { + fn clean(&self) -> Item { + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + id: self.id, + visibility: self.vis.clean(), + inner: TraitItem(Trait { + methods: self.methods.clean(), + generics: self.generics.clean(), + parents: self.parents.clean(), + }), + } + } +} + +impl Clean for ast::trait_ref { + fn clean(&self) -> Type { + let t = Unresolved(self.path.clean(), None, self.ref_id); + resolve_type(&t) + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum TraitMethod { + Required(Item), + Provided(Item), +} + +impl TraitMethod { + fn is_req(&self) -> bool { + match self { + &Required(*) => true, + _ => false, + } + } + fn is_def(&self) -> bool { + match self { + &Provided(*) => true, + _ => false, + } + } +} + +impl Clean for ast::trait_method { + fn clean(&self) -> TraitMethod { + match self { + &ast::required(ref t) => Required(t.clean()), + &ast::provided(ref t) => Provided(t.clean()), + } + } +} + +/// A representation of a Type suitable for hyperlinking purposes. Ideally one can get the original +/// type out of the AST/ty::ctxt given one of these, if more information is needed. Most importantly +/// it does not preserve mutability or boxes. +#[deriving(Clone, Encodable, Decodable)] +pub enum Type { + /// Most types start out as "Unresolved". It serves as an intermediate stage between cleaning + /// and type resolution. + Unresolved(Path, Option<~[TyParamBound]>, ast::NodeId), + /// structs/enums/traits (anything that'd be an ast::ty_path) + ResolvedPath { path: Path, typarams: Option<~[TyParamBound]>, id: ast::NodeId }, + /// Reference to an item in an external crate (fully qualified path) + External(~str, ~str), + // I have no idea how to usefully use this. + TyParamBinder(ast::NodeId), + /// For parameterized types, so the consumer of the JSON don't go looking + /// for types which don't exist anywhere. + Generic(ast::NodeId), + /// For references to self + Self(ast::NodeId), + /// Primitives are just the fixed-size numeric types (plus int/uint/float), and char. + Primitive(ast::prim_ty), + Closure(~ClosureDecl), + /// extern "ABI" fn + BareFunction(~BareFunctionDecl), + Tuple(~[Type]), + Vector(~Type), + FixedVector(~Type, ~str), + String, + Bool, + /// aka ty_nil + Unit, + /// aka ty_bot + Bottom, + Unique(~Type), + Managed(Mutability, ~Type), + RawPointer(Mutability, ~Type), + BorrowedRef { lifetime: Option, mutability: Mutability, type_: ~Type}, + // region, raw, other boxes, mutable +} + +impl Clean for ast::Ty { + fn clean(&self) -> Type { + use syntax::ast::*; + debug!("cleaning type `%?`", self); + let codemap = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess.codemap; + debug!("span corresponds to `%s`", codemap.span_to_str(self.span)); + let t = match self.node { + ty_nil => Unit, + ty_ptr(ref m) => RawPointer(m.mutbl.clean(), ~resolve_type(&m.ty.clean())), + ty_rptr(ref l, ref m) => + BorrowedRef {lifetime: l.clean(), mutability: m.mutbl.clean(), + type_: ~resolve_type(&m.ty.clean())}, + ty_box(ref m) => Managed(m.mutbl.clean(), ~resolve_type(&m.ty.clean())), + ty_uniq(ref m) => Unique(~resolve_type(&m.ty.clean())), + ty_vec(ref m) => Vector(~resolve_type(&m.ty.clean())), + ty_fixed_length_vec(ref m, ref e) => FixedVector(~resolve_type(&m.ty.clean()), + e.span.to_src()), + ty_tup(ref tys) => Tuple(tys.iter().map(|x| resolve_type(&x.clean())).collect()), + ty_path(ref p, ref tpbs, id) => Unresolved(p.clean(), tpbs.clean(), id), + ty_closure(ref c) => Closure(~c.clean()), + ty_bare_fn(ref barefn) => BareFunction(~barefn.clean()), + ty_bot => Bottom, + ref x => fail!("Unimplemented type %?", x), + }; + resolve_type(&t) + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct StructField { + type_: Type, +} + +impl Clean for ast::struct_field { + fn clean(&self) -> Item { + let (name, vis) = match self.node.kind { + ast::named_field(id, vis) => (Some(id), Some(vis)), + _ => (None, None) + }; + Item { + name: name.clean(), + attrs: self.node.attrs.clean(), + source: self.span.clean(), + visibility: vis, + id: self.node.id, + inner: StructFieldItem(StructField { + type_: self.node.ty.clean(), + }), + } + } +} + +pub type Visibility = ast::visibility; + +impl Clean> for ast::visibility { + fn clean(&self) -> Option { + Some(*self) + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Struct { + struct_type: doctree::StructType, + generics: Generics, + fields: ~[Item], +} + +impl Clean for doctree::Struct { + fn clean(&self) -> Item { + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + id: self.id, + visibility: self.vis.clean(), + inner: StructItem(Struct { + struct_type: self.struct_type, + generics: self.generics.clean(), + fields: self.fields.clean(), + }), + } + } +} + +/// This is a more limited form of the standard Struct, different in that it +/// it lacks the things most items have (name, id, parameterization). Found +/// only as a variant in an enum. +#[deriving(Clone, Encodable, Decodable)] +pub struct VariantStruct { + struct_type: doctree::StructType, + fields: ~[Item], +} + +impl Clean for syntax::ast::struct_def { + fn clean(&self) -> VariantStruct { + VariantStruct { + struct_type: doctree::struct_type_from_def(self), + fields: self.fields.clean(), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Enum { + variants: ~[Item], + generics: Generics, +} + +impl Clean for doctree::Enum { + fn clean(&self) -> Item { + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + id: self.id, + visibility: self.vis.clean(), + inner: EnumItem(Enum { + variants: self.variants.clean(), + generics: self.generics.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Variant { + kind: VariantKind, +} + +impl Clean for doctree::Variant { + fn clean(&self) -> Item { + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + visibility: self.vis.clean(), + id: self.id, + inner: VariantItem(Variant { + kind: self.kind.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum VariantKind { + CLikeVariant, + TupleVariant(~[Type]), + StructVariant(VariantStruct), +} + +impl Clean for ast::variant_kind { + fn clean(&self) -> VariantKind { + match self { + &ast::tuple_variant_kind(ref args) => { + if args.len() == 0 { + CLikeVariant + } else { + TupleVariant(args.iter().map(|x| x.ty.clean()).collect()) + } + }, + &ast::struct_variant_kind(ref sd) => StructVariant(sd.clean()), + } + } +} + +impl Clean<~str> for syntax::codemap::span { + fn clean(&self) -> ~str { + let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap; + cm.span_to_str(*self) + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Path { + name: ~str, + lifetime: Option, + typarams: ~[Type] +} + +impl Clean for ast::Path { + fn clean(&self) -> Path { + Path { + name: path_to_str(self), + lifetime: self.rp.clean(), + typarams: self.types.clean(), + } + } +} + +fn path_to_str(p: &ast::Path) -> ~str { + use syntax::parse::token::interner_get; + + let mut s = ~""; + let mut first = true; + for i in p.idents.iter().map(|x| interner_get(x.name)) { + if !first || p.global { + s.push_str("::"); + } else { + first = false; + } + s.push_str(i); + } + s +} + +impl Clean<~str> for ast::ident { + fn clean(&self) -> ~str { + its(self).to_owned() + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Typedef { + type_: Type, + generics: Generics, +} + +impl Clean for doctree::Typedef { + fn clean(&self) -> Item { + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + id: self.id.clone(), + visibility: self.vis.clean(), + inner: TypedefItem(Typedef { + type_: self.ty.clean(), + generics: self.gen.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct BareFunctionDecl { + purity: ast::purity, + generics: Generics, + decl: FnDecl, + abi: ~str +} + +impl Clean for ast::TyBareFn { + fn clean(&self) -> BareFunctionDecl { + BareFunctionDecl { + purity: self.purity, + generics: Generics { + lifetimes: self.lifetimes.clean(), + type_params: ~[], + }, + decl: self.decl.clean(), + abi: self.abis.to_str(), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Static { + type_: Type, + mutability: Mutability, + /// It's useful to have the value of a static documented, but I have no + /// desire to represent expressions (that'd basically be all of the AST, + /// which is huge!). So, have a string. + expr: ~str, +} + +impl Clean for doctree::Static { + fn clean(&self) -> Item { + debug!("claning static %s: %?", self.name.clean(), self); + Item { + name: Some(self.name.clean()), + attrs: self.attrs.clean(), + source: self.where.clean(), + id: self.id, + visibility: self.vis.clean(), + inner: StaticItem(Static { + type_: self.type_.clean(), + mutability: self.mutability.clean(), + expr: self.expr.span.to_src(), + }), + } + } +} + +#[deriving(ToStr, Clone, Encodable, Decodable)] +pub enum Mutability { + Mutable, + Immutable, + Const, +} + +impl Clean for ast::mutability { + fn clean(&self) -> Mutability { + match self { + &ast::m_mutbl => Mutable, + &ast::m_imm => Immutable, + &ast::m_const => Const + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct Impl { + generics: Generics, + trait_: Option, + for_: Type, + methods: ~[Item], +} + +impl Clean for doctree::Impl { + fn clean(&self) -> Item { + Item { + name: None, + attrs: self.attrs.clean(), + source: self.where.clean(), + id: self.id, + visibility: self.vis.clean(), + inner: ImplItem(Impl { + generics: self.generics.clean(), + trait_: self.trait_.clean(), + for_: self.for_.clean(), + methods: self.methods.clean(), + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub struct ViewItem { + inner: ViewItemInner +} + +impl Clean for ast::view_item { + fn clean(&self) -> Item { + Item { + name: None, + attrs: self.attrs.clean(), + source: self.span.clean(), + id: 0, + visibility: self.vis.clean(), + inner: ViewItemItem(ViewItem { + inner: self.node.clean() + }), + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum ViewItemInner { + ExternMod(~str, Option<~str>, ~[Attribute], ast::NodeId), + Import(~[ViewPath]) +} + +impl Clean for ast::view_item_ { + fn clean(&self) -> ViewItemInner { + match self { + &ast::view_item_extern_mod(ref i, ref p, ref mi, ref id) => + ExternMod(i.clean(), p.map(|x| x.to_owned()), mi.clean(), *id), + &ast::view_item_use(ref vp) => Import(vp.clean()) + } + } +} + +#[deriving(Clone, Encodable, Decodable)] +pub enum ViewPath { + SimpleImport(~str, Path, ast::NodeId), + GlobImport(Path, ast::NodeId), + ImportList(Path, ~[ViewListIdent], ast::NodeId) +} + +impl Clean for ast::view_path { + fn clean(&self) -> ViewPath { + match self.node { + ast::view_path_simple(ref i, ref p, ref id) => SimpleImport(i.clean(), p.clean(), *id), + ast::view_path_glob(ref p, ref id) => GlobImport(p.clean(), *id), + ast::view_path_list(ref p, ref pl, ref id) => ImportList(p.clean(), pl.clean(), *id), + } + } +} + +pub type ViewListIdent = ~str; + +impl Clean for ast::path_list_ident { + fn clean(&self) -> ViewListIdent { + self.node.name.clean() + } +} + +// Utilities + +trait ToSource { + fn to_src(&self) -> ~str; +} + +impl ToSource for syntax::codemap::span { + fn to_src(&self) -> ~str { + debug!("converting span %s to snippet", self.clean()); + let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap.clone(); + let sn = match cm.span_to_snippet(*self) { + Some(x) => x, + None => ~"" + }; + debug!("got snippet %s", sn); + sn + } +} + +fn lit_to_str(lit: &ast::lit) -> ~str { + match lit.node { + ast::lit_str(st) => st.to_owned(), + ast::lit_int(ch, ast::ty_char) => ~"'" + ch.to_str() + "'", + ast::lit_int(i, _t) => i.to_str(), + ast::lit_uint(u, _t) => u.to_str(), + ast::lit_int_unsuffixed(i) => i.to_str(), + ast::lit_float(f, _t) => f.to_str(), + ast::lit_float_unsuffixed(f) => f.to_str(), + ast::lit_bool(b) => b.to_str(), + ast::lit_nil => ~"", + } +} + +fn name_from_pat(p: &ast::pat) -> ~str { + use syntax::ast::*; + match p.node { + pat_wild => ~"_", + pat_ident(_, ref p, _) => path_to_str(p), + pat_enum(ref p, _) => path_to_str(p), + pat_struct(*) => fail!("tried to get argument name from pat_struct, \ + which is not allowed in function arguments"), + pat_tup(*) => ~"(tuple arg NYI)", + pat_box(p) => name_from_pat(p), + pat_uniq(p) => name_from_pat(p), + pat_region(p) => name_from_pat(p), + pat_lit(*) => fail!("tried to get argument name from pat_lit, \ + which is not allowed in function arguments"), + pat_range(*) => fail!("tried to get argument name from pat_range, \ + which is not allowed in function arguments"), + pat_vec(*) => fail!("tried to get argument name from pat_vec, \ + which is not allowed in function arguments") + } +} + +fn remove_comment_tags(s: &str) -> ~str { + if s.starts_with("/") { + match s.slice(0,3) { + &"///" => return s.slice(3, s.len()).trim().to_owned(), + &"/**" | &"/*!" => return s.slice(3, s.len() - 2).trim().to_owned(), + _ => return s.trim().to_owned() + } + } else { + return s.to_owned(); + } +} + +/*fn collapse_docs(attrs: ~[Attribute]) -> ~[Attribute] { + let mut docstr = ~""; + for at in attrs.iter() { + match *at { + //XXX how should these be separated? + NameValue(~"doc", ref s) => docstr.push_str(fmt!("%s ", clean_comment_body(s.clone()))), + _ => () + } + } + let mut a = attrs.iter().filter(|&a| match a { + &NameValue(~"doc", _) => false, + _ => true + }).map(|x| x.clone()).collect::<~[Attribute]>(); + a.push(NameValue(~"doc", docstr.trim().to_owned())); + a +}*/ + +/// Given a Type, resolve it using the def_map +fn resolve_type(t: &Type) -> Type { + use syntax::ast::*; + + let (path, tpbs, id) = match t { + &Unresolved(ref path, ref tbps, id) => (path, tbps, id), + _ => return (*t).clone(), + }; + + let dm = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.def_map; + debug!("searching for %? in defmap", id); + let d = match dm.find(&id) { + Some(k) => k, + None => { + let ctxt = local_data::get(super::ctxtkey, |x| *x.unwrap()); + debug!("could not find %? in defmap (`%s`)", id, + syntax::ast_map::node_id_to_str(ctxt.tycx.items, id, ctxt.sess.intr())); + fail!("Unexpected failure: unresolved id not in defmap (this is a bug!)") + } + }; + + let def_id = match *d { + def_fn(i, _) => i, + def_self(i, _) | def_self_ty(i) => return Self(i), + def_ty(i) => i, + def_trait(i) => { + debug!("saw def_trait in def_to_id"); + i + }, + def_prim_ty(p) => match p { + ty_str => return String, + ty_bool => return Bool, + _ => return Primitive(p) + }, + def_ty_param(i, _) => return Generic(i.node), + def_struct(i) => i, + def_typaram_binder(i) => { + debug!("found a typaram_binder, what is it? %d", i); + return TyParamBinder(i); + }, + x => fail!("resolved type maps to a weird def %?", x), + }; + + if def_id.crate != ast::CRATE_NODE_ID { + let sess = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess; + let mut path = ~""; + let mut ty = ~""; + do csearch::each_path(sess.cstore, def_id.crate) |pathstr, deflike, _vis| { + match deflike { + decoder::dl_def(di) => { + let d2 = match di { + def_fn(i, _) | def_ty(i) | def_trait(i) | + def_struct(i) | def_mod(i) => Some(i), + _ => None, + }; + if d2.is_some() { + let d2 = d2.unwrap(); + if def_id.node == d2.node { + debug!("found external def: %?", di); + path = pathstr.to_owned(); + ty = match di { + def_fn(*) => ~"fn", + def_ty(*) => ~"enum", + def_trait(*) => ~"trait", + def_prim_ty(p) => match p { + ty_str => ~"str", + ty_bool => ~"bool", + ty_int(t) => match t.to_str() { + ~"" => ~"i", + s => s + }, + ty_uint(t) => t.to_str(), + ty_float(t) => t.to_str() + }, + def_ty_param(*) => ~"generic", + def_struct(*) => ~"struct", + def_typaram_binder(*) => ~"typaram_binder", + x => fail!("resolved external maps to a weird def %?", x), + }; + + } + } + }, + _ => (), + }; + true + }; + let cname = cstore::get_crate_data(sess.cstore, def_id.crate).name.to_owned(); + External(cname + "::" + path, ty) + } else { + ResolvedPath {path: path.clone(), typarams: tpbs.clone(), id: def_id.node} + } +} + +#[cfg(test)] +mod tests { + use super::NameValue; + + #[test] + fn test_doc_collapsing() { + assert_eq!(collapse_docs(~"// Foo\n//Bar\n // Baz\n"), ~"Foo\nBar\nBaz"); + assert_eq!(collapse_docs(~"* Foo\n * Bar\n *Baz\n"), ~"Foo\n Bar\nBaz"); + assert_eq!(collapse_docs(~"* Short desc\n *\n * Bar\n *Baz\n"), ~"Short desc\n\nBar\nBaz"); + assert_eq!(collapse_docs(~" * Foo"), ~"Foo"); + assert_eq!(collapse_docs(~"\n *\n *\n * Foo"), ~"Foo"); + } + + fn collapse_docs(input: ~str) -> ~str { + let attrs = ~[NameValue(~"doc", input)]; + let attrs_clean = super::collapse_docs(attrs); + + match attrs_clean[0] { + NameValue(~"doc", s) => s, + _ => (fail!("dude where's my doc?")) + } + } +} diff --git a/src/rustdoc_ng/core.rs b/src/rustdoc_ng/core.rs new file mode 100644 index 00000000000..8e12dbdce4d --- /dev/null +++ b/src/rustdoc_ng/core.rs @@ -0,0 +1,68 @@ +use rustc; +use rustc::{driver, middle}; + +use syntax; +use syntax::parse; +use syntax::ast; + +use std::os; +use std::local_data; + +use visit_ast::RustdocVisitor; +use clean; +use clean::Clean; + +pub struct DocContext { + crate: @ast::Crate, + tycx: middle::ty::ctxt, + sess: driver::session::Session +} + +/// Parses, resolves, and typechecks the given crate +fn get_ast_and_resolve(cpath: &Path, libs: ~[Path]) -> DocContext { + use syntax::codemap::dummy_spanned; + use rustc::driver::driver::*; + + let parsesess = parse::new_parse_sess(None); + let input = file_input(cpath.clone()); + + let sessopts = @driver::session::options { + binary: @"rustdoc", + maybe_sysroot: Some(@os::self_exe_path().unwrap().pop()), + addl_lib_search_paths: @mut libs, + .. (*rustc::driver::session::basic_options()).clone() + }; + + + let diagnostic_handler = syntax::diagnostic::mk_handler(None); + let span_diagnostic_handler = + syntax::diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm); + + let sess = driver::driver::build_session_(sessopts, parsesess.cm, + syntax::diagnostic::emit, + span_diagnostic_handler); + + let mut cfg = build_configuration(sess, @"rustdoc_ng", &input); + cfg.push(@dummy_spanned(ast::MetaWord(@"stage2"))); + + let mut crate = phase_1_parse_input(sess, cfg.clone(), &input); + crate = phase_2_configure_and_expand(sess, cfg, crate); + let analysis = phase_3_run_analysis_passes(sess, crate); + + debug!("crate: %?", crate); + DocContext { crate: crate, tycx: analysis.ty_cx, sess: sess } +} + +pub fn run_core (libs: ~[Path], path: &Path) -> clean::Crate { + let ctxt = @get_ast_and_resolve(path, libs); + debug!("defmap:"); + for (k, v) in ctxt.tycx.def_map.iter() { + debug!("%?: %?", k, v); + } + local_data::set(super::ctxtkey, ctxt); + + let v = @mut RustdocVisitor::new(); + v.visit(ctxt.crate); + + v.clean() +} diff --git a/src/rustdoc_ng/doctree.rs b/src/rustdoc_ng/doctree.rs new file mode 100644 index 00000000000..d5ad0fca30f --- /dev/null +++ b/src/rustdoc_ng/doctree.rs @@ -0,0 +1,158 @@ +//! This module is used to store stuff from Rust's AST in a more convenient +//! manner (and with prettier names) before cleaning. + +use syntax; +use syntax::codemap::span; +use syntax::ast; +use syntax::ast::{ident, NodeId}; + +pub struct Module { + name: Option, + attrs: ~[ast::Attribute], + where: span, + structs: ~[Struct], + enums: ~[Enum], + fns: ~[Function], + mods: ~[Module], + id: NodeId, + typedefs: ~[Typedef], + statics: ~[Static], + traits: ~[Trait], + vis: ast::visibility, + impls: ~[Impl], + view_items: ~[ast::view_item], +} + +impl Module { + pub fn new(name: Option) -> Module { + Module { + name : name, + id: 0, + vis: ast::private, + where: syntax::codemap::dummy_sp(), + attrs : ~[], + structs : ~[], + enums : ~[], + fns : ~[], + mods : ~[], + typedefs : ~[], + statics : ~[], + traits : ~[], + impls : ~[], + view_items : ~[], + } + } +} + +#[deriving(ToStr, Clone, Encodable, Decodable)] +pub enum StructType { + /// A normal struct + Plain, + /// A tuple struct + Tuple, + /// A newtype struct (tuple struct with one element) + Newtype, + /// A unit struct + Unit +} + +pub enum TypeBound { + RegionBound, + TraitBound(ast::trait_ref) +} + +pub struct Struct { + vis: ast::visibility, + id: NodeId, + struct_type: StructType, + name: ident, + generics: ast::Generics, + attrs: ~[ast::Attribute], + fields: ~[@ast::struct_field], + where: span, +} + +pub struct Enum { + vis: ast::visibility, + variants: ~[Variant], + generics: ast::Generics, + attrs: ~[ast::Attribute], + id: NodeId, + where: span, + name: ident, +} + +pub struct Variant { + name: ident, + attrs: ~[ast::Attribute], + kind: ast::variant_kind, + id: ast::NodeId, + vis: ast::visibility, + where: span, +} + +pub struct Function { + decl: ast::fn_decl, + attrs: ~[ast::Attribute], + id: NodeId, + name: ident, + vis: ast::visibility, + where: span, + generics: ast::Generics, +} + +pub struct Typedef { + ty: ast::Ty, + gen: ast::Generics, + name: ast::ident, + id: ast::NodeId, + attrs: ~[ast::Attribute], + where: span, + vis: ast::visibility, +} + +pub struct Static { + type_: ast::Ty, + mutability: ast::mutability, + expr: @ast::expr, + name: ast::ident, + attrs: ~[ast::Attribute], + vis: ast::visibility, + id: ast::NodeId, + where: span, +} + +pub struct Trait { + name: ast::ident, + methods: ~[ast::trait_method], //should be TraitMethod + generics: ast::Generics, + parents: ~[ast::trait_ref], + attrs: ~[ast::Attribute], + id: ast::NodeId, + where: span, + vis: ast::visibility, +} + +pub struct Impl { + generics: ast::Generics, + trait_: Option, + for_: ast::Ty, + methods: ~[@ast::method], + attrs: ~[ast::Attribute], + where: span, + vis: ast::visibility, + id: ast::NodeId, +} + +pub fn struct_type_from_def(sd: &ast::struct_def) -> StructType { + if sd.ctor_id.is_some() { + // We are in a tuple-struct + match sd.fields.len() { + 0 => Unit, + 1 => Newtype, + _ => Tuple + } + } else { + Plain + } +} diff --git a/src/rustdoc_ng/fold.rs b/src/rustdoc_ng/fold.rs new file mode 100644 index 00000000000..740e434f465 --- /dev/null +++ b/src/rustdoc_ng/fold.rs @@ -0,0 +1,101 @@ +use std; +use clean::*; +use std::iterator::Extendable; + +pub trait DocFolder { + fn fold_item(&mut self, item: Item) -> Option { + self.fold_item_recur(item) + } + + /// don't override! + fn fold_item_recur(&mut self, item: Item) -> Option { + use std::util::swap; + let Item { attrs, name, source, visibility, id, inner } = item; + let inner = inner; + let c = |x| self.fold_item(x); + let inner = match inner { + StructItem(i) => { + let mut i = i; + let mut foo = ~[]; swap(&mut foo, &mut i.fields); + i.fields.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x))); + StructItem(i) + }, + ModuleItem(i) => { + ModuleItem(self.fold_mod(i)) + }, + EnumItem(i) => { + let mut i = i; + let mut foo = ~[]; swap(&mut foo, &mut i.variants); + i.variants.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x))); + EnumItem(i) + }, + TraitItem(i) => { + fn vtrm(this: &mut T, trm: TraitMethod) -> Option { + match trm { + Required(it) => { + match this.fold_item(it) { + Some(x) => return Some(Required(x)), + None => return None, + } + }, + Provided(it) => { + match this.fold_item(it) { + Some(x) => return Some(Provided(x)), + None => return None, + } + }, + } + } + let mut i = i; + let mut foo = ~[]; swap(&mut foo, &mut i.methods); + i.methods.extend(&mut foo.move_iter().filter_map(|x| vtrm(self, x))); + TraitItem(i) + }, + ImplItem(i) => { + let mut i = i; + let mut foo = ~[]; swap(&mut foo, &mut i.methods); + i.methods.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x))); + ImplItem(i) + }, + VariantItem(i) => { + let i2 = i.clone(); // this clone is small + match i.kind { + StructVariant(j) => { + let mut j = j; + let mut foo = ~[]; swap(&mut foo, &mut j.fields); + j.fields.extend(&mut foo.move_iter().filter_map(c)); + VariantItem(Variant {kind: StructVariant(j), ..i2}) + }, + _ => VariantItem(i2) + } + }, + x => x + }; + + Some(Item { attrs: attrs, name: name, source: source, inner: inner, + visibility: visibility, id: id }) + } + + fn fold_mod(&mut self, m: Module) -> Module { + Module { items: m.items.move_iter().filter_map(|i| self.fold_item(i)).collect() } + } + + fn fold_crate(&mut self, mut c: Crate) -> Crate { + let mut mod_ = None; + std::util::swap(&mut mod_, &mut c.module); + let mod_ = mod_.unwrap(); + c.module = self.fold_item(mod_); + let Crate { name, module } = c; + match module { + Some(Item { inner: ModuleItem(m), name: name_, attrs: attrs_, + source, visibility: vis, id }) => { + return Crate { module: Some(Item { inner: + ModuleItem(self.fold_mod(m)), + name: name_, attrs: attrs_, + source: source, id: id, visibility: vis }), name: name}; + }, + Some(_) => fail!("non-module item set as module of crate"), + None => return Crate { module: None, name: name}, + } + } +} diff --git a/src/rustdoc_ng/lib.rs b/src/rustdoc_ng/lib.rs new file mode 100644 index 00000000000..6d34fae6090 --- /dev/null +++ b/src/rustdoc_ng/lib.rs @@ -0,0 +1,27 @@ +#[link(name = "rustdoc_ng", + vers = "0.1.0", + uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6")]; +#[desc = "rustdoc, the Rust documentation extractor"]; +#[license = "MIT/ASL2"]; +#[crate_type = "lib"]; + +#[deny(warnings)]; + +extern mod syntax; +extern mod rustc; + +extern mod extra; + +use extra::serialize::Encodable; + +pub mod core; +pub mod doctree; +pub mod clean; +pub mod visit_ast; +pub mod fold; +pub mod plugins; +pub mod passes; + +pub static SCHEMA_VERSION: &'static str = "0.8.0"; + +pub static ctxtkey: std::local_data::Key<@core::DocContext> = &std::local_data::Key; diff --git a/src/rustdoc_ng/main.rs b/src/rustdoc_ng/main.rs new file mode 100644 index 00000000000..47b37052917 --- /dev/null +++ b/src/rustdoc_ng/main.rs @@ -0,0 +1,94 @@ +#[link(name = "rustdoc_ng", + vers = "0.1.0", + uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6")]; +#[desc = "rustdoc, the Rust documentation extractor"]; +#[license = "MIT/ASL2"]; +#[crate_type = "bin"]; + +extern mod extra; +extern mod rustdoc_ng; + +use rustdoc_ng::*; +use std::cell::Cell; + +use extra::serialize::Encodable; + +fn main() { + use extra::getopts::*; + use extra::getopts::groups::*; + + let args = std::os::args(); + let opts = ~[ + optmulti("L", "library-path", "directory to add to crate search path", "DIR"), + optmulti("p", "plugin", "plugin to load and run", "NAME"), + optmulti("", "plugin-path", "directory to load plugins from", "DIR"), + // auxillary pass (defaults to hidden_strip + optmulti("a", "pass", "auxillary pass to run", "NAME"), + optflag("n", "no-defult-passes", "do not run the default passes"), + optflag("h", "help", "show this help message"), + ]; + + let matches = getopts(args.tail(), opts).unwrap(); + + if opt_present(&matches, "h") || opt_present(&matches, "help") { + println(usage(args[0], opts)); + return; + } + + let libs = Cell::new(opt_strs(&matches, "L").map(|s| Path(*s))); + + let mut passes = if opt_present(&matches, "n") { + ~[] + } else { + ~[~"collapse-docs", ~"clean-comments", ~"collapse-privacy" ] + }; + + opt_strs(&matches, "a").map(|x| passes.push(x.clone())); + + if matches.free.len() != 1 { + println(usage(args[0], opts)); + return; + } + + let cr = Cell::new(Path(matches.free[0])); + + let crate = std::task::try(|| {let cr = cr.take(); core::run_core(libs.take(), &cr)}).unwrap(); + + // { "schema": version, "crate": { parsed crate ... }, "plugins": { output of plugins ... }} + let mut json = ~extra::treemap::TreeMap::new(); + json.insert(~"schema", extra::json::String(SCHEMA_VERSION.to_owned())); + + let mut pm = plugins::PluginManager::new(Path("/tmp/rustdoc_ng/plugins")); + + for pass in passes.iter() { + pm.add_plugin(match pass.as_slice() { + "strip-hidden" => passes::strip_hidden, + "clean-comments" => passes::clean_comments, + "collapse-docs" => passes::collapse_docs, + "collapse-privacy" => passes::collapse_privacy, + s => { error!("unknown pass %s, skipping", s); passes::noop }, + }) + } + + for pname in opt_strs(&matches, "p").move_iter() { + pm.load_plugin(pname); + } + + let (crate, res) = pm.run_plugins(crate); + let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect(); + + // FIXME: yuck, Rust -> str -> JSON round trip! No way to .encode + // straight to the Rust JSON representation. + let crate_json_str = do std::io::with_str_writer |w| { + crate.encode(&mut extra::json::Encoder(w)); + }; + let crate_json = match extra::json::from_str(crate_json_str) { + Ok(j) => j, + Err(_) => fail!("Rust generated JSON is invalid??") + }; + + json.insert(~"crate", crate_json); + json.insert(~"plugins", extra::json::Object(plugins_json)); + + println(extra::json::Object(json).to_str()); +} diff --git a/src/rustdoc_ng/passes.rs b/src/rustdoc_ng/passes.rs new file mode 100644 index 00000000000..73f94ef0f27 --- /dev/null +++ b/src/rustdoc_ng/passes.rs @@ -0,0 +1,193 @@ +use std; +use clean; +use syntax::ast; +use clean::Item; +use plugins; +use fold; +use fold::DocFolder; + +/// A sample pass showing the minimum required work for a plugin. +pub fn noop(crate: clean::Crate) -> plugins::PluginResult { + (crate, None) +} + +/// Strip items marked `#[doc(hidden)]` +pub fn strip_hidden(crate: clean::Crate) -> plugins::PluginResult { + struct Stripper; + impl fold::DocFolder for Stripper { + fn fold_item(&mut self, i: Item) -> Option { + for attr in i.attrs.iter() { + match attr { + &clean::List(~"doc", ref l) => { + for innerattr in l.iter() { + match innerattr { + &clean::Word(ref s) if "hidden" == *s => { + info!("found one in strip_hidden; removing"); + return None; + }, + _ => (), + } + } + }, + _ => () + } + } + self.fold_item_recur(i) + } + } + let mut stripper = Stripper; + let crate = stripper.fold_crate(crate); + (crate, None) +} + +pub fn clean_comments(crate: clean::Crate) -> plugins::PluginResult { + struct CommentCleaner; + impl fold::DocFolder for CommentCleaner { + fn fold_item(&mut self, i: Item) -> Option { + let mut i = i; + let mut avec: ~[clean::Attribute] = ~[]; + for attr in i.attrs.iter() { + match attr { + &clean::NameValue(~"doc", ref s) => avec.push( + clean::NameValue(~"doc", clean_comment_body(s.clone()))), + x => avec.push(x.clone()) + } + } + i.attrs = avec; + self.fold_item_recur(i) + } + } + let mut cleaner = CommentCleaner; + let crate = cleaner.fold_crate(crate); + (crate, None) +} + +pub fn collapse_privacy(crate: clean::Crate) -> plugins::PluginResult { + struct PrivacyCollapser { + stack: ~[clean::Visibility] + } + impl fold::DocFolder for PrivacyCollapser { + fn fold_item(&mut self, mut i: Item) -> Option { + if i.visibility.is_some() { + if i.visibility == Some(ast::inherited) { + i.visibility = Some(self.stack.last().clone()); + } else { + self.stack.push(i.visibility.clone().unwrap()); + } + } + self.fold_item_recur(i) + } + } + let mut privacy = PrivacyCollapser { stack: ~[] }; + let crate = privacy.fold_crate(crate); + (crate, None) +} + +pub fn collapse_docs(crate: clean::Crate) -> plugins::PluginResult { + struct Collapser; + impl fold::DocFolder for Collapser { + fn fold_item(&mut self, i: Item) -> Option { + let mut docstr = ~""; + let mut i = i; + for attr in i.attrs.iter() { + match *attr { + clean::NameValue(~"doc", ref s) => { + docstr.push_str(s.clone()); + docstr.push_char('\n'); + }, + _ => () + } + } + let mut a: ~[clean::Attribute] = i.attrs.iter().filter(|&a| match a { + &clean::NameValue(~"doc", _) => false, + _ => true + }).map(|x| x.clone()).collect(); + if "" != docstr { + a.push(clean::NameValue(~"doc", docstr.trim().to_owned())); + } + i.attrs = a; + self.fold_item_recur(i) + } + } + let mut collapser = Collapser; + let crate = collapser.fold_crate(crate); + (crate, None) +} + +//Utility +enum CleanCommentStates { + Collect, + Strip, + Stripped, +} + +/// Returns the index of the last character all strings have common in their +/// prefix. +fn longest_common_prefix(s: ~[~str]) -> uint { + // find the longest common prefix + + debug!("lcp: looking into %?", s); + // index of the last character all the strings share + let mut index = 0u; + + if s.len() <= 1 { + return 0; + } + + // whether one of the strings has been exhausted of characters yet + let mut exhausted = false; + + // character iterators for all the lines + let mut lines = s.iter().filter(|x| x.len() != 0).map(|x| x.iter()).to_owned_vec(); + + 'outer: loop { + // because you can't label a while loop + if exhausted == true { + break; + } + debug!("lcp: index %u", index); + let mut lines = lines.mut_iter(); + let ch = match lines.next().unwrap().next() { + Some(c) => c, + None => { exhausted = true; loop }, + }; + debug!("looking for char %c", ch); + for line in lines { + match line.next() { + Some(c) => if c == ch { loop } else { exhausted = true; loop 'outer }, + None => { exhausted = true; loop 'outer } + } + } + index += 1; + } + + debug!("lcp: last index %u", index); + index +} + +fn clean_comment_body(s: ~str) -> ~str { + // FIXME #31: lots of copies in here. + let lines = s.line_iter().to_owned_vec(); + match lines.len() { + 0 => return ~"", + 1 => return lines[0].slice_from(2).trim().to_owned(), + _ => (), + } + + let mut ol = std::vec::with_capacity(lines.len()); + for line in lines.clone().move_iter() { + // replace meaningless things with a single newline + match line { + x if ["/**", "/*!", "///", "//!", "*/"].contains(&x.trim()) => ol.push(~""), + x if x.trim() == "" => ol.push(~""), + x => ol.push(x.to_owned()) + } + } + let li = longest_common_prefix(ol.clone()); + + let x = ol.iter() + .filter(|x| { debug!("cleaning line: %s", **x); true }) + .map(|x| if x.len() == 0 { ~"" } else { x.slice_chars(li, x.char_len()).to_owned() }) + .to_owned_vec().connect("\n"); + x.trim().to_owned() +} diff --git a/src/rustdoc_ng/plugins.rs b/src/rustdoc_ng/plugins.rs new file mode 100644 index 00000000000..db243c9e21c --- /dev/null +++ b/src/rustdoc_ng/plugins.rs @@ -0,0 +1,80 @@ +use clean; + +use extra; +use dl = std::unstable::dynamic_lib; + +pub type PluginJson = Option<(~str, extra::json::Json)>; +pub type PluginResult = (clean::Crate, PluginJson); +pub type plugin_callback = extern fn (clean::Crate) -> PluginResult; + +/// Manages loading and running of plugins +pub struct PluginManager { + priv dylibs: ~[dl::DynamicLibrary], + priv callbacks: ~[plugin_callback], + /// The directory plugins will be loaded from + prefix: Path, +} + +impl PluginManager { + /// Create a new plugin manager + pub fn new(prefix: Path) -> PluginManager { + PluginManager { + dylibs: ~[], + callbacks: ~[], + prefix: prefix, + } + } + + /// Load a plugin with the given name. + /// + /// Turns `name` into the proper dynamic library filename for the given + /// platform. On windows, it turns into name.dll, on OS X, name.dylib, and + /// elsewhere, libname.so. + pub fn load_plugin(&mut self, name: ~str) { + let x = self.prefix.push(libname(name)); + let lib_result = dl::DynamicLibrary::open(Some(&x)); + let lib = lib_result.unwrap(); + let plugin = unsafe { lib.symbol("rustdoc_plugin_entrypoint") }.unwrap(); + self.dylibs.push(lib); + self.callbacks.push(plugin); + } + + /// Load a normal Rust function as a plugin. + /// + /// This is to run passes over the cleaned crate. Plugins run this way + /// correspond to the A-aux tag on Github. + pub fn add_plugin(&mut self, plugin: plugin_callback) { + self.callbacks.push(plugin); + } + /// Run all the loaded plugins over the crate, returning their results + pub fn run_plugins(&self, crate: clean::Crate) -> (clean::Crate, ~[PluginJson]) { + let mut out_json = ~[]; + let mut crate = crate; + for &callback in self.callbacks.iter() { + let (c, res) = callback(crate); + crate = c; + out_json.push(res); + } + (crate, out_json) + } +} + +#[cfg(target_os="win32")] +fn libname(mut n: ~str) -> ~str { + n.push_str(".dll"); + n +} + +#[cfg(target_os="macos")] +fn libname(mut n: ~str) -> ~str { + n.push_str(".dylib"); + n +} + +#[cfg(not(target_os="win32"), not(target_os="macos"))] +fn libname(n: ~str) -> ~str { + let mut i = ~"lib"; + i.push_str(n); + i.push_str(".so"); + i +} diff --git a/src/rustdoc_ng/visit_ast.rs b/src/rustdoc_ng/visit_ast.rs new file mode 100644 index 00000000000..2e437a028ec --- /dev/null +++ b/src/rustdoc_ng/visit_ast.rs @@ -0,0 +1,173 @@ +//! Rust AST Visitor. Extracts useful information and massages it into a form +//! usable for clean + +use syntax::abi::AbiSet; +use syntax::{ast, ast_map}; +use syntax::codemap::span; + +use doctree::*; +use std::local_data; + +pub struct RustdocVisitor { + module: Module, + attrs: ~[ast::Attribute], +} + +impl RustdocVisitor { + pub fn new() -> RustdocVisitor { + RustdocVisitor { + module: Module::new(None), + attrs: ~[], + } + } +} + +impl RustdocVisitor { + pub fn visit(@mut self, crate: &ast::Crate) { + self.attrs = crate.attrs.clone(); + fn visit_struct_def(item: &ast::item, sd: @ast::struct_def, generics: + &ast::Generics) -> Struct { + debug!("Visiting struct"); + let struct_type = struct_type_from_def(sd); + Struct { + id: item.id, + struct_type: struct_type, + name: item.ident, + vis: item.vis, + attrs: item.attrs.clone(), + generics: generics.clone(), + fields: sd.fields.iter().map(|x| (*x).clone()).to_owned_vec(), + where: item.span + } + } + + fn visit_enum_def(it: &ast::item, def: &ast::enum_def, params: &ast::Generics) -> Enum { + debug!("Visiting enum"); + let mut vars: ~[Variant] = ~[]; + for x in def.variants.iter() { + vars.push(Variant { + name: x.node.name, + attrs: x.node.attrs.clone(), + vis: x.node.vis, + id: x.node.id, + kind: x.node.kind.clone(), + where: x.span, + }); + } + Enum { + name: it.ident, + variants: vars, + vis: it.vis, + generics: params.clone(), + attrs: it.attrs.clone(), + id: it.id, + where: it.span, + } + } + + fn visit_fn(item: &ast::item, fd: &ast::fn_decl, _purity: &ast::purity, + _abi: &AbiSet, gen: &ast::Generics) -> Function { + debug!("Visiting fn"); + Function { + id: item.id, + vis: item.vis, + attrs: item.attrs.clone(), + decl: fd.clone(), + name: item.ident, + where: item.span, + generics: gen.clone(), + } + } + + fn visit_mod_contents(span: span, attrs: ~[ast::Attribute], vis: + ast::visibility, id: ast::NodeId, m: &ast::_mod) -> Module { + let am = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.items; + let name = match am.find(&id) { + Some(m) => match m { + &ast_map::node_item(ref it, _) => Some(it.ident), + _ => fail!("mod id mapped to non-item in the ast map") + }, + None => None + }; + let mut om = Module::new(name); + om.view_items = m.view_items.clone(); + om.where = span; + om.attrs = attrs; + om.vis = vis; + om.id = id; + for i in m.items.iter() { + visit_item(*i, &mut om); + } + om + } + + fn visit_item(item: &ast::item, om: &mut Module) { + debug!("Visiting item %?", item); + match item.node { + ast::item_mod(ref m) => { + om.mods.push(visit_mod_contents(item.span, item.attrs.clone(), + item.vis, item.id, m)); + }, + ast::item_enum(ref ed, ref gen) => om.enums.push(visit_enum_def(item, ed, gen)), + ast::item_struct(sd, ref gen) => om.structs.push(visit_struct_def(item, sd, gen)), + ast::item_fn(ref fd, ref pur, ref abi, ref gen, _) => + om.fns.push(visit_fn(item, fd, pur, abi, gen)), + ast::item_ty(ref ty, ref gen) => { + let t = Typedef { + ty: ty.clone(), + gen: gen.clone(), + name: item.ident, + id: item.id, + attrs: item.attrs.clone(), + where: item.span, + vis: item.vis, + }; + om.typedefs.push(t); + }, + ast::item_static(ref ty, ref mut_, ref exp) => { + let s = Static { + type_: ty.clone(), + mutability: mut_.clone(), + expr: exp.clone(), + id: item.id, + name: item.ident, + attrs: item.attrs.clone(), + where: item.span, + vis: item.vis, + }; + om.statics.push(s); + }, + ast::item_trait(ref gen, ref tr, ref met) => { + let t = Trait { + name: item.ident, + methods: met.clone(), + generics: gen.clone(), + parents: tr.clone(), + id: item.id, + attrs: item.attrs.clone(), + where: item.span, + vis: item.vis, + }; + om.traits.push(t); + }, + ast::item_impl(ref gen, ref tr, ref ty, ref meths) => { + let i = Impl { + generics: gen.clone(), + trait_: tr.clone(), + for_: ty.clone(), + methods: meths.clone(), + attrs: item.attrs.clone(), + id: item.id, + where: item.span, + vis: item.vis, + }; + om.impls.push(i); + }, + _ => (), + } + } + + self.module = visit_mod_contents(crate.span, crate.attrs.clone(), + ast::public, ast::CRATE_NODE_ID, &crate.module); + } +}