From e773caea8010b87726ea524d31798fb2e43e12f4 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Sat, 21 Dec 2019 12:13:21 +0100 Subject: newtype in types, more generator config, parsing -> parser --- src/cli.rs | 7 +- src/config.rs | 6 +- src/generator/config.rs | 52 ++- src/generator/mod.rs | 42 ++- src/generator/pandoc.rs | 19 +- src/main.rs | 9 +- src/parser/clang/config.rs | 53 +++ src/parser/clang/entities.rs | 390 +++++++++++++++++++++ src/parser/clang/mod.rs | 5 + src/parser/clang/parsing.rs | 764 ++++++++++++++++++++++++++++++++++++++++++ src/parser/mod.rs | 1 + src/parsing/clang/config.rs | 53 --- src/parsing/clang/entities.rs | 369 -------------------- src/parsing/clang/mod.rs | 5 - src/parsing/clang/parsing.rs | 748 ----------------------------------------- src/parsing/mod.rs | 1 - src/types.rs | 70 +++- 17 files changed, 1365 insertions(+), 1229 deletions(-) create mode 100644 src/parser/clang/config.rs create mode 100644 src/parser/clang/entities.rs create mode 100644 src/parser/clang/mod.rs create mode 100644 src/parser/clang/parsing.rs create mode 100644 src/parser/mod.rs delete mode 100644 src/parsing/clang/config.rs delete mode 100644 src/parsing/clang/entities.rs delete mode 100644 src/parsing/clang/mod.rs delete mode 100644 src/parsing/clang/parsing.rs delete mode 100644 src/parsing/mod.rs diff --git a/src/cli.rs b/src/cli.rs index ff25792..0000644 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,11 +21,14 @@ pub(crate) struct Cli { #[derive(Debug, Clone, StructOpt)] pub(crate) enum Command { - Generate { file: String }, + Generate { + file: String, + }, + Inspect, Config { #[structopt(subcommand)] command: ConfigCommand, - } + }, } #[derive(Debug, Clone, StructOpt)] diff --git a/src/config.rs b/src/config.rs index 72a9ff6..942dfcf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use crate::cli::Cli; use crate::generator::config::{ Config as GeneratorConfig, ProvidedConfig as ProvidedGeneratorConfig, }; -use crate::parsing::clang::config::{Config as ClangConfig, ProvidedConfig as ProvidedClangConfig}; +use crate::parser::clang::config::{Config as ClangConfig, ProvidedConfig as ProvidedClangConfig}; use anyhow::{anyhow, Context, Result}; use codemap::CodeMap; @@ -19,8 +19,8 @@ pub(super) const DEFAULT_PROJECT_CONFIGURATION_FILE_NAME: &str = "poseidoc.toml" #[derive(Debug, Clone, Serialize)] pub(crate) struct Config { pub(crate) name: String, - pub(crate) clang: ClangConfig, pub(crate) generator: GeneratorConfig, + pub(crate) clang: ClangConfig, } impl Default for Config { @@ -57,7 +57,7 @@ pub(crate) struct ProvidedConfig { #[structopt(flatten)] #[serde(default)] pub(crate) clang: ProvidedClangConfig, - #[structopt(skip)] + #[structopt(flatten)] #[serde(default)] pub(crate) generator: ProvidedGeneratorConfig, } diff --git a/src/generator/config.rs b/src/generator/config.rs index 43fee60..3057fee 100644 --- a/src/generator/config.rs +++ b/src/generator/config.rs @@ -1,40 +1,58 @@ +use crate::types::*; + use serde_derive::{Deserialize, Serialize}; use structopt::StructOpt; use std::collections::{HashMap, HashSet}; +pub(crate) type Inlines = HashMap>>; + #[derive(Debug, Clone, Serialize)] pub(crate) struct Config { + pub(super) from: String, + pub(super) to: String, + pub(super) pandoc_filters: Vec, /// Tells us in which language, which entity types should inline which children entity type in /// their documentation. /// /// e.g. in C++, classes should inline their children methods documentation. - pub(crate) inlines: HashMap>>, + pub(super) inlines: Inlines, } impl Config { - pub(crate) fn from_merge(_cli: ProvidedConfig, conf: ProvidedConfig) -> Self { + pub(crate) fn from_merge(mut cli: ProvidedConfig, mut conf: ProvidedConfig) -> Self { + conf.pandoc_filters.append(&mut cli.pandoc_filters); Self { + from: cli + .from + .or(conf.from) + .unwrap_or_else(|| String::from("markdown-raw_tex")), + to: cli.to.or(conf.to).unwrap_or_else(|| String::from("html")), + pandoc_filters: conf.pandoc_filters, inlines: conf.inlines.unwrap_or_else(default_inlines), } } } -fn default_inlines() -> HashMap>> { +fn default_inlines() -> Inlines { let mut clang = HashMap::new(); let mut clang_inline_children = HashSet::new(); - // TODO: this is not great: no differences between variable/field for children, but differences - // as a parent... - clang_inline_children.insert(String::from("variable")); - //classes.insert(String::from("field")); - clang_inline_children.insert(String::from("function")); - //classes.insert(String::from("method")); - clang.insert(String::from("struct"), clang_inline_children.clone()); - clang.insert(String::from("class"), clang_inline_children.clone()); - clang.insert(String::from("namespace"), clang_inline_children); + clang_inline_children.insert(ChildrenKind(String::from("variables"))); + clang_inline_children.insert(ChildrenKind(String::from("fields"))); + clang_inline_children.insert(ChildrenKind(String::from("functions"))); + clang_inline_children.insert(ChildrenKind(String::from("methods"))); + clang.insert( + EntityKind(String::from("struct")), + clang_inline_children.clone(), + ); + clang.insert( + EntityKind(String::from("class")), + clang_inline_children.clone(), + ); + clang.insert(EntityKind(String::from("namespace")), clang_inline_children); let mut inlines = HashMap::new(); - inlines.insert(String::from("clang"), clang); + inlines.insert(Language::Clang, clang); inlines } @@ -48,6 +66,12 @@ impl Default for Config { #[derive(Debug, Clone, Default, StructOpt, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub(crate) struct ProvidedConfig { + #[structopt(long = "generator-from")] + pub(super) from: Option, + #[structopt(long = "generator-to")] + pub(super) to: Option, + #[structopt(long = "generator-pandoc-filters", number_of_values = 1)] + pub(super) pandoc_filters: Vec, #[structopt(skip)] - pub(crate) inlines: Option>>>, + pub(super) inlines: Option, } diff --git a/src/generator/mod.rs b/src/generator/mod.rs index b4e1c94..3f62779 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -2,11 +2,8 @@ pub(crate) mod config; mod pandoc; use self::config::Config; -//use crate::entities::{ -// Description, DynEntity, EntitiesManager, EntitiesManagerComponents, Usr, -//}; use self::pandoc::into_pandoc; -use crate::types::Entity; +use crate::types::*; use anyhow::{ensure, Context, Result}; use rayon::Scope; @@ -18,7 +15,11 @@ use std::path::Path; const DEFAULT_CSS: &[u8] = include_bytes!("../../res/style.css"); -pub(crate) fn generate(base_dir: &Path, entities: BTreeMap, config: &Config) -> Result<()> { +pub(crate) fn generate( + base_dir: &Path, + entities: BTreeMap, + config: &Config, +) -> Result<()> { let md_output_dir = base_dir.join("markdown"); let html_output_dir = base_dir.join("html"); @@ -54,7 +55,7 @@ pub(crate) fn generate(base_dir: &Path, entities: BTreeMap, conf } fn generate_recursively<'a>( - id: String, + id: EntityId, entity: Entity, pool: &Scope<'a>, md_output_dir: &'a Path, @@ -63,7 +64,7 @@ fn generate_recursively<'a>( config: &'a Config, ) { pool.spawn(move |pool| { - trace!("Trying to generate {}", id); + trace!("Trying to generate {}", id.0); let leftovers = generate_single( &id, @@ -90,18 +91,18 @@ fn generate_recursively<'a>( } fn generate_single( - id: &str, + id: &EntityId, entity: Entity, md_output_dir: impl AsRef, html_output_dir: impl AsRef, css_path: impl AsRef, - config: &Config -) -> Result> { + config: &Config, +) -> Result> { use std::io::prelude::*; use std::process::{Command, Stdio}; - let md_output_file = md_output_dir.as_ref().join(id); - let html_output_file = html_output_dir.as_ref().join(id); + let md_output_file = md_output_dir.as_ref().join(&id.0); + let html_output_file = html_output_dir.as_ref().join(&id.0); let mut command = Command::new("pandoc"); @@ -111,7 +112,10 @@ fn generate_single( .stderr(Stdio::piped()) .args(&[ "--from=json", - "--to=markdown", + // standalone keeps the sent metadat + "--standalone", + "--to", + &config.from, "--output", md_output_file .to_str() @@ -165,8 +169,10 @@ fn generate_single( .stdout(Stdio::piped()) .stderr(Stdio::piped()) .args(&[ - "--from=markdown-raw_tex", - "--to=html", + "--from", + &config.from, + "--to", + &config.to, "--css", css_path .as_ref() @@ -183,6 +189,10 @@ fn generate_single( .context("Entity name is not valid UTF-8")?, ]); + for filter in &config.pandoc_filters { + command.args(&["--filter", filter]); + } + let command_str = format!("{:?}", command); debug!("Launching command: {}", command_str); @@ -190,6 +200,8 @@ fn generate_single( .output() .context("Failed to execute Pandoc command")?; + std::io::stderr().lock().write_all(&output.stderr)?; + ensure!( output.status.success(), CommandError { diff --git a/src/generator/pandoc.rs b/src/generator/pandoc.rs index 1753d34..035acab 100644 --- a/src/generator/pandoc.rs +++ b/src/generator/pandoc.rs @@ -1,14 +1,11 @@ use super::config::Config; -use crate::types::Entity; +use crate::types::*; use pandoc_types::definition::{Attr, Block, Inline, Meta, MetaValue, Pandoc}; use std::collections::BTreeMap; -pub(crate) fn into_pandoc( - entity: Entity, - config: &Config, -) -> (Pandoc, BTreeMap) { +pub(crate) fn into_pandoc(entity: Entity, config: &Config) -> (Pandoc, BTreeMap) { let mut meta = Meta::null(); let title = vec![Inline::Code(Attr::null(), entity.name.clone())]; @@ -57,7 +54,7 @@ pub(crate) fn into_pandoc( content.push(Block::Header( 2, Attr::null(), - vec![Inline::Str(String::from(section_name))], + vec![Inline::Str(section_name.0.clone())], )); content.push(members_list); @@ -71,7 +68,7 @@ pub(crate) fn into_pandoc( content.push(Block::Header( 2, Attr::null(), - vec![Inline::Str(section_name.clone())], + vec![Inline::Str(section_name.0.clone())], )); content.push(members_list); @@ -79,7 +76,7 @@ pub(crate) fn into_pandoc( embedded_documentation.push(Block::Header( 2, Attr::null(), - vec![Inline::Str(section_name + " Documentation")], + vec![Inline::Str(section_name.0 + " Documentation")], )); for (_id, child) in children { @@ -112,7 +109,7 @@ fn str_block(content: String) -> Block { Block::Plain(vec![Inline::Str(content)]) } -fn entity_link(id: &str, name: String) -> Inline { +fn entity_link(id: &EntityId, name: String) -> Inline { use pandoc_types::definition::Target; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; @@ -135,7 +132,7 @@ fn entity_link(id: &str, name: String) -> Inline { vec![Inline::Code(Attr::null(), name)], Target( once("./") - .chain(utf8_percent_encode(id, FRAGMENT)) + .chain(utf8_percent_encode(&id.0, FRAGMENT)) .collect(), String::new(), ), @@ -147,7 +144,7 @@ fn raw_markdown(text: String) -> Block { Block::RawBlock(Format(String::from("markdown")), text) } -fn member_list<'a>(members: impl IntoIterator) -> Option { +fn member_list<'a>(members: impl IntoIterator) -> Option { let definitions: Vec<(Vec, Vec>)> = members .into_iter() .map(|(id, entity)| { diff --git a/src/main.rs b/src/main.rs index b42d73c..5c613e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod cli; mod config; mod generator; -mod parsing; +mod parser; mod types; #[macro_use] @@ -87,12 +87,17 @@ fn start(codemap: &mut CodeMap) -> Result<()> { //let file = file.clone(); let config = load_effective_config(cli, codemap)?; - let entities = parsing::clang::parse_compile_commands(&config.clang, codemap)?; + let entities = parser::clang::parse_compile_commands(&config.clang, codemap)?; //let manager = parse_file(file, &config.extra_clang_args); let base_output_dir = std::path::Path::new("doc"); generate(&base_output_dir, entities, &config.generator)?; } + Command::Inspect => { + let config = load_effective_config(cli, codemap)?; + let entities = parser::clang::parse_compile_commands(&config.clang, codemap)?; + serde_json::to_writer_pretty(std::io::stdout().lock(), &entities)?; + } Command::Config { command: ConfigCommand::Default, } => { diff --git a/src/parser/clang/config.rs b/src/parser/clang/config.rs new file mode 100644 index 0000000..05fe3ab --- /dev/null +++ b/src/parser/clang/config.rs @@ -0,0 +1,53 @@ +use anyhow::Result; +use serde_derive::{Deserialize, Serialize}; +use structopt::StructOpt; + +use std::path::PathBuf; + +#[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) struct Config { + pub(super) compile_commands_location: PathBuf, + pub(super) extra_args: Vec, +} + +#[derive(Debug, Clone, Default, StructOpt, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) struct ProvidedConfig { + #[structopt(long = "clang-compile-commands-location")] + pub(super) compile_commands_location: Option, + #[structopt(long = "clang-extra-args", number_of_values = 1)] + #[serde(default)] + pub(super) extra_args: Vec, +} + +impl Default for Config { + fn default() -> Self { + Config::from_merge( + ProvidedConfig::default(), + ProvidedConfig::default(), + ) + // Currently errors out only on CLI parse fail with clang's extra args + .unwrap() + } +} + +impl Config { + pub(crate) fn from_merge( + cli: ProvidedConfig, + mut config: ProvidedConfig, + ) -> Result { + let mut extra_args = Vec::new(); + for args in cli.extra_args { + extra_args.append(&mut ::shell_words::split(&args)?); + } + extra_args.append(&mut config.extra_args); + Ok(Self { + compile_commands_location: cli + .compile_commands_location + .or(config.compile_commands_location) + .unwrap_or_else(|| PathBuf::from(r".")), + extra_args, + }) + } +} diff --git a/src/parser/clang/entities.rs b/src/parser/clang/entities.rs new file mode 100644 index 0000000..41fe061 --- /dev/null +++ b/src/parser/clang/entities.rs @@ -0,0 +1,390 @@ +use crate::types::*; + +use clang::{EntityKind, Usr}; +use thiserror::Error; + +use std::collections::BTreeMap; +use std::convert::TryFrom; + +// TODO: factor out hardcoded strings + +#[derive(Debug)] +pub(super) struct Children { + namespaces: Option>>, + variables: Option>>, + structs: Option>>, + functions: Option>>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ClangEntityKind { + Namespace, + Variable(VariableKind), + Struct(StructKind), + Function(FunctionKind), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum VariableKind { + Variable, + Field, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum StructKind { + Struct, + Class, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum FunctionKind { + Function, + Method, +} + +impl ClangEntityKind { + fn to_str_singular(self) -> &'static str { + match self { + ClangEntityKind::Namespace => "namespace", + ClangEntityKind::Variable(VariableKind::Variable) => "variable", + ClangEntityKind::Variable(VariableKind::Field) => "field", + ClangEntityKind::Struct(StructKind::Struct) => "struct", + ClangEntityKind::Struct(StructKind::Class) => "class", + ClangEntityKind::Function(FunctionKind::Function) => "function", + ClangEntityKind::Function(FunctionKind::Method) => "method", + } + } + + fn to_str_plural(self) -> &'static str { + match self { + ClangEntityKind::Namespace => "namespaces", + ClangEntityKind::Variable(VariableKind::Variable) => "variables", + ClangEntityKind::Variable(VariableKind::Field) => "fields", + ClangEntityKind::Struct(StructKind::Struct) => "structs", + ClangEntityKind::Struct(StructKind::Class) => "classes", + ClangEntityKind::Function(FunctionKind::Function) => "functions", + ClangEntityKind::Function(FunctionKind::Method) => "methods", + } + } +} + +#[derive(Debug, Error)] +#[error("Unsupported Clang entity kind: {:?}", _0)] +pub(super) struct TryFromEntityKindError(String); + +impl TryFrom for ClangEntityKind { + type Error = TryFromEntityKindError; + + fn try_from(kind: EntityKind) -> Result { + Ok(match kind { + EntityKind::Namespace => ClangEntityKind::Namespace, + EntityKind::VarDecl => ClangEntityKind::Variable(VariableKind::Variable), + EntityKind::FieldDecl => ClangEntityKind::Variable(VariableKind::Field), + EntityKind::FunctionDecl => ClangEntityKind::Function(FunctionKind::Function), + EntityKind::Method | EntityKind::Constructor => { + ClangEntityKind::Function(FunctionKind::Method) + } + EntityKind::StructDecl => ClangEntityKind::Struct(StructKind::Struct), + EntityKind::ClassDecl => ClangEntityKind::Struct(StructKind::Class), + kind => return Err(TryFromEntityKindError(format!("{:?}", kind))), + }) + } +} + +pub(super) trait ClangEntity { + fn kind(&self) -> ClangEntityKind; + + fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap>> { + None + } + fn get_member_variables(&mut self) -> Option<&mut BTreeMap>> { + None + } + fn get_member_structs(&mut self) -> Option<&mut BTreeMap>> { + None + } + fn get_member_functions(&mut self) -> Option<&mut BTreeMap>> { + None + } + + fn into_children(self) -> Children + where + Self: Sized, + { + Children { + namespaces: None, + variables: None, + structs: None, + functions: None, + } + } +} + +/* +pub(super) trait FromNamespaceParent: ClangEntity + Sized { + fn from_namespace_parent<'a>( + parent: &'a mut Described, + name: &Usr, + ) -> Option<&'a mut Described>; +} +*/ + +pub(super) trait NamespaceParentManipulation { + fn get_members_mut(&mut self) -> Option<&mut BTreeMap>>; +} + +impl NamespaceParentManipulation for U +where + U: ClangEntity, +{ + fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { + self.get_member_namespaces() + } +} + +impl NamespaceParentManipulation for U +where + U: ClangEntity, +{ + fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { + self.get_member_variables() + } +} + +impl NamespaceParentManipulation for U +where + U: ClangEntity, +{ + fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { + self.get_member_functions() + } +} + +impl NamespaceParentManipulation for U +where + U: ClangEntity, +{ + fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { + self.get_member_structs() + } +} + +impl From> for Entity { + fn from(entity: Described) -> Self { + let mut children: BTreeMap> = BTreeMap::new(); + + let kind = EntityKind(String::from(entity.entity.kind().to_str_singular())); + let clang_children = entity.entity.into_children(); + + if let Some(namespaces) = clang_children.namespaces { + for (usr, namespace) in namespaces { + children + .entry(ChildrenKind(String::from( + namespace.entity.kind().to_str_plural(), + ))) + .or_default() + .insert(EntityId(usr.0), namespace.into()); + } + } + + if let Some(variables) = clang_children.variables { + for (usr, variable) in variables { + children + .entry(ChildrenKind(String::from( + variable.entity.kind().to_str_plural(), + ))) + .or_default() + .insert(EntityId(usr.0), variable.into()); + } + } + + if let Some(structs) = clang_children.structs { + for (usr, r#struct) in structs { + children + .entry(ChildrenKind(String::from( + r#struct.entity.kind().to_str_plural(), + ))) + .or_default() + .insert(EntityId(usr.0), r#struct.into()); + } + } + + if let Some(functions) = clang_children.functions { + for (usr, function) in functions { + children + .entry(ChildrenKind(String::from( + function.entity.kind().to_str_plural(), + ))) + .or_default() + .insert(EntityId(usr.0), function.into()); + } + } + + Entity { + name: entity.description.name, + language: Language::Clang, + kind, + brief_description: entity.description.brief, + documentation: entity.description.detailed, + children, + } + } +} + +#[derive(Debug, Clone, Default)] +pub(super) struct Description { + pub(super) name: String, + pub(super) brief: String, + pub(super) detailed: String, +} + +#[derive(Debug, Clone)] +pub(super) struct Described { + pub(super) description: Description, + pub(super) entity: T, +} + +#[derive(Debug, Clone)] +pub(super) struct Namespace { + pub(super) member_namespaces: BTreeMap>, + pub(super) member_variables: BTreeMap>, + pub(super) member_structs: BTreeMap>, + pub(super) member_functions: BTreeMap>, +} + +impl ClangEntity for Namespace { + fn kind(&self) -> ClangEntityKind { + ClangEntityKind::Namespace + } + + fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_namespaces) + } + fn get_member_variables(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_variables) + } + fn get_member_structs(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_structs) + } + fn get_member_functions(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_functions) + } + + fn into_children(self) -> Children { + Children { + namespaces: Some(self.member_namespaces), + variables: Some(self.member_variables), + structs: Some(self.member_structs), + functions: Some(self.member_functions), + } + } +} + +/* +impl FromNamespaceParent for Namespace { + fn from_namespace_parent<'a>( + parent: &'a mut Described, + name: &Usr, + ) -> Option<&'a mut Described> { + parent.entity.member_namespaces.get_mut(name) + } +} +*/ + +#[derive(Debug, Clone)] +pub(super) struct Variable { + pub(super) kind: VariableKind, + pub(super) r#type: String, +} + +impl ClangEntity for Variable { + fn kind(&self) -> ClangEntityKind { + ClangEntityKind::Variable(self.kind) + } +} + +/* +impl FromNamespaceParent for Variable { + fn from_namespace_parent<'a>( + parent: &'a mut Described, + name: &Usr, + ) -> Option<&'a mut Described> { + parent.entity.member_variables.get_mut(name) + } +} +*/ + +#[derive(Debug, Clone)] +pub(super) struct Struct { + //pub(super) member_types: Vec, + pub(super) kind: StructKind, + pub(super) member_variables: BTreeMap>, + pub(super) member_structs: BTreeMap>, + pub(super) member_functions: BTreeMap>, +} + +impl ClangEntity for Struct { + fn kind(&self) -> ClangEntityKind { + ClangEntityKind::Struct(self.kind) + } + + fn get_member_variables(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_variables) + } + fn get_member_structs(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_structs) + } + fn get_member_functions(&mut self) -> Option<&mut BTreeMap>> { + Some(&mut self.member_functions) + } + + fn into_children(self) -> Children { + Children { + namespaces: None, + variables: Some(self.member_variables), + structs: Some(self.member_structs), + functions: Some(self.member_functions), + } + } +} + +/* +impl FromNamespaceParent for Struct { + fn from_namespace_parent<'a>( + parent: &'a mut Described, + name: &Usr, + ) -> Option<&'a mut Described> { + parent.entity.member_structs.get_mut(name) + } +} +*/ + +#[derive(Debug, Clone)] +pub(super) struct Function { + pub(super) kind: FunctionKind, + pub(super) arguments: Vec, + pub(super) return_type: String, +} + +impl ClangEntity for Function { + fn kind(&self) -> ClangEntityKind { + ClangEntityKind::Function(self.kind) + } +} + +/* +impl FromNamespaceParent for Function { + fn from_namespace_parent<'a>( + parent: &'a mut Described, + name: &Usr, + ) -> Option<&'a mut Described> { + parent.entity.member_functions.get_mut(name) + } +} +*/ + +#[derive(Debug, Clone)] +pub(super) struct FunctionArgument { + pub(super) name: String, + pub(super) r#type: String, +} diff --git a/src/parser/clang/mod.rs b/src/parser/clang/mod.rs new file mode 100644 index 0000000..047c49e --- /dev/null +++ b/src/parser/clang/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod config; +mod entities; +mod parsing; + +pub(crate) use parsing::parse_compile_commands; diff --git a/src/parser/clang/parsing.rs b/src/parser/clang/parsing.rs new file mode 100644 index 0000000..a654681 --- /dev/null +++ b/src/parser/clang/parsing.rs @@ -0,0 +1,764 @@ +use super::config::Config; +use super::entities::*; +use crate::types::*; + +use anyhow::{anyhow, Context, Error, Result}; +use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr}; +use codemap::CodeMap; +use thiserror::Error; + +use std::collections::BTreeMap; +use std::convert::{TryFrom, TryInto}; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Default)] +struct TopLevel { + namespaces: BTreeMap>, + variables: BTreeMap>, + structs: BTreeMap>, + functions: BTreeMap>, +} + +/* +enum TopLevelEntry<'a, T> { + Vacant { + parent: &'a mut Described, + }, + /// Vacant, but no semantic parent + TopLevel, + Occupied { + entity: &'a mut Described, + }, + Error, +} +*/ + +impl TopLevel { + // Somehow has a lifetime issue I can't get my head around + /* + fn entry<'a, T>(&'a mut self, path: &::clang::Entity) -> Result> + where + T: ClangEntity + FromNamespaceParent + FromTopLevel, + { + let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; + if let Some(parent_path) = parent(&path) { + let parent_entry = self.entry::(&parent_path)?; + if let TopLevelEntry::Occupied { + entity: namespace_parent, + } = parent_entry + { + Ok(match T::from_namespace_parent(namespace_parent, &usr) { + None => TopLevelEntry::Vacant { + parent: namespace_parent, + }, + Some(entity) => TopLevelEntry::Occupied { entity }, + }) + } else { + panic!("Wut"); + } + } else { + Ok(match T::from_toplevel(self, &usr) { + Some(entity) => TopLevelEntry::Occupied { entity }, + None => TopLevelEntry::TopLevel, + }) + } + } + */ + + fn get_entity_mut(&mut self, path: clang::Entity) -> Option<&mut dyn ClangEntity> { + let usr = path.get_usr()?; + if let Some(parent_path) = parent(path) { + let parent = self.get_entity_mut(parent_path)?; + Some(match path.get_kind().try_into().ok()? { + ClangEntityKind::Namespace => { + &mut parent.get_member_namespaces()?.get_mut(&usr)?.entity + } + ClangEntityKind::Variable(_) => { + &mut parent.get_member_variables()?.get_mut(&usr)?.entity + } + ClangEntityKind::Function(_) => { + &mut parent.get_member_functions()?.get_mut(&usr)?.entity + } + ClangEntityKind::Struct(_) => { + &mut parent.get_member_structs()?.get_mut(&usr)?.entity + } + }) + } else { + Some(match path.get_kind().try_into().ok()? { + ClangEntityKind::Namespace => &mut self.namespaces.get_mut(&usr)?.entity, + ClangEntityKind::Variable(_) => &mut self.variables.get_mut(&usr)?.entity, + ClangEntityKind::Struct(_) => &mut self.structs.get_mut(&usr)?.entity, + ClangEntityKind::Function(_) => &mut self.functions.get_mut(&usr)?.entity, + }) + } + } + + fn get_namespace_mut(&mut self, path: clang::Entity) -> Option<&mut Described> { + let usr = path.get_usr()?; + + if let Some(parent_path) = parent(path) { + let parent = self.get_entity_mut(parent_path)?; + parent.get_member_namespaces()?.get_mut(&usr) + } else { + self.namespaces.get_mut(&usr) + } + } + + fn insert(&mut self, path: clang::Entity, entity: Described) -> Result<()> + where + T: ClangEntity + std::fmt::Debug, + Self: TopLevelManipulation, + Namespace: NamespaceParentManipulation, + { + let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; + if let Some(parent_path) = parent(path) { + if let Some(parent_namespace) = self.get_namespace_mut(parent_path) { + parent_namespace + .entity + .get_members_mut() + // Namespace should be able to contain every kind of entity + .unwrap() + .insert(usr, entity); + Ok(()) + } else { + Err(anyhow!("has parent but no parent in tree")) + } + } else { + self.insert_toplevel(usr, entity); + Ok(()) + } + } +} + +// Like .get_semantic_parent(), but return none if the parent is the translation unit +fn parent(libclang_entity: clang::Entity) -> Option { + match libclang_entity.get_semantic_parent() { + Some(parent) => { + if parent.get_kind() != clang::EntityKind::TranslationUnit { + Some(parent) + } else { + None + } + } + None => { + warn!("get_semantic_parent() returned None"); + None + } + } +} + +trait TopLevelManipulation { + fn insert_toplevel(&mut self, usr: Usr, entity: Described); +} + +impl TopLevelManipulation for TopLevel { + fn insert_toplevel(&mut self, usr: Usr, entity: Described) { + self.namespaces.insert(usr, entity); + } +} + +impl TopLevelManipulation for TopLevel { + fn insert_toplevel(&mut self, usr: Usr, entity: Described) { + self.variables.insert(usr, entity); + } +} + +impl TopLevelManipulation for TopLevel { + fn insert_toplevel(&mut self, usr: Usr, entity: Described) { + self.functions.insert(usr, entity); + } +} + +impl TopLevelManipulation for TopLevel { + fn insert_toplevel(&mut self, usr: Usr, entity: Described) { + self.structs.insert(usr, entity); + } +} + +/* +trait FromTopLevel: ClangEntity + Sized { + fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described>; +} + +impl FromTopLevel for Namespace { + fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { + toplevel.namespaces.get_mut(usr) + } +} + +impl FromTopLevel for Variable { + fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { + toplevel.variables.get_mut(usr) + } +} + +impl FromTopLevel for Function { + fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { + toplevel.functions.get_mut(usr) + } +} + +impl FromTopLevel for Struct { + fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { + toplevel.structs.get_mut(usr) + } +} +*/ + +pub(crate) fn parse_compile_commands( + config: &Config, + codemap: &mut CodeMap, +) -> Result> { + let clang = Clang::new().unwrap(); + let index = Index::new( + &clang, /* exclude from pch = */ false, /* print diagnostics = */ false, + ); + + debug!("Extra libclang argument: {:?}", config.extra_args); + + debug!( + "Loading compile commands from: {:?}", + config.compile_commands_location + ); + let database = + CompilationDatabase::from_directory(&config.compile_commands_location).map_err(|()| { + CompileCommandsLoadError { + path: config.compile_commands_location.clone(), + } + })?; + + let toplevel_directory = std::env::current_dir().context("Cannot read current directory")?; + + let mut entities = TopLevel::default(); + + for command in database.get_all_compile_commands().get_commands() { + let directory = command.get_directory(); + trace!("Changing directory to: {:?}", directory); + std::env::set_current_dir(&directory) + .with_context(|| format!("Cannot change current directory to: {:?}", directory))?; + + let filename = command.get_filename(); + + let file_map = codemap.add_file( + filename + .to_str() + .context("File is not valid UTF-8")? + .to_owned(), + std::fs::read_to_string(&filename) + .with_context(|| format!("Cannot readfile: {:?}", filename))?, + ); + + trace!("Parsing file: {:?}", filename); + // The file name is passed as an argument in the compile commands + let mut parser = index.parser(""); + parser.skip_function_bodies(true); + + let mut clang_arguments = command.get_arguments(); + clang_arguments.extend_from_slice(&config.extra_args); + trace!("Parsing with libclang arguments: {:?}", clang_arguments); + parser.arguments(&clang_arguments); + + parse_unit( + &parser + .parse() + .with_context(|| format!("Could not parse file: {:?}", filename))?, + &mut entities, + &toplevel_directory, + file_map.span, + &codemap, + )?; + + trace!("Changing directory to: {:?}", directory); + std::env::set_current_dir(&toplevel_directory).with_context(|| { + format!( + "Cannot change current directory to: {:?}", + toplevel_directory + ) + })?; + } + + let normalized_entities = entities + .namespaces + .into_iter() + .map(|(usr, entity)| (EntityId(usr.0), entity.into())) + .chain( + entities + .variables + .into_iter() + .map(|(usr, entity)| (EntityId(usr.0), entity.into())), + ) + .chain( + entities + .structs + .into_iter() + .map(|(usr, entity)| (EntityId(usr.0), entity.into())), + ) + .chain( + entities + .functions + .into_iter() + .map(|(usr, entity)| (EntityId(usr.0), entity.into())), + ) + .collect(); + + Ok(normalized_entities) +} + +/* +pub(crate) fn parse_file(path: T, extra_args: &[S]) -> EntitiesManager +where + T: Into, + T: AsRef, + T: ToString, + S: AsRef, + S: std::fmt::Debug, +{ + let mut codemap = CodeMap::new(); + let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap()); + let file_span = file_map.span; + + let clang = Clang::new().unwrap(); + let index = Index::new(&clang, true, false); + let mut parser = index.parser(path); + parser.skip_function_bodies(true); + + parser.arguments(&extra_args); + + if log_enabled!(log::Level::Debug) { + for extra_arg in extra_args { + debug!("Extra libclang argument: {:?}", extra_arg); + } + } + + let trans_unit = parser.parse().unwrap(); + let mut entities = EntitiesManager::new(); + + parse_unit( + &trans_unit, + &mut entities, + &std::env::current_dir().unwrap(), + file_span, + &codemap, + ) + .unwrap(); + + entities +} +*/ + +fn parse_unit( + trans_unit: &TranslationUnit, + entities: &mut TopLevel, + base_dir: impl AsRef, + file_span: codemap::Span, + codemap: &CodeMap, +) -> Result<()> { + trans_unit.get_entity().visit_children(|entity, _parent| { + if is_in_system_header(entity, &base_dir) { + trace!( + "Entity is in system header, skipping: USR = {:?}", + entity.get_display_name() + ); + return clang::EntityVisitResult::Continue; + } + + // TODO: wrap this callback in another function so that we can use the + // "?" operator instead of all these `match`es + let usr = match entity.get_usr() { + Some(usr) => usr, + None => return clang::EntityVisitResult::Continue, + }; + trace!("Entity with USR = {:?}", usr); + debug!("Parsing toplevel entity: {:?}", entity); + + add_entity(entity, entities, file_span, codemap) + }); + + /* + use codemap_diagnostic::{ColorConfig, Emitter}; + + let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); + + for diagnostic in trans_unit.get_diagnostics().iter() { + let main_diag = match clang_diag_to_codemap_diag(&diagnostic, file_span) { + Some(diag) => diag, + None => continue, + }; + + let sub_diags = diagnostic + .get_children() + .into_iter() + .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, file_span)); + + let fix_it_diags = diagnostic + .get_fix_its() + .into_iter() + .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, file_span)); + + emitter.emit( + &std::iter::once(main_diag) + .chain(sub_diags) + .chain(fix_it_diags) + .collect::>(), + ); + } + */ + + Ok(()) +} + +fn is_in_system_header(entity: clang::Entity, base_dir: impl AsRef) -> bool { + if entity.is_in_system_header() { + true + } else if let Some(location) = entity.get_location() { + if let Some(file) = location.get_file_location().file { + !file + .get_path() + .canonicalize() + .unwrap() + .starts_with(base_dir) + } else { + // Not defined in a file? probably shouldn't document + true + } + } else { + // Not defined anywhere? probably shouldn't document + true + } +} + +// Entries encountered in the toplevel lexical context +fn add_entity( + libclang_entity: clang::Entity, + toplevel: &mut TopLevel, + file_span: codemap::Span, + codemap: &CodeMap, +) -> clang::EntityVisitResult { + if libclang_entity.get_usr().is_none() { + return clang::EntityVisitResult::Continue; + }; + + let kind = match ClangEntityKind::try_from(libclang_entity.get_kind()) { + Ok(kind) => kind, + Err(err) => { + use codemap_diagnostic::{ + ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle, + }; + let spans = if let Some(range) = libclang_entity.get_range() { + // TODO: add every file parsed in this translation unit to the + // codemap, so we can properly report errors + if !range.is_in_main_file() { + vec![] + } else { + let begin = range.get_start().get_file_location().offset as u64; + let end = range.get_end().get_file_location().offset as u64; + + vec![SpanLabel { + span: file_span.subspan(begin, end), + label: None, + style: SpanStyle::Primary, + }] + } + } else { + vec![] + }; + + let diag = Diagnostic { + level: Level::Warning, + message: format!("{}", err), + code: None, + spans, + }; + + let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(codemap)); + emitter.emit(&[diag]); + + return clang::EntityVisitResult::Continue; + } + }; + + if let Some(in_tree_entity) = toplevel.get_entity_mut(libclang_entity) { + // if current.has_documentation && !tree.has_documentation { + // append_documentation + // } + } else if libclang_entity.is_definition() { + // TODO: This probably means that you can't put documentation in forward declarations. + // TODO: Ad-hoc toplevel functions are not documented + // + // This seems restrictive, but since there can be multiple declarations but only one definition, + // you should probably put your documentation on the definition anyway? + // + // Also, skipping forward declarations allows us to not have to insert, then update the tree + // when we see the definition. + + let result = match kind { + ClangEntityKind::Namespace => Described::::try_from(libclang_entity) + .and_then(|namespace| toplevel.insert(libclang_entity, namespace)), + ClangEntityKind::Variable(_) => Described::::try_from(libclang_entity) + .and_then(|variable| toplevel.insert(libclang_entity, variable)), + ClangEntityKind::Struct(_) => Described::::try_from(libclang_entity) + .and_then(|r#struct| toplevel.insert(libclang_entity, r#struct)), + ClangEntityKind::Function(_) => Described::::try_from(libclang_entity) + .and_then(|function| toplevel.insert(libclang_entity, function)), + }; + // TODO: check result + } + + if kind == ClangEntityKind::Namespace { + // Recurse here since namespace definitions are allowed to change between translation units. + ::clang::EntityVisitResult::Recurse + } else { + ::clang::EntityVisitResult::Continue + } +} + +impl<'a, T> TryFrom> for Described +where + T: TryFrom, Error = Error>, +{ + type Error = Error; + + fn try_from(entity: clang::Entity<'a>) -> Result { + Ok(Described:: { + description: get_description(entity)?, + entity: T::try_from(entity)?, + }) + } +} + +impl<'a> TryFrom> for Namespace { + type Error = Error; + + fn try_from(entity: clang::Entity) -> Result { + match entity.get_kind().try_into() { + Ok(ClangEntityKind::Namespace) => {} + _ => panic!("Trying to parse a non-variable into a variable"), + } + debug!("Parsing Namespace: {:?}", entity); + + // Do not recurse here, but recurse in the main loop, since namespace + // definitions is allowed to change between translation units + + Ok(Namespace { + member_namespaces: Default::default(), + member_variables: Default::default(), + member_structs: Default::default(), + member_functions: Default::default(), + }) + } +} + +impl<'a> TryFrom> for Variable { + type Error = Error; + + fn try_from(entity: clang::Entity) -> Result { + let variable_kind; + match entity.get_kind().try_into() { + Ok(ClangEntityKind::Variable(kind)) => { + variable_kind = kind; + } + _ => panic!("Trying to parse a non-variable into a variable"), + } + debug!("Parsing Variable: {:?}", entity); + + let r#type = entity.get_type().unwrap().get_display_name(); + trace!("Variable has type: {:?}", r#type); + + Ok(Variable { + r#type, + kind: variable_kind, + }) + } +} + +impl<'a> TryFrom> for Struct { + type Error = Error; + + fn try_from(entity: clang::Entity) -> Result { + let struct_kind; + match entity.get_kind().try_into() { + Ok(ClangEntityKind::Struct(kind)) => { + struct_kind = kind; + } + _ => panic!("Trying to parse a non-class into a class"), + } + debug!("Parsing Struct: {:?}", entity); + + let mut member_variables = BTreeMap::new(); + let mut member_structs = BTreeMap::new(); + let mut member_functions = BTreeMap::new(); + + for child in entity.get_children() { + trace!("Struct has child: {:?}", child); + + match child.get_kind().try_into() { + Ok(ClangEntityKind::Variable(_)) => { + let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; + member_variables.insert(child_usr, Described::::try_from(child)?); + } + Ok(ClangEntityKind::Struct(_)) => { + let child_usr: Usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; + member_structs.insert(child_usr, Described::::try_from(child)?); + } + Ok(ClangEntityKind::Function(_)) => { + let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; + member_functions.insert(child_usr, Described::::try_from(child)?); + } + _ => trace!("Skipping child"), + } + } + + Ok(Struct { + kind: struct_kind, + member_functions, + member_structs, + member_variables, + }) + } +} + +impl<'a> TryFrom> for Function { + type Error = Error; + + fn try_from(entity: clang::Entity) -> Result { + let function_kind; + match entity.get_kind().try_into() { + Ok(ClangEntityKind::Function(kind)) => { + function_kind = kind; + } + _ => panic!("Trying to parse a non-function into a function"), + } + debug!("Parsing Function: {:?}", entity); + + let return_type = entity.get_result_type().unwrap().get_display_name(); + trace!("Function has return type: {:?}", return_type); + let arguments = entity + .get_arguments() + .unwrap() + .into_iter() + .map(|arg| { + let name = arg + .get_display_name() + .unwrap_or_else(|| String::from("unnamed")); + let r#type = arg.get_type().unwrap().get_display_name(); + trace!("Function has argument {:?} of type {:?}", name, r#type); + FunctionArgument { name, r#type } + }) + .collect(); + + Ok(Function { + kind: function_kind, + arguments, + return_type, + }) + } +} + +fn get_description(entity: clang::Entity) -> Result { + let name = entity + .get_display_name() + .ok_or_else(|| anyhow!("Entity has no name: {:?}", entity))?; + + // TODO: is that the best? + if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) { + Ok(Description { + name, + brief, + detailed: parse_comment(comment), + }) + } else { + Ok(Description { + name, + brief: String::new(), + detailed: String::new(), + }) + } +} + +pub fn parse_comment(raw: String) -> String { + #[derive(Debug)] + enum CommentStyle { + // Comments of type `/**` or `/*!` + Starred, + // Comments of type `///` + SingleLine, + } + + let mut chars = raw.chars(); + let style = match &chars.as_str()[..3] { + "/*!" | "/**" => CommentStyle::Starred, + "///" => CommentStyle::SingleLine, + _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"), + }; + + chars.nth(2); + + let mut result = String::new(); + + 'parse_loop: loop { + let maybe_space = chars.next(); + let mut empty_line = false; + match maybe_space { + // TODO: Warn on empty comments + None => break, + Some(' ') => {} + Some('\n') => { + empty_line = true; + result.push('\n'); + } + Some(ch) => result.push(ch), + } + + if !empty_line { + let rest = chars.as_str(); + match rest.find('\n') { + None => { + result.push_str(rest); + break; + } + Some(position) => { + result.push_str(&rest[..=position]); + chars.nth(position); + } + } + } + + // Beginning of the line + let first_non_ws_ch = 'ws_loop: loop { + let maybe_whitespace = chars.next(); + match maybe_whitespace { + None => break 'parse_loop, + Some(ch) if ch.is_whitespace() => continue, + Some(ch) => break 'ws_loop ch, + } + }; + + match style { + CommentStyle::Starred if first_non_ws_ch == '*' => { + if &chars.as_str()[..1] == "/" { + break; + } + } + CommentStyle::Starred => result.push(first_non_ws_ch), + CommentStyle::SingleLine => { + assert!(first_non_ws_ch == '/'); + let rest = chars.as_str(); + if &rest[..2] == "//" { + chars.nth(1); + } else if &rest[..1] == "/" { + chars.nth(0); + } else { + panic!("Could not parse comment"); + } + } + } + } + + result +} + +#[derive(Debug, Clone, Error)] +#[error("Failed to load 'compile_commands.json' at path: {:?}", path)] +pub(crate) struct CompileCommandsLoadError { + path: PathBuf, +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..0736eee --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1 @@ +pub(crate) mod clang; diff --git a/src/parsing/clang/config.rs b/src/parsing/clang/config.rs deleted file mode 100644 index cb05876..0000000 --- a/src/parsing/clang/config.rs +++ /dev/null @@ -1,53 +0,0 @@ -use anyhow::Result; -use serde_derive::{Deserialize, Serialize}; -use structopt::StructOpt; - -use std::path::PathBuf; - -#[derive(Debug, Clone, StructOpt, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub(crate) struct Config { - pub(crate) compile_commands_location: PathBuf, - pub(crate) extra_args: Vec, -} - -#[derive(Debug, Clone, Default, StructOpt, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub(crate) struct ProvidedConfig { - #[structopt(long = "clang-compile-commands-location")] - pub(crate) compile_commands_location: Option, - #[structopt(long = "clang-extra-args", number_of_values = 1)] - #[serde(default)] - pub(crate) extra_args: Vec, -} - -impl Default for Config { - fn default() -> Self { - Config::from_merge( - ProvidedConfig::default(), - ProvidedConfig::default(), - ) - // Currently errors out only on CLI parse fail with clang's extra args - .unwrap() - } -} - -impl Config { - pub(crate) fn from_merge( - cli: ProvidedConfig, - mut config: ProvidedConfig, - ) -> Result { - let mut extra_args = Vec::new(); - for args in cli.extra_args { - extra_args.append(&mut ::shell_words::split(&args)?); - } - extra_args.append(&mut config.extra_args); - Ok(Self { - compile_commands_location: cli - .compile_commands_location - .or(config.compile_commands_location) - .unwrap_or_else(|| PathBuf::from(r".")), - extra_args, - }) - } -} diff --git a/src/parsing/clang/entities.rs b/src/parsing/clang/entities.rs deleted file mode 100644 index a8062bb..0000000 --- a/src/parsing/clang/entities.rs +++ /dev/null @@ -1,369 +0,0 @@ -use crate::types::Entity; - -use clang::{EntityKind, Usr}; -use thiserror::Error; - -use std::collections::BTreeMap; -use std::convert::TryFrom; - -// TODO: factor out hardcoded strings - -#[derive(Debug)] -pub(super) struct Children { - namespaces: Option>>, - variables: Option>>, - structs: Option>>, - functions: Option>>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum ClangEntityKind { - Namespace, - Variable(VariableKind), - Struct(StructKind), - Function(FunctionKind), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum VariableKind { - Variable, - Field, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum StructKind { - Struct, - Class, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum FunctionKind { - Function, - Method, -} - -#[derive(Debug, Error)] -#[error("Unsupported Clang entity kind: {:?}", _0)] -pub(super) struct TryFromEntityKindError(String); - -impl TryFrom for ClangEntityKind { - type Error = TryFromEntityKindError; - - fn try_from(kind: EntityKind) -> Result { - Ok(match kind { - EntityKind::Namespace => ClangEntityKind::Namespace, - EntityKind::VarDecl => ClangEntityKind::Variable(VariableKind::Variable), - EntityKind::FieldDecl => ClangEntityKind::Variable(VariableKind::Field), - EntityKind::FunctionDecl => ClangEntityKind::Function(FunctionKind::Function), - EntityKind::Method | EntityKind::Constructor => { - ClangEntityKind::Function(FunctionKind::Method) - } - EntityKind::StructDecl => ClangEntityKind::Struct(StructKind::Struct), - EntityKind::ClassDecl => ClangEntityKind::Struct(StructKind::Class), - kind => return Err(TryFromEntityKindError(format!("{:?}", kind))), - }) - } -} - -pub(super) trait ClangEntity { - fn kind(&self) -> &'static str; - - fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap>> { - None - } - fn get_member_variables(&mut self) -> Option<&mut BTreeMap>> { - None - } - fn get_member_structs(&mut self) -> Option<&mut BTreeMap>> { - None - } - fn get_member_functions(&mut self) -> Option<&mut BTreeMap>> { - None - } - - fn into_children(self) -> Children - where - Self: Sized, - { - Children { - namespaces: None, - variables: None, - structs: None, - functions: None, - } - } -} - -/* -pub(super) trait FromNamespaceParent: ClangEntity + Sized { - fn from_namespace_parent<'a>( - parent: &'a mut Described, - name: &Usr, - ) -> Option<&'a mut Described>; -} -*/ - -pub(super) trait NamespaceParentManipulation { - fn get_members_mut(&mut self) -> Option<&mut BTreeMap>>; -} - -impl NamespaceParentManipulation for U -where - U: ClangEntity, -{ - fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { - self.get_member_namespaces() - } -} - -impl NamespaceParentManipulation for U -where - U: ClangEntity, -{ - fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { - self.get_member_variables() - } -} - -impl NamespaceParentManipulation for U -where - U: ClangEntity, -{ - fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { - self.get_member_functions() - } -} - -impl NamespaceParentManipulation for U -where - U: ClangEntity, -{ - fn get_members_mut(&mut self) -> Option<&mut BTreeMap>> { - self.get_member_structs() - } -} - -impl From> for Entity { - fn from(entity: Described) -> Self { - let mut children = BTreeMap::new(); - - let kind = String::from(entity.entity.kind()); - let clang_children = entity.entity.into_children(); - - if let Some(namespaces) = clang_children.namespaces { - children.insert( - String::from("namespace"), - namespaces - .into_iter() - .map(|(usr, namespace)| (usr.0, namespace.into())) - .collect(), - ); - } - - if let Some(variables) = clang_children.variables { - children.insert( - String::from("variable"), - variables - .into_iter() - .map(|(usr, variable)| (usr.0, variable.into())) - .collect(), - ); - } - - if let Some(structs) = clang_children.structs { - children.insert( - String::from("struct"), - structs - .into_iter() - .map(|(usr, the_struct)| (usr.0, the_struct.into())) - .collect(), - ); - } - - if let Some(functions) = clang_children.functions { - children.insert( - String::from("function"), - functions - .into_iter() - .map(|(usr, function)| (usr.0, function.into())) - .collect(), - ); - } - - Entity { - name: entity.description.name, - language: String::from("clang"), - kind, - brief_description: entity.description.brief, - documentation: entity.description.detailed, - children, - } - } -} - -#[derive(Debug, Clone, Default)] -pub(super) struct Description { - pub(super) name: String, - pub(super) brief: String, - pub(super) detailed: String, -} - -#[derive(Debug, Clone)] -pub(super) struct Described { - pub(super) description: Description, - pub(super) entity: T, -} - -#[derive(Debug, Clone)] -pub(super) struct Namespace { - pub(super) member_namespaces: BTreeMap>, - pub(super) member_variables: BTreeMap>, - pub(super) member_structs: BTreeMap>, - pub(super) member_functions: BTreeMap>, -} - -impl ClangEntity for Namespace { - fn kind(&self) -> &'static str { - "namespace" - } - - fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_namespaces) - } - fn get_member_variables(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_variables) - } - fn get_member_structs(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_structs) - } - fn get_member_functions(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_functions) - } - - fn into_children(self) -> Children { - Children { - namespaces: Some(self.member_namespaces), - variables: Some(self.member_variables), - structs: Some(self.member_structs), - functions: Some(self.member_functions), - } - } -} - -/* -impl FromNamespaceParent for Namespace { - fn from_namespace_parent<'a>( - parent: &'a mut Described, - name: &Usr, - ) -> Option<&'a mut Described> { - parent.entity.member_namespaces.get_mut(name) - } -} -*/ - -#[derive(Debug, Clone)] -pub(super) struct Variable { - pub(super) kind: VariableKind, - pub(super) r#type: String, -} - -impl ClangEntity for Variable { - fn kind(&self) -> &'static str { - match self.kind { - VariableKind::Variable => "variable", - VariableKind::Field => "field", - } - } -} - -/* -impl FromNamespaceParent for Variable { - fn from_namespace_parent<'a>( - parent: &'a mut Described, - name: &Usr, - ) -> Option<&'a mut Described> { - parent.entity.member_variables.get_mut(name) - } -} -*/ - -#[derive(Debug, Clone)] -pub(super) struct Struct { - //pub(super) member_types: Vec, - pub(super) kind: StructKind, - pub(super) member_variables: BTreeMap>, - pub(super) member_structs: BTreeMap>, - pub(super) member_functions: BTreeMap>, -} - -impl ClangEntity for Struct { - fn kind(&self) -> &'static str { - match self.kind { - StructKind::Struct => "struct", - StructKind::Class => "class", - } - } - - fn get_member_variables(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_variables) - } - fn get_member_structs(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_structs) - } - fn get_member_functions(&mut self) -> Option<&mut BTreeMap>> { - Some(&mut self.member_functions) - } - - fn into_children(self) -> Children { - Children { - namespaces: None, - variables: Some(self.member_variables), - structs: Some(self.member_structs), - functions: Some(self.member_functions), - } - } -} - -/* -impl FromNamespaceParent for Struct { - fn from_namespace_parent<'a>( - parent: &'a mut Described, - name: &Usr, - ) -> Option<&'a mut Described> { - parent.entity.member_structs.get_mut(name) - } -} -*/ - -#[derive(Debug, Clone)] -pub(super) struct Function { - pub(super) kind: FunctionKind, - pub(super) arguments: Vec, - pub(super) return_type: String, -} - -impl ClangEntity for Function { - fn kind(&self) -> &'static str { - match self.kind { - FunctionKind::Function => "function", - FunctionKind::Method => "method", - } - } -} - -/* -impl FromNamespaceParent for Function { - fn from_namespace_parent<'a>( - parent: &'a mut Described, - name: &Usr, - ) -> Option<&'a mut Described> { - parent.entity.member_functions.get_mut(name) - } -} -*/ - -#[derive(Debug, Clone)] -pub(super) struct FunctionArgument { - pub(super) name: String, - pub(super) r#type: String, -} diff --git a/src/parsing/clang/mod.rs b/src/parsing/clang/mod.rs deleted file mode 100644 index 047c49e..0000000 --- a/src/parsing/clang/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(crate) mod config; -mod entities; -mod parsing; - -pub(crate) use parsing::parse_compile_commands; diff --git a/src/parsing/clang/parsing.rs b/src/parsing/clang/parsing.rs deleted file mode 100644 index 5359253..0000000 --- a/src/parsing/clang/parsing.rs +++ /dev/null @@ -1,748 +0,0 @@ -use super::config::Config; -use super::entities::*; -use crate::types::Entity; - -use anyhow::{anyhow, Context, Error, Result}; -use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr}; -use codemap::CodeMap; -use thiserror::Error; - -use std::collections::BTreeMap; -use std::convert::{TryFrom, TryInto}; -use std::path::{Path, PathBuf}; - -#[derive(Debug, Default)] -struct TopLevel { - namespaces: BTreeMap>, - variables: BTreeMap>, - structs: BTreeMap>, - functions: BTreeMap>, -} - -/* -enum TopLevelEntry<'a, T> { - Vacant { - parent: &'a mut Described, - }, - /// Vacant, but no semantic parent - TopLevel, - Occupied { - entity: &'a mut Described, - }, - Error, -} -*/ - -impl TopLevel { - // Somehow has a lifetime issue I can't get my head around - /* - fn entry<'a, T>(&'a mut self, path: &::clang::Entity) -> Result> - where - T: ClangEntity + FromNamespaceParent + FromTopLevel, - { - let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; - if let Some(parent_path) = parent(&path) { - let parent_entry = self.entry::(&parent_path)?; - if let TopLevelEntry::Occupied { - entity: namespace_parent, - } = parent_entry - { - Ok(match T::from_namespace_parent(namespace_parent, &usr) { - None => TopLevelEntry::Vacant { - parent: namespace_parent, - }, - Some(entity) => TopLevelEntry::Occupied { entity }, - }) - } else { - panic!("Wut"); - } - } else { - Ok(match T::from_toplevel(self, &usr) { - Some(entity) => TopLevelEntry::Occupied { entity }, - None => TopLevelEntry::TopLevel, - }) - } - } - */ - - fn get_entity_mut(&mut self, path: clang::Entity) -> Option<&mut dyn ClangEntity> { - let usr = path.get_usr()?; - if let Some(parent_path) = parent(path) { - let parent = self.get_entity_mut(parent_path)?; - Some(match path.get_kind().try_into().ok()? { - ClangEntityKind::Namespace => { - &mut parent.get_member_namespaces()?.get_mut(&usr)?.entity - } - ClangEntityKind::Variable(_) => { - &mut parent.get_member_variables()?.get_mut(&usr)?.entity - } - ClangEntityKind::Function(_) => { - &mut parent.get_member_functions()?.get_mut(&usr)?.entity - } - ClangEntityKind::Struct(_) => { - &mut parent.get_member_structs()?.get_mut(&usr)?.entity - } - }) - } else { - Some(match path.get_kind().try_into().ok()? { - ClangEntityKind::Namespace => &mut self.namespaces.get_mut(&usr)?.entity, - ClangEntityKind::Variable(_) => &mut self.variables.get_mut(&usr)?.entity, - ClangEntityKind::Struct(_) => &mut self.structs.get_mut(&usr)?.entity, - ClangEntityKind::Function(_) => &mut self.functions.get_mut(&usr)?.entity, - }) - } - } - - fn get_namespace_mut(&mut self, path: clang::Entity) -> Option<&mut Described> { - let usr = path.get_usr()?; - - if let Some(parent_path) = parent(path) { - let parent = self.get_entity_mut(parent_path)?; - parent.get_member_namespaces()?.get_mut(&usr) - } else { - self.namespaces.get_mut(&usr) - } - } - - fn insert(&mut self, path: clang::Entity, entity: Described) -> Result<()> - where - T: ClangEntity + std::fmt::Debug, - Self: TopLevelManipulation, - Namespace: NamespaceParentManipulation, - { - let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; - if let Some(parent_path) = parent(path) { - if let Some(parent_namespace) = self.get_namespace_mut(parent_path) { - parent_namespace - .entity - .get_members_mut() - // Namespace should be able to contain every kind of entity - .unwrap() - .insert(usr, entity); - Ok(()) - } else { - Err(anyhow!("has parent but no parent in tree")) - } - } else { - self.insert_toplevel(usr, entity); - Ok(()) - } - } -} - -// Like .get_semantic_parent(), but return none if the parent is the translation unit -fn parent(libclang_entity: clang::Entity) -> Option { - match libclang_entity.get_semantic_parent() { - Some(parent) => { - if parent.get_kind() != clang::EntityKind::TranslationUnit { - Some(parent) - } else { - None - } - } - None => { - warn!("get_semantic_parent() returned None"); - None - } - } -} - -trait TopLevelManipulation { - fn insert_toplevel(&mut self, usr: Usr, entity: Described); -} - -impl TopLevelManipulation for TopLevel { - fn insert_toplevel(&mut self, usr: Usr, entity: Described) { - self.namespaces.insert(usr, entity); - } -} - -impl TopLevelManipulation for TopLevel { - fn insert_toplevel(&mut self, usr: Usr, entity: Described) { - self.variables.insert(usr, entity); - } -} - -impl TopLevelManipulation for TopLevel { - fn insert_toplevel(&mut self, usr: Usr, entity: Described) { - self.functions.insert(usr, entity); - } -} - -impl TopLevelManipulation for TopLevel { - fn insert_toplevel(&mut self, usr: Usr, entity: Described) { - self.structs.insert(usr, entity); - } -} - -/* -trait FromTopLevel: ClangEntity + Sized { - fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described>; -} - -impl FromTopLevel for Namespace { - fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { - toplevel.namespaces.get_mut(usr) - } -} - -impl FromTopLevel for Variable { - fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { - toplevel.variables.get_mut(usr) - } -} - -impl FromTopLevel for Function { - fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { - toplevel.functions.get_mut(usr) - } -} - -impl FromTopLevel for Struct { - fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described> { - toplevel.structs.get_mut(usr) - } -} -*/ - -pub(crate) fn parse_compile_commands( - config: &Config, - codemap: &mut CodeMap, -) -> Result> { - let clang = Clang::new().unwrap(); - let index = Index::new( - &clang, /* exclude from pch = */ false, /* print diagnostics = */ false, - ); - - debug!("Extra libclang argument: {:?}", config.extra_args); - - debug!( - "Loading compile commands from: {:?}", - config.compile_commands_location - ); - let database = - CompilationDatabase::from_directory(&config.compile_commands_location).map_err(|()| { - CompileCommandsLoadError { - path: config.compile_commands_location.clone(), - } - })?; - - let toplevel_directory = std::env::current_dir().context("Cannot read current directory")?; - - let mut entities = TopLevel::default(); - - for command in database.get_all_compile_commands().get_commands() { - let directory = command.get_directory(); - trace!("Changing directory to: {:?}", directory); - std::env::set_current_dir(&directory) - .with_context(|| format!("Cannot change current directory to: {:?}", directory))?; - - let filename = command.get_filename(); - - let file_map = codemap.add_file( - filename - .to_str() - .context("File is not valid UTF-8")? - .to_owned(), - std::fs::read_to_string(&filename) - .with_context(|| format!("Cannot readfile: {:?}", filename))?, - ); - - trace!("Parsing file: {:?}", filename); - // The file name is passed as an argument in the compile commands - let mut parser = index.parser(""); - parser.skip_function_bodies(true); - - let mut clang_arguments = command.get_arguments(); - clang_arguments.extend_from_slice(&config.extra_args); - trace!("Parsing with libclang arguments: {:?}", clang_arguments); - parser.arguments(&clang_arguments); - - parse_unit( - &parser - .parse() - .with_context(|| format!("Could not parse file: {:?}", filename))?, - &mut entities, - &toplevel_directory, - file_map.span, - &codemap, - )?; - - trace!("Changing directory to: {:?}", directory); - std::env::set_current_dir(&toplevel_directory).with_context(|| { - format!( - "Cannot change current directory to: {:?}", - toplevel_directory - ) - })?; - } - - let normalized_entities = entities - .namespaces - .into_iter() - .map(|(usr, entity)| (usr.0, entity.into())) - .chain(entities.variables.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) - .chain(entities.structs.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) - .chain(entities.functions.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) - .collect(); - - Ok(normalized_entities) -} - -/* -pub(crate) fn parse_file(path: T, extra_args: &[S]) -> EntitiesManager -where - T: Into, - T: AsRef, - T: ToString, - S: AsRef, - S: std::fmt::Debug, -{ - let mut codemap = CodeMap::new(); - let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap()); - let file_span = file_map.span; - - let clang = Clang::new().unwrap(); - let index = Index::new(&clang, true, false); - let mut parser = index.parser(path); - parser.skip_function_bodies(true); - - parser.arguments(&extra_args); - - if log_enabled!(log::Level::Debug) { - for extra_arg in extra_args { - debug!("Extra libclang argument: {:?}", extra_arg); - } - } - - let trans_unit = parser.parse().unwrap(); - let mut entities = EntitiesManager::new(); - - parse_unit( - &trans_unit, - &mut entities, - &std::env::current_dir().unwrap(), - file_span, - &codemap, - ) - .unwrap(); - - entities -} -*/ - -fn parse_unit( - trans_unit: &TranslationUnit, - entities: &mut TopLevel, - base_dir: impl AsRef, - file_span: codemap::Span, - codemap: &CodeMap, -) -> Result<()> { - trans_unit.get_entity().visit_children(|entity, _parent| { - if is_in_system_header(entity, &base_dir) { - trace!( - "Entity is in system header, skipping: USR = {:?}", - entity.get_display_name() - ); - return clang::EntityVisitResult::Continue; - } - - // TODO: wrap this callback in another function so that we can use the - // "?" operator instead of all these `match`es - let usr = match entity.get_usr() { - Some(usr) => usr, - None => return clang::EntityVisitResult::Continue, - }; - trace!("Entity with USR = {:?}", usr); - debug!("Parsing toplevel entity: {:?}", entity); - - add_entity(entity, entities, file_span, codemap) - }); - - /* - use codemap_diagnostic::{ColorConfig, Emitter}; - - let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); - - for diagnostic in trans_unit.get_diagnostics().iter() { - let main_diag = match clang_diag_to_codemap_diag(&diagnostic, file_span) { - Some(diag) => diag, - None => continue, - }; - - let sub_diags = diagnostic - .get_children() - .into_iter() - .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, file_span)); - - let fix_it_diags = diagnostic - .get_fix_its() - .into_iter() - .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, file_span)); - - emitter.emit( - &std::iter::once(main_diag) - .chain(sub_diags) - .chain(fix_it_diags) - .collect::>(), - ); - } - */ - - Ok(()) -} - -fn is_in_system_header(entity: clang::Entity, base_dir: impl AsRef) -> bool { - if entity.is_in_system_header() { - true - } else if let Some(location) = entity.get_location() { - if let Some(file) = location.get_file_location().file { - !file - .get_path() - .canonicalize() - .unwrap() - .starts_with(base_dir) - } else { - // Not defined in a file? probably shouldn't document - true - } - } else { - // Not defined anywhere? probably shouldn't document - true - } -} - -// Entries encountered in the toplevel lexical context -fn add_entity( - libclang_entity: clang::Entity, - toplevel: &mut TopLevel, - file_span: codemap::Span, - codemap: &CodeMap, -) -> clang::EntityVisitResult { - if libclang_entity.get_usr().is_none() { - return clang::EntityVisitResult::Continue; - }; - - let kind = match ClangEntityKind::try_from(libclang_entity.get_kind()) { - Ok(kind) => kind, - Err(err) => { - use codemap_diagnostic::{ - ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle, - }; - let spans = if let Some(range) = libclang_entity.get_range() { - // TODO: add every file parsed in this translation unit to the - // codemap, so we can properly report errors - if !range.is_in_main_file() { - vec![] - } else { - let begin = range.get_start().get_file_location().offset as u64; - let end = range.get_end().get_file_location().offset as u64; - - vec![SpanLabel { - span: file_span.subspan(begin, end), - label: None, - style: SpanStyle::Primary, - }] - } - } else { - vec![] - }; - - let diag = Diagnostic { - level: Level::Warning, - message: format!("{}", err), - code: None, - spans, - }; - - let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(codemap)); - emitter.emit(&[diag]); - - return clang::EntityVisitResult::Continue; - } - }; - - if let Some(in_tree_entity) = toplevel.get_entity_mut(libclang_entity) { - // if current.has_documentation && !tree.has_documentation { - // append_documentation - // } - } else if libclang_entity.is_definition() { - // TODO: This probably means that you can't put documentation in forward declarations. - // - // This seems restrictive, but since there can be multiple declarations but only one definition, - // you should probably put your documentation on the definition anyway? - // - // Also, skipping forward declarations allows us to not have to insert, then update the tree - // when we see the definition. - - let result = match kind { - ClangEntityKind::Namespace => Described::::try_from(libclang_entity) - .and_then(|namespace| toplevel.insert(libclang_entity, namespace)), - ClangEntityKind::Variable(_) => Described::::try_from(libclang_entity) - .and_then(|variable| toplevel.insert(libclang_entity, variable)), - ClangEntityKind::Struct(_) => Described::::try_from(libclang_entity) - .and_then(|r#struct| toplevel.insert(libclang_entity, r#struct)), - ClangEntityKind::Function(_) => Described::::try_from(libclang_entity) - .and_then(|function| toplevel.insert(libclang_entity, function)), - }; - // TODO: check result - } - - if kind == ClangEntityKind::Namespace { - // Recurse here since namespace definitions are allowed to change between translation units. - ::clang::EntityVisitResult::Recurse - } else { - ::clang::EntityVisitResult::Continue - } -} - -impl<'a, T> TryFrom> for Described -where - T: TryFrom, Error = Error>, -{ - type Error = Error; - - fn try_from(entity: clang::Entity<'a>) -> Result { - Ok(Described:: { - description: get_description(entity)?, - entity: T::try_from(entity)?, - }) - } -} - -impl<'a> TryFrom> for Namespace { - type Error = Error; - - fn try_from(entity: clang::Entity) -> Result { - match entity.get_kind().try_into() { - Ok(ClangEntityKind::Namespace) => {} - _ => panic!("Trying to parse a non-variable into a variable"), - } - debug!("Parsing Namespace: {:?}", entity); - - // Do not recurse here, but recurse in the main loop, since namespace - // definitions is allowed to change between translation units - - Ok(Namespace { - member_namespaces: Default::default(), - member_variables: Default::default(), - member_structs: Default::default(), - member_functions: Default::default(), - }) - } -} - -impl<'a> TryFrom> for Variable { - type Error = Error; - - fn try_from(entity: clang::Entity) -> Result { - let variable_kind; - match entity.get_kind().try_into() { - Ok(ClangEntityKind::Variable(kind)) => { - variable_kind = kind; - } - _ => panic!("Trying to parse a non-variable into a variable"), - } - debug!("Parsing Variable: {:?}", entity); - - let r#type = entity.get_type().unwrap().get_display_name(); - trace!("Variable has type: {:?}", r#type); - - Ok(Variable { - r#type, - kind: variable_kind, - }) - } -} - -impl<'a> TryFrom> for Struct { - type Error = Error; - - fn try_from(entity: clang::Entity) -> Result { - let struct_kind; - match entity.get_kind().try_into() { - Ok(ClangEntityKind::Struct(kind)) => { - struct_kind = kind; - } - _ => panic!("Trying to parse a non-class into a class"), - } - debug!("Parsing Struct: {:?}", entity); - - let mut member_variables = BTreeMap::new(); - let mut member_structs = BTreeMap::new(); - let mut member_functions = BTreeMap::new(); - - for child in entity.get_children() { - trace!("Struct has child: {:?}", child); - - match child.get_kind().try_into() { - Ok(ClangEntityKind::Variable(_)) => { - let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; - member_variables.insert(child_usr, Described::::try_from(child)?); - } - Ok(ClangEntityKind::Struct(_)) => { - let child_usr: Usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; - member_structs.insert(child_usr, Described::::try_from(child)?); - } - Ok(ClangEntityKind::Function(_)) => { - let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; - member_functions.insert(child_usr, Described::::try_from(child)?); - } - _ => trace!("Skipping child"), - } - } - - Ok(Struct { - kind: struct_kind, - member_functions, - member_structs, - member_variables, - }) - } -} - -impl<'a> TryFrom> for Function { - type Error = Error; - - fn try_from(entity: clang::Entity) -> Result { - let function_kind; - match entity.get_kind().try_into() { - Ok(ClangEntityKind::Function(kind)) => { - function_kind = kind; - } - _ => panic!("Trying to parse a non-function into a function"), - } - debug!("Parsing Function: {:?}", entity); - - let return_type = entity.get_result_type().unwrap().get_display_name(); - trace!("Function has return type: {:?}", return_type); - let arguments = entity - .get_arguments() - .unwrap() - .into_iter() - .map(|arg| { - let name = arg - .get_display_name() - .unwrap_or_else(|| String::from("unnamed")); - let r#type = arg.get_type().unwrap().get_display_name(); - trace!("Function has argument {:?} of type {:?}", name, r#type); - FunctionArgument { name, r#type } - }) - .collect(); - - Ok(Function { - kind: function_kind, - arguments, - return_type, - }) - } -} - -fn get_description(entity: clang::Entity) -> Result { - let name = entity - .get_display_name() - .ok_or_else(|| anyhow!("Entity has no name: {:?}", entity))?; - - // TODO: is that the best? - if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) { - Ok(Description { - name, - brief, - detailed: parse_comment(comment), - }) - } else { - Ok(Description { - name, - brief: String::new(), - detailed: String::new(), - }) - } -} - -pub fn parse_comment(raw: String) -> String { - #[derive(Debug)] - enum CommentStyle { - // Comments of type `/**` or `/*!` - Starred, - // Comments of type `///` - SingleLine, - } - - let mut chars = raw.chars(); - let style = match &chars.as_str()[..3] { - "/*!" | "/**" => CommentStyle::Starred, - "///" => CommentStyle::SingleLine, - _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"), - }; - - chars.nth(2); - - let mut result = String::new(); - - 'parse_loop: loop { - let maybe_space = chars.next(); - let mut empty_line = false; - match maybe_space { - // TODO: Warn on empty comments - None => break, - Some(' ') => {} - Some('\n') => { - empty_line = true; - result.push('\n'); - } - Some(ch) => result.push(ch), - } - - if !empty_line { - let rest = chars.as_str(); - match rest.find('\n') { - None => { - result.push_str(rest); - break; - } - Some(position) => { - result.push_str(&rest[..=position]); - chars.nth(position); - } - } - } - - // Beginning of the line - let first_non_ws_ch = 'ws_loop: loop { - let maybe_whitespace = chars.next(); - match maybe_whitespace { - None => break 'parse_loop, - Some(ch) if ch.is_whitespace() => continue, - Some(ch) => break 'ws_loop ch, - } - }; - - match style { - CommentStyle::Starred if first_non_ws_ch == '*' => { - if &chars.as_str()[..1] == "/" { - break; - } - } - CommentStyle::Starred => result.push(first_non_ws_ch), - CommentStyle::SingleLine => { - assert!(first_non_ws_ch == '/'); - let rest = chars.as_str(); - if &rest[..2] == "//" { - chars.nth(1); - } else if &rest[..1] == "/" { - chars.nth(0); - } else { - panic!("Could not parse comment"); - } - } - } - } - - result -} - -#[derive(Debug, Clone, Error)] -#[error("Failed to load 'compile_commands.json' at path: {:?}", path)] -pub(crate) struct CompileCommandsLoadError { - path: PathBuf, -} diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs deleted file mode 100644 index 0736eee..0000000 --- a/src/parsing/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod clang; diff --git a/src/types.rs b/src/types.rs index 82f9c65..f378f8b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,22 +1,80 @@ use serde_derive::{Serialize, Deserialize}; +use thiserror::Error; use std::collections::BTreeMap; +use std::str::FromStr; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Poseidoc { pub config: toml::Value, pub entities: BTreeMap, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Entity { pub name: String, //location: SourceLocation - pub language: String, - pub kind: String, + pub language: Language, + pub kind: EntityKind, pub brief_description: String, pub documentation: String, - pub children: BTreeMap>, + pub children: BTreeMap>, } -// TODO: use newtype for language, entity kind, entity ID +// TODO: use newtype for entity kind, entity ID + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(try_from = "&str", into = "&'static str")] +pub enum Language { + Clang, +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", <&'static str>::from(*self)) + } +} + +impl FromStr for Language { + type Err = LanguageParseError; + + fn from_str(s: &str) -> Result { + match s { + "clang" => Ok(Language::Clang), + _ => Err(LanguageParseError(())), + } + } +} + +impl std::convert::TryFrom<&str> for Language { + type Error = LanguageParseError; + fn try_from(s: &str) -> Result { + Language::from_str(s) + } +} + +impl From for &'static str { + fn from(lang: Language) -> &'static str { + match lang { + Language::Clang => "clang", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)] +#[error("invalid language")] +pub struct LanguageParseError(()); + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct EntityKind(pub String); + +// Plural version of EntityKind + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct ChildrenKind(pub String); + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[serde(transparent)] +pub struct EntityId(pub String); -- cgit v1.2.3