File
Blob: src/workerd/tools/param-extractor.rs
| 1 | use std::ffi::OsStr; |
| 2 | use std::fs::File; |
| 3 | use std::io::BufRead; |
| 4 | use std::io::BufReader; |
| 5 | use std::io::BufWriter; |
| 6 | use std::io::Write; |
| 7 | use std::path::Path; |
| 8 | |
| 9 | use anyhow::Result; |
| 10 | use flate2::read::GzDecoder; |
| 11 | use serde::Deserialize; |
| 12 | use serde::Serialize; |
| 13 | |
| 14 | /// Contains the declarations we care about |
| 15 | #[derive(Deserialize, PartialEq, Debug)] |
| 16 | enum Clang { |
| 17 | NamespaceDecl { name: Option<String> }, |
| 18 | |
| 19 | // Function-like -- direct parents of parameters |
| 20 | FunctionDecl { name: Option<String> }, |
| 21 | CXXMethodDecl { name: Option<String> }, |
| 22 | CXXRecordDecl { name: Option<String> }, |
| 23 | CXXConstructorDecl, |
| 24 | |
| 25 | // Parameter names |
| 26 | ParmVarDecl { name: Option<String> }, |
| 27 | |
| 28 | // Everything else |
| 29 | Other { name: Option<String> }, |
| 30 | } |
| 31 | |
| 32 | impl Clang { |
| 33 | fn is_function_like(&self) -> bool { |
| 34 | matches!( |
| 35 | *self, |
| 36 | Self::FunctionDecl { .. } | Self::CXXMethodDecl { .. } | Self::CXXConstructorDecl |
| 37 | ) |
| 38 | } |
| 39 | |
| 40 | fn name(&self) -> Option<&str> { |
| 41 | match self { |
| 42 | Self::NamespaceDecl { name } |
| 43 | | Self::FunctionDecl { name } |
| 44 | | Self::CXXMethodDecl { name } |
| 45 | | Self::CXXRecordDecl { name } |
| 46 | | Self::ParmVarDecl { name } |
| 47 | | Self::Other { name } => name.as_ref().map(AsRef::as_ref), |
| 48 | Self::CXXConstructorDecl => Some("constructor"), |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | type ClangNode = clang_ast::Node<Clang>; |
| 54 | |
| 55 | fn main() -> Result<()> { |
| 56 | let mut args = pico_args::Arguments::from_env(); |
| 57 | |
| 58 | let clang_ast = args.value_from_os_str("--input", |path_str| { |
| 59 | let path = Path::new(path_str); |
| 60 | let file = File::open(path)?; |
| 61 | let serde = { |
| 62 | let reader: &mut dyn BufRead = { |
| 63 | if Some("gz") == path.extension().and_then(OsStr::to_str) { |
| 64 | &mut BufReader::new(GzDecoder::new(file)) |
| 65 | } else { |
| 66 | &mut BufReader::new(file) |
| 67 | } |
| 68 | }; |
| 69 | |
| 70 | let mut deserializer = serde_json::Deserializer::from_reader(reader); |
| 71 | // Note: serde_json doesn't support custom recursion limits, only disabling. |
| 72 | // We disable the limit to handle deeply nested AST structures (default 128 is |
| 73 | // insufficient for the clang AST dump, which can be deeply nested). |
| 74 | deserializer.disable_recursion_limit(); |
| 75 | ClangNode::deserialize(&mut deserializer) |
| 76 | }; |
| 77 | serde.map_err(anyhow::Error::from) |
| 78 | })?; |
| 79 | |
| 80 | let value = get_parameter_names(clang_ast); |
| 81 | |
| 82 | let mut writer = |
| 83 | args.value_from_os_str("--output", |path| File::create(path).map(BufWriter::new))?; |
| 84 | |
| 85 | serde_json::to_writer(&mut writer, &value)?; |
| 86 | writer.flush()?; |
| 87 | |
| 88 | Ok(()) |
| 89 | } |
| 90 | |
| 91 | fn get_parameter_names(clang_ast: ClangNode) -> Vec<Parameter> { |
| 92 | let workerd_namespace = Clang::NamespaceDecl { |
| 93 | name: Some("workerd".to_owned()), |
| 94 | }; |
| 95 | |
| 96 | clang_ast |
| 97 | .inner |
| 98 | .into_iter() |
| 99 | .filter(|node| node.kind == workerd_namespace) |
| 100 | .flat_map(|node| traverse_disambiguous(node, &[])) |
| 101 | .collect() |
| 102 | } |
| 103 | |
| 104 | #[derive(Serialize, Debug)] |
| 105 | struct Parameter { |
| 106 | fully_qualified_parent_name: Vec<String>, |
| 107 | function_like_name: String, |
| 108 | index: usize, |
| 109 | name: String, |
| 110 | } |
| 111 | |
| 112 | fn traverse_disambiguous( |
| 113 | disambiguous: ClangNode, |
| 114 | fully_qualified_parent_name: &[String], |
| 115 | ) -> Vec<Parameter> { |
| 116 | let disambiguous_name = disambiguous |
| 117 | .kind |
| 118 | .name() |
| 119 | .map(ToOwned::to_owned) |
| 120 | .unwrap_or_default(); |
| 121 | |
| 122 | disambiguous |
| 123 | .inner |
| 124 | .into_iter() |
| 125 | .flat_map(|node| { |
| 126 | let mut qualified: Vec<_> = fully_qualified_parent_name.to_vec(); |
| 127 | qualified.push(disambiguous_name.clone()); |
| 128 | if node.kind.is_function_like() { |
| 129 | traverse_function_like(node, &qualified) |
| 130 | } else { |
| 131 | traverse_disambiguous(node, &qualified) |
| 132 | } |
| 133 | }) |
| 134 | .collect() |
| 135 | } |
| 136 | |
| 137 | fn traverse_function_like( |
| 138 | node: ClangNode, |
| 139 | fully_qualified_parent_name: &[String], |
| 140 | ) -> Vec<Parameter> { |
| 141 | let function_like_name = node.kind.name().expect("missing name").to_owned(); |
| 142 | |
| 143 | node.inner |
| 144 | .into_iter() |
| 145 | .filter_map(|child| { |
| 146 | if let Clang::ParmVarDecl { name: Some(name) } = child.kind { |
| 147 | Some(name) |
| 148 | } else { |
| 149 | None |
| 150 | } |
| 151 | }) |
| 152 | .enumerate() |
| 153 | .map(|(i, param_name)| Parameter { |
| 154 | fully_qualified_parent_name: fully_qualified_parent_name.to_vec(), |
| 155 | function_like_name: function_like_name.clone(), |
| 156 | index: i, |
| 157 | name: param_name, |
| 158 | }) |
| 159 | .collect() |
| 160 | } |