├── tests ├── malformed_cfg.xml ├── dummy_cfg.json └── lib.rs ├── Cargo.toml ├── src ├── string_gen │ ├── mod.rs │ ├── keywords.rs │ ├── cpp.rs │ └── rust.rs ├── error.rs ├── xml_parser.rs ├── lib.rs ├── json_parser.rs ├── fmt_code.rs └── raw_code.rs ├── .github └── workflows │ └── rust.yml ├── examples ├── config.json ├── json.rs ├── xml.rs ├── sample2.json ├── config.xml ├── sample.json └── sample.xml ├── LICENSE └── README.md /tests/malformed_cfg.xml: -------------------------------------------------------------------------------- 1 | rust num_tabs="4" tab_char=" " /> -------------------------------------------------------------------------------- /tests/dummy_cfg.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust": [ 3 | { "type": "foo", "value": "bar" } 4 | ] 5 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codespawn" 3 | version = "0.3.3" 4 | description = "C++ and Rust code generator. Supports XML and JSON for API definitions." 5 | keywords = ["code", "generator", "codegen", "gencode"] 6 | license = "MIT/Apache-2.0" 7 | authors = ["Krzysztof Kondrak "] 8 | repository = "https://github.com/kondrak/codespawn" 9 | homepage = "https://github.com/kondrak/codespawn" 10 | documentation = "https://docs.rs/codespawn" 11 | 12 | [dependencies] 13 | xml-rs = "0.3" 14 | json = "0.10" -------------------------------------------------------------------------------- /src/string_gen/mod.rs: -------------------------------------------------------------------------------- 1 | use fmt_code::{FormattedCode, Lang}; 2 | pub mod keywords; 3 | mod cpp; 4 | mod rust; 5 | use self::cpp::convert as convert_cpp; 6 | use self::rust::convert as convert_rust; 7 | 8 | pub const AUTOGEN_HEADER: &'static str = concat!("!!! Autogenerated with codespawn (", env!("CARGO_PKG_VERSION"), ") - do not modify. !!!\n"); 9 | pub const AUTOGEN_FOOTER: &'static str = "!!! End of autogenerated data. !!!\n"; 10 | 11 | pub fn code_to_str(fmt_code: &FormattedCode) -> String { 12 | match fmt_code.language { 13 | Lang::Cpp => convert_cpp(&fmt_code.elements, fmt_code.num_tabs, fmt_code.tab_char), 14 | Lang::Rust => convert_rust(&fmt_code.elements, fmt_code.num_tabs, fmt_code.tab_char), 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build and Test 15 | strategy: 16 | matrix: 17 | os: [Ubuntu-latest, Windows-latest, MacOS-latest] 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | override: true 26 | - name: cargo fetch 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: fetch 30 | - name: cargo test 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: test 34 | args: --verbose 35 | -------------------------------------------------------------------------------- /src/string_gen/keywords.rs: -------------------------------------------------------------------------------- 1 | // primary element names 2 | pub const CONFIG: &'static str = "config"; 3 | pub const ENUM: &'static str = "enum"; 4 | pub const VAR: &'static str = "var"; 5 | pub const FUNC: &'static str = "func"; 6 | pub const FPTR: &'static str = "fptr"; 7 | pub const STRUCT: &'static str = "struct"; 8 | pub const BITFLAGS: &'static str = "bitflags"; 9 | 10 | // element attributes 11 | pub const NAME: &'static str = "name"; 12 | pub const TYPE: &'static str = "type"; 13 | pub const VALUE: &'static str = "value"; 14 | pub const NUM_TABS: &'static str = "num_tabs"; 15 | pub const TAB_CHAR: &'static str = "tab_char"; 16 | pub const ATTRIBUTE: &'static str = "attribute"; 17 | pub const QUALIFIER: &'static str = "qualifier"; 18 | 19 | pub const ATTRIB_ARRAY: [&'static str; 7] = [NAME, TYPE, VALUE, NUM_TABS, 20 | TAB_CHAR, ATTRIBUTE, QUALIFIER]; 21 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust": [ 3 | { "num_tabs": "1", "tab_char": "\t" }, 4 | { "type": "int", "value": "c_int" }, 5 | { "type": "float", "value": "c_float" }, 6 | { "type": "void", "value": "c_void" }, 7 | { "type": "char", "value": "c_char" }, 8 | { "type": "int*", "value": "*mut c_int" }, 9 | { "type": "float*", "value": "*mut c_float" }, 10 | { "type": "void*", "value": "*mut c_void" }, 11 | { "type": "const float*", "value": "*const c_float" }, 12 | { "type": "const char*", "value": "*const c_char" }, 13 | { "name": "GenericStruct", "value": "RustStruct" }, 14 | { "name": "C-ABI", "value": "#[repr(C)]"}, 15 | { "name": "DERIVE-DBG", "value": "#[derive(Debug)]"}, 16 | { "name": "OPTION", "value": "Option<" }, 17 | { "name": "OPTION_FPTR", "value": "Option - Override will be used when generating Rust code. 8 | - - Override will be used when generating C/C++ code. 9 | 10 | * Valid attributes: 11 | - num_tabs - Defines how many tab characters to insert per indentation. 12 | Defaults to 4 if not specified. 13 | - tab_char - Defines the actual tab character. Default: space. 14 | - value - Used in conjunction with either 'name' or 'type'. Defines 15 | the override value. Can be defined as empty. 16 | - type - When specified, 'value' will override all occurences 17 | of type 'type' in generated code. 18 | - name - When specified, 'value' will override all occurences 19 | of name 'name' in generated code. This applies to 20 | all named code objects (so variables, functions, structs etc.) 21 | --> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Codespawn 2 | ========= 3 | 4 | [![Crates.io](https://img.shields.io/crates/v/codespawn.svg)](https://crates.io/crates/codespawn) 5 | [![Documentation](https://docs.rs/codespawn/badge.svg)](https://docs.rs/codespawn) 6 | [![CI](https://github.com/kondrak/codespawn/actions/workflows/rust.yml/badge.svg)](https://github.com/kondrak/codespawn/actions/workflows/rust.yml) 7 | [![Coverage Status](https://coveralls.io/repos/github/kondrak/codespawn/badge.svg?branch=master)](https://coveralls.io/github/kondrak/codespawn?branch=master) 8 | ![](https://img.shields.io/crates/l/json.svg) 9 | 10 | Codespawn is a basic C++ and Rust code generator. Desired API can be defined using either JSON or XML and the crate supports both reading from a file or a string. Currently it's possible to generate enums, structs, functions, function pointers, variables and bitflags with all applicable attributes and properties. 11 | 12 | This crate was created as a helper tool for [ProDBG](https://github.com/emoon/ProDBG). See [example XML](https://github.com/kondrak/codespawn/blob/master/examples/sample.xml) for instructions on how to construct the API definition. 13 | 14 | [Documentation](https://docs.rs/codespawn) 15 | 16 | Usage 17 | ----- 18 | ```toml 19 | # Cargo.toml 20 | [dependencies] 21 | codespawn = "0.3" 22 | ``` 23 | 24 | Example 25 | ------- 26 | ```rust 27 | extern crate codespawn; 28 | 29 | fn main() 30 | { 31 | // generate from XML definition 32 | let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 33 | // generate from JSON definition 34 | //let raw_code = codespawn::from_json("examples/sample.json").unwrap(); 35 | 36 | // generate code, store as String 37 | let cpp_code = raw_code.to_cpp().unwrap().to_string(); 38 | let rust_code = raw_code.to_rust().unwrap().to_string(); 39 | 40 | // generate and save directly to file 41 | raw_code.to_cpp().unwrap().to_file("sample.cpp"); 42 | raw_code.to_rust().unwrap().to_file("sample.rs"); 43 | } 44 | ``` 45 | 46 | Build instructions 47 | ------------------ 48 | 49 | ``` 50 | cargo build 51 | cargo run --example xml 52 | cargo run --example json 53 | ``` 54 | 55 | This will run the [example](https://github.com/kondrak/codespawn/blob/master/examples/xml.rs) which will generate code and save it to files using sample XML definition. 56 | 57 | ## License 58 | 59 | Licensed under either of 60 | 61 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 62 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 63 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error type for codespawn crate. 2 | extern crate json; 3 | extern crate xml; 4 | 5 | use std::error::Error; 6 | use std::fmt; 7 | use std::num; 8 | use std::io; 9 | use std::result; 10 | 11 | macro_rules! some { 12 | ($x:expr, $msg:expr) => ($x.ok_or(CodeSpawnError::Other($msg.to_owned()))?) 13 | } 14 | 15 | macro_rules! some_str { 16 | ($x:expr) => (some!($x, "Unable to fetch string.")) 17 | } 18 | 19 | macro_rules! some_get { 20 | ($x:expr) => (some!($x, "Unable to fetch object.")) 21 | } 22 | 23 | /// Result type used throughout the crate. 24 | pub type Result = result::Result; 25 | 26 | /// Error type for codespawn crate. 27 | #[derive(Debug)] 28 | pub enum CodeSpawnError { 29 | /// I/O error 30 | Io(io::Error), 31 | /// JSON parser error 32 | Json(json::JsonError), 33 | /// XML parser error 34 | Xml(xml::reader::Error), 35 | /// Any other kind of error 36 | Other(String) 37 | } 38 | 39 | impl fmt::Display for CodeSpawnError { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | match *self { 42 | CodeSpawnError::Io(ref err) => err.fmt(f), 43 | CodeSpawnError::Json(ref err) => err.fmt(f), 44 | CodeSpawnError::Xml(ref err) => err.fmt(f), 45 | CodeSpawnError::Other(ref err) => err.fmt(f) 46 | } 47 | } 48 | } 49 | 50 | impl Error for CodeSpawnError { 51 | fn description(&self) -> &str { 52 | match *self { 53 | CodeSpawnError::Io(ref err) => err.description(), 54 | CodeSpawnError::Json(ref err) => err.description(), 55 | CodeSpawnError::Xml(ref err) => err.description(), 56 | CodeSpawnError::Other(ref err) => err 57 | } 58 | } 59 | } 60 | 61 | impl From for CodeSpawnError { 62 | fn from(err: io::Error) -> CodeSpawnError { 63 | CodeSpawnError::Io(err) 64 | } 65 | } 66 | 67 | impl From for CodeSpawnError { 68 | fn from(err: json::JsonError) -> CodeSpawnError { 69 | CodeSpawnError::Json(err) 70 | } 71 | } 72 | 73 | impl From for CodeSpawnError { 74 | fn from(err: xml::reader::Error) -> CodeSpawnError { 75 | CodeSpawnError::Xml(err) 76 | } 77 | } 78 | 79 | impl From for CodeSpawnError { 80 | fn from(err: String) -> CodeSpawnError { 81 | CodeSpawnError::Other(err) 82 | } 83 | } 84 | 85 | impl From for CodeSpawnError { 86 | fn from(err: num::ParseIntError) -> CodeSpawnError { 87 | CodeSpawnError::Other(err.description().to_owned()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/xml_parser.rs: -------------------------------------------------------------------------------- 1 | extern crate xml; 2 | 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | use std::io::prelude::*; 6 | use std::path::Path; 7 | 8 | use self::xml::reader::{EventReader, XmlEvent}; 9 | use string_gen::keywords::*; 10 | use error::{CodeSpawnError, Result}; 11 | use raw_code::{RawCode, CodeData, generate_raw}; 12 | 13 | pub fn process_xml_file(filename: &str) -> Result { 14 | let path = Path::new(&filename); 15 | let mut xml_data = String::new(); 16 | let mut file = File::open(&path)?; 17 | file.read_to_string(&mut xml_data)?; 18 | 19 | process_xml_str(xml_data.as_str()) 20 | } 21 | 22 | pub fn process_xml_str(xml_str: &str) -> Result { 23 | let parser = EventReader::from_str(xml_str); 24 | let mut code_data = Vec::::new(); 25 | let mut config_tags = Vec::::new(); 26 | let mut config_data = Vec::::new(); 27 | let mut depth = 0; 28 | 29 | for e in parser { 30 | match e { 31 | Ok(XmlEvent::StartElement { name, attributes, .. }) => { 32 | let mut attribs = Vec::<(String, String)>::new(); 33 | for a in attributes.iter() { 34 | attribs.push((a.name.local_name.clone(), a.value.clone())); 35 | } 36 | if name.local_name == CONFIG { 37 | config_tags.push((name.local_name, attribs, depth)); 38 | } 39 | else { 40 | code_data.push((name.local_name, attribs, depth)); 41 | } 42 | depth += 1; 43 | } 44 | Ok(XmlEvent::EndElement { .. }) => { 45 | depth -= 1; 46 | } 47 | Err(why) => { 48 | return Err(CodeSpawnError::Xml(why)); 49 | } 50 | _ => {} 51 | } 52 | } 53 | 54 | // process configs, if found 55 | for c in config_tags.iter() { 56 | for a in c.1.iter() { 57 | if a.0 == NAME { 58 | let path = Path::new(&a.1); 59 | let file =File::open(&path)?; 60 | let file = BufReader::new(file); 61 | let parser = EventReader::new(file); 62 | for e in parser { 63 | match e { 64 | Ok(XmlEvent::StartElement { name, attributes, .. }) => { 65 | let mut attribs = Vec::<(String, String)>::new(); 66 | for a in attributes.iter() { 67 | attribs.push((a.name.local_name.clone(), a.value.clone())); 68 | } 69 | config_data.push((name.local_name, attribs, 0)); 70 | } 71 | Err(why) => { 72 | return Err(CodeSpawnError::Xml(why)); 73 | } 74 | _ => {} 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | generate_raw(&code_data, &config_data) 82 | } 83 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [Codespawn](https://github.com/kondrak/codespawn) is a basic C++ and Rust code generator. 2 | //! Desired API can be defined using either JSON or XML and the crate supports both reading 3 | //! from a file or a string. 4 | //! 5 | //! Currently it's possible to generate enums, structs, functions, function pointers, variables 6 | //! and bitflags with all applicable attributes and properties. 7 | //! 8 | //! See [example XML](https://github.com/kondrak/codespawn/blob/master/examples/sample.xml) for instructions 9 | //! on how to construct the API definition. 10 | //! 11 | //!# Quick Start 12 | //! 13 | //!``` 14 | //!extern crate codespawn; 15 | //! 16 | //!fn main() 17 | //!{ 18 | //! // generate from XML definition 19 | //! let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 20 | //! // generate from JSON definition 21 | //! //let raw_code = codespawn::from_json("examples/sample.json").unwrap(); 22 | //! 23 | //! // generate code, store as String 24 | //! let cpp_code = raw_code.to_cpp().unwrap().to_string(); 25 | //! let rust_code = raw_code.to_rust().unwrap().to_string(); 26 | //! 27 | //! // generate C++ and save directly to file 28 | //! raw_code.to_cpp().unwrap().to_file("examples/sample.cpp"); 29 | //! // generate Rust and save directly to file 30 | //! //raw_code.to_rust().unwrap().to_file("examples/sample.rs"); 31 | //!} 32 | //!``` 33 | #[macro_use] 34 | pub mod error; 35 | pub mod raw_code; 36 | pub mod fmt_code; 37 | mod xml_parser; 38 | mod json_parser; 39 | mod string_gen; 40 | 41 | use error::Result; 42 | use raw_code::{RawCode}; 43 | 44 | /// Reads XML data from file and compiles it into `RawCode` 45 | /// 46 | /// # Examples 47 | /// 48 | /// ``` 49 | /// extern crate codespawn; 50 | /// 51 | /// let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 52 | /// ``` 53 | pub fn from_xml(filename: &str) -> Result { 54 | xml_parser::process_xml_file(filename) 55 | } 56 | 57 | /// Reads XML data from a `&str` and compiles it into `RawCode` 58 | /// 59 | /// # Examples 60 | /// 61 | /// ``` 62 | /// extern crate codespawn; 63 | /// 64 | /// let raw_code = codespawn::from_xml_str("").unwrap(); 65 | /// ``` 66 | pub fn from_xml_str(xml: &str) -> Result { 67 | xml_parser::process_xml_str(xml) 68 | } 69 | 70 | /// Reads JSON data from file and compiles it into `RawCode` 71 | /// 72 | /// # Examples 73 | /// 74 | /// ``` 75 | /// extern crate codespawn; 76 | /// 77 | /// let raw_code = codespawn::from_json("examples/sample.json").unwrap(); 78 | /// ``` 79 | pub fn from_json(filename: &str) -> Result { 80 | json_parser::process_json_file(filename) 81 | } 82 | 83 | /// Reads JSON data from a `&str` and compiles it into `RawCode` 84 | /// 85 | /// # Examples 86 | /// 87 | /// ``` 88 | /// extern crate codespawn; 89 | /// 90 | /// let raw_code = codespawn::from_json_str("{\"enum\": { \"name\": \"Foo\",\"var\": {\"name\": \"EnumVal1\",\"type\": \"int\" }}}").unwrap(); 91 | /// ``` 92 | pub fn from_json_str(json: &str) -> Result { 93 | json_parser::process_json_str(json) 94 | } 95 | -------------------------------------------------------------------------------- /src/json_parser.rs: -------------------------------------------------------------------------------- 1 | extern crate json; 2 | 3 | use std::fs::File; 4 | use std::io::prelude::*; 5 | use std::path::Path; 6 | 7 | use string_gen::keywords::*; 8 | use error::{CodeSpawnError, Result}; 9 | use raw_code::{RawCode, CodeData, generate_raw}; 10 | 11 | pub fn process_json_file(filename: &str) -> Result { 12 | let path = Path::new(&filename); 13 | let mut json_data = String::new(); 14 | let mut file = File::open(&path)?; 15 | file.read_to_string(&mut json_data)?; 16 | 17 | process_json_str(json_data.as_str()) 18 | } 19 | 20 | pub fn process_json_str(json_str: &str) -> Result { 21 | let mut code_data = Vec::::new(); 22 | let mut config_tags = Vec::::new(); 23 | let mut config_data = Vec::::new(); 24 | 25 | let parsed_json = json::parse(json_str)?; 26 | for i in parsed_json.entries() { 27 | if i.0 == CONFIG { 28 | if i.1.len() == 0 { 29 | let cfg_file = String::from(some_str!(i.1.as_str())); 30 | config_tags.push((String::from(i.0), vec![(String::from(NAME), cfg_file)], 0)); 31 | } 32 | else { 33 | let mut filename_vec = Vec::<(String, String)>::new(); 34 | for e in i.1.members() { 35 | let cfg_file = String::from(some_str!(e.as_str())); 36 | filename_vec.push((String::from(NAME), cfg_file)); 37 | } 38 | config_tags.push((String::from(i.0), filename_vec, 0)); 39 | } 40 | } 41 | else { 42 | process(&i, &mut code_data, 0)?; 43 | } 44 | } 45 | 46 | // process configs, if found 47 | let mut json_cfg = String::new(); 48 | for c in config_tags.iter() { 49 | for a in c.1.iter() { 50 | if a.0 == NAME { 51 | json_cfg.clear(); 52 | let path = Path::new(&a.1); 53 | let mut file = File::open(&path)?; 54 | file.read_to_string(&mut json_cfg)?; 55 | 56 | let parsed_json = json::parse(json_cfg.as_str())?; 57 | for i in parsed_json.entries() { 58 | process(&i, &mut config_data, 0)?; 59 | } 60 | } 61 | } 62 | } 63 | 64 | generate_raw(&code_data, &config_data) 65 | } 66 | 67 | fn process(json: &(&str, &json::JsonValue), data: &mut Vec, depth: u8) -> Result<()> { 68 | data.push((String::from(json.0), Vec::<(String, String)>::new(), depth)); 69 | let mut attribs = Vec::<(String, String)>::new(); 70 | let idx = data.len() - 1; 71 | 72 | for i in json.1.entries() { 73 | if i.1.entries().count() == 0 && i.1.members().count() == 0 { 74 | if ATTRIB_ARRAY.contains(&i.0) { 75 | attribs.push((String::from(i.0), String::from(some_str!(i.1.as_str())))); 76 | } 77 | else { 78 | // simplified JSON: unknown keywords will be identified as a type->name pair 79 | attribs.push((String::from(NAME), String::from(some_str!(i.1.as_str())))); 80 | attribs.push((String::from(TYPE), String::from(i.0))); 81 | } 82 | } 83 | for j in i.1.members() { 84 | process(&(&i.0, &j), data, depth+1)?; 85 | } 86 | if i.1.entries().count() > 0 { 87 | process(&i, data, depth+1)?; 88 | } 89 | } 90 | 91 | for j in json.1.members() { 92 | process(&(&json.0, &j), data, depth)?; 93 | } 94 | 95 | // assign collected attributes to element 96 | data[idx].1 = attribs; 97 | 98 | // if element is only a list of members, remove it from the list 99 | if json.1.entries().count() == 0 && json.1.members().count() > 0 { 100 | data.remove(idx); 101 | } 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /src/fmt_code.rs: -------------------------------------------------------------------------------- 1 | //! Structures and formatters for language-specific code data. 2 | use std::fmt; 3 | use std::fs::File; 4 | use std::io::prelude::*; 5 | use std::path::Path; 6 | 7 | use error::{CodeSpawnError, Result}; 8 | use raw_code::{CodeItem, CodeConfig, print_code_item}; 9 | use string_gen::keywords::*; 10 | use string_gen::{code_to_str}; 11 | 12 | /// Supported languages. 13 | #[derive(PartialEq, Eq, Hash)] 14 | pub enum Lang { 15 | Cpp, 16 | Rust 17 | } 18 | 19 | /// Formatted code data representation. 20 | /// Object of this type is already pre-parsed and ready to generate code for the specific 21 | /// language it's been formatted to. 22 | pub struct FormattedCode { 23 | /// Language name. 24 | pub language: Lang, 25 | /// List of code elements, formatted for current language. 26 | pub elements: Vec, 27 | /// Number of tab characters per line (default: 4). 28 | pub num_tabs: u8, 29 | /// Tab character to be used (default: space). 30 | pub tab_char: char, 31 | } 32 | 33 | impl FormattedCode { 34 | #[doc(hidden)] 35 | pub fn new(lang: Lang, cfg: &Option<&CodeConfig>, data: &Vec) -> Result { 36 | let mut fmt_code = FormattedCode { 37 | language: lang, 38 | elements: data.to_vec(), 39 | num_tabs: 4, 40 | tab_char: ' ' 41 | }; 42 | 43 | match *cfg { 44 | Some(config) => { 45 | // replace types and names using data from config file 46 | fmt_code.process_config(&config)?; 47 | }, 48 | None => {} 49 | }; 50 | 51 | Ok(fmt_code) 52 | } 53 | 54 | fn process_config(&mut self, config: &CodeConfig) -> Result<()> { 55 | for (k, v) in config.type_dict.iter() { 56 | for e in self.elements.iter_mut() { 57 | FormattedCode::update_element(e, &k, &v); 58 | } 59 | } 60 | for (k, v) in config.name_dict.iter() { 61 | for e in self.elements.iter_mut() { 62 | FormattedCode::update_element(e, &k, &v); 63 | } 64 | } 65 | for (k, v) in config.global_cfg.iter() { 66 | match k.as_str() { 67 | NUM_TABS => if !v.is_empty() { self.num_tabs = v.parse::()?; }, 68 | TAB_CHAR => if !v.is_empty() { self.tab_char = some!(v.chars().next(), "Invalid iterator"); }, 69 | _ => {} 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | fn update_element(e: &mut CodeItem, key: &String, value: &String) { 77 | for child in e.children.iter_mut() { 78 | FormattedCode::update_element(child, key, value); 79 | } 80 | for a in e.attributes.iter_mut() { 81 | if a.1 == *key { 82 | a.1 = value.clone(); 83 | } 84 | } 85 | } 86 | 87 | /// Saves generated code to file. 88 | /// 89 | /// # Examples 90 | /// 91 | /// ``` 92 | /// extern crate codespawn; 93 | /// 94 | /// let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 95 | /// 96 | /// // Create a FormattedCode object for C++ language 97 | /// let cpp_code = raw_code.to_cpp().unwrap(); 98 | /// cpp_code.to_file("examples/sample.cpp"); 99 | /// ``` 100 | pub fn to_file(&self, filename: &str) -> Result<()> { 101 | let code = self.to_string(); 102 | let path = Path::new(&filename); 103 | let mut file = File::create(&path)?; 104 | file.write_all(code.as_bytes())?; 105 | Ok(()) 106 | } 107 | 108 | /// Generates code and returns it as a `String` 109 | /// 110 | /// # Examples 111 | /// 112 | /// ``` 113 | /// extern crate codespawn; 114 | /// 115 | /// let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 116 | /// 117 | /// // Create a FormattedCode object for C++ language 118 | /// let cpp_code = raw_code.to_cpp().unwrap(); 119 | /// println!("Generated C++ code:\n {}", cpp_code.to_string()); 120 | /// ``` 121 | pub fn to_string(&self) -> String { 122 | code_to_str(self) 123 | } 124 | } 125 | 126 | impl fmt::Display for Lang { 127 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 128 | let str_name = match *self { 129 | Lang::Cpp => "C/C++", Lang::Rust => "Rust" 130 | }; 131 | write!(f, "{}", str_name) 132 | } 133 | } 134 | 135 | impl fmt::Display for FormattedCode { 136 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 137 | let _ = write!(f, "Target: {}\n", self.language); 138 | let _ = write!(f, "*\n"); 139 | for e in self.elements.iter() { 140 | let mut empty_spaces = Vec::::new(); 141 | print_code_item(e, f, 0, &mut empty_spaces); 142 | } 143 | write!(f, "*\n") 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate codespawn; 2 | 3 | #[test] 4 | fn check_from_xml() { 5 | let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 6 | 7 | for c in raw_code.configs.iter() { 8 | println!("{}", c.1); 9 | } 10 | 11 | println!("{}", raw_code); 12 | println!("{}", raw_code.to_cpp().unwrap()); 13 | println!("{}", raw_code.to_rust().unwrap()); 14 | 15 | // save to .rst file instead of .rs so that subsequent test don't get confused 16 | let _ = raw_code.to_rust().unwrap().to_file("tests/sample.rst"); 17 | let _ = raw_code.to_cpp().unwrap().to_file("tests/sample.cpp"); 18 | } 19 | 20 | #[test] 21 | fn check_from_json() { 22 | let raw_code = codespawn::from_json("examples/sample.json").unwrap(); 23 | 24 | for c in raw_code.configs.iter() { 25 | println!("{}", c.1); 26 | } 27 | 28 | println!("{}", raw_code); 29 | println!("{}", raw_code.to_cpp().unwrap()); 30 | println!("{}", raw_code.to_rust().unwrap()); 31 | 32 | // save to .rst file instead of .rs so that subsequent test don't get confused 33 | let _ = raw_code.to_rust().unwrap().to_file("tests/sample.rst"); 34 | let _ = raw_code.to_cpp().unwrap().to_file("tests/sample.cpp"); 35 | } 36 | 37 | #[test] 38 | fn check_simple_json() { 39 | let raw_code = codespawn::from_json_str("{\"config\": [\"tests/dummy_cfg.json\", \"examples/config.json\"],\"var\":[{\"void*\":\"void_ptr\"},{\"int\":\"some_number\"}]}").unwrap(); 40 | let _ = raw_code.to_cpp(); 41 | let _ = raw_code.to_rust(); 42 | } 43 | 44 | #[test] 45 | #[should_panic] 46 | fn check_from_xml_fail() { 47 | let _ = codespawn::from_xml("foobar").unwrap_or_else(|e| { panic!("{}", e);}); } 48 | 49 | #[test] 50 | #[should_panic] 51 | fn check_from_json_fail() { 52 | let _ = codespawn::from_json("foobar").unwrap_or_else(|e| { panic!("{}", e);}); } 53 | 54 | #[test] 55 | #[should_panic] 56 | fn check_from_xml_fail_cfg() { 57 | let _ = codespawn::from_xml_str("").unwrap_or_else(|e| { panic!("{}", e);}); } 58 | 59 | #[test] 60 | #[should_panic] 61 | fn check_from_json_fail_cfg() { 62 | let _ = codespawn::from_json_str("{\"config\": \"foobar\", \"var\": {\"name\": \"x\", \"type\":\"int\" }}").unwrap(); } 63 | 64 | #[test] 65 | #[should_panic] 66 | fn check_from_xml_fail_malformed() { 67 | let _ = codespawn::from_xml_str("var name=\"x\" type=\"int\"/>").unwrap_or_else(|e| { panic!("{}", e);}); } 68 | 69 | #[test] 70 | #[should_panic] 71 | fn check_from_json_fail_malformed() { 72 | let _ = codespawn::from_json_str("{\"config\": \"examples/config.json\" \"var\": {\"name\": \"x\", \"type\":\"int\" }}").unwrap_or_else(|e| { panic!("{}", e);}); } 73 | 74 | #[test] 75 | #[should_panic] 76 | fn check_from_xml_fail_enum() { 77 | let rc = codespawn::from_xml_str("").unwrap_or_else(|e| { panic!("{}", e);}); 78 | rc.to_rust().unwrap().to_string(); } 79 | 80 | #[test] 81 | #[should_panic] 82 | fn check_from_json_fail_enum() { 83 | let rc = codespawn::from_json_str("{\"enum\":{ \"name\":\"Foo\",\"func\": { \"name\":\"x\",\"type\":\"int\"}}}").unwrap_or_else(|e| { panic!("{}", e);}); 84 | rc.to_cpp().unwrap().to_string(); } 85 | 86 | #[test] 87 | #[should_panic] 88 | fn check_from_xml_fail_func() { 89 | let rc = codespawn::from_xml_str("").unwrap_or_else(|e| { panic!("{}", e);}); 90 | rc.to_rust().unwrap().to_string(); } 91 | 92 | #[test] 93 | #[should_panic] 94 | fn check_from_json_fail_func() { 95 | let rc = codespawn::from_json_str("{\"func\": {\"name\":\"x\",\"type\":\"int\",\"enum\": {\"name\":\"Foo\"}}}").unwrap_or_else(|e| { panic!("{}", e);}); 96 | rc.to_cpp().unwrap().to_string(); } 97 | 98 | #[test] 99 | #[should_panic] 100 | fn check_from_xml_fail_bitflags() { 101 | let rc = codespawn::from_xml_str("").unwrap_or_else(|e| { panic!("{}", e);}); 102 | rc.to_rust().unwrap().to_string(); } 103 | 104 | #[test] 105 | #[should_panic] 106 | fn check_from_json_fail_bitflags() { 107 | let rc = codespawn::from_json_str("{\"struct\":{ \"name\":\"Foo\",\"bitflags\": { \"name\":\"x\",\"type\":\"int\", \"enum\": { \"name\": \"foonum\" }}}}").unwrap_or_else(|e| { panic!("{}", e);}); 108 | rc.to_rust().unwrap().to_string(); } 109 | 110 | #[test] 111 | #[should_panic] 112 | fn check_from_xml_fail_malformed_cfg() { 113 | let _ = codespawn::from_xml_str("").unwrap_or_else(|e| { panic!("{}", e);}); } 114 | 115 | #[test] 116 | #[should_panic] 117 | fn check_write_file() { 118 | let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 119 | raw_code.to_cpp().unwrap().to_file("").unwrap_or_else(|e| { panic!("{}", e);}); } 120 | -------------------------------------------------------------------------------- /examples/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": "examples/config.json", 3 | "var": [ 4 | { 5 | "type": "void*", 6 | "name": "void_ptr", 7 | "qualifier": "extern" 8 | }, 9 | { 10 | "type": "int", 11 | "name": "some_number", 12 | "value": "1" 13 | }, 14 | { 15 | "name": "ignored_var" 16 | } 17 | ], 18 | "enum": { 19 | "name": "GenericEnum", 20 | "attribute": [ {"name": "C-ABI"}, {"name": "DERIVE-DBG"} ], 21 | "var": [ 22 | { 23 | "name": "EnumVal1", 24 | "type": "int" 25 | }, 26 | { 27 | "name": "EnumVal2", 28 | "type": "int" 29 | }, 30 | { 31 | "name": "Count", 32 | "value": "2", 33 | "type": "int" 34 | } 35 | ] 36 | }, 37 | "bitflags": [ 38 | { 39 | "name": "SampleBitflags", 40 | "type": "int", 41 | "attribute": [ {"name": "C-ABI"}, {"name": "DERIVE-DBG"} ], 42 | "var": [ 43 | { 44 | "name": "Field1", 45 | "type": "int", 46 | "value": "1" 47 | }, 48 | { 49 | "name": "Field2", 50 | "type": "int", 51 | "value": "2" 52 | } 53 | ] 54 | }, 55 | { 56 | "name": "AltBitflags", 57 | "type": "int", 58 | "attribute": "C-ABI", 59 | "var": { 60 | "name": "C_Field1", 61 | "type": "int", 62 | "value": "1" 63 | } 64 | } 65 | ], 66 | "func": [ 67 | { 68 | "type": "int", 69 | "name": "get_ascii", 70 | "var": { 71 | "type": "char&", 72 | "name": "letter" 73 | }, 74 | "fptr": { 75 | "type": "bool", 76 | "name": "callback", 77 | "var": { 78 | "type": "int", 79 | "name": "code" 80 | } 81 | } 82 | }, 83 | { 84 | "type": "int", 85 | "name": "get_ascii_alt", 86 | "var": [ 87 | { 88 | "type": "char&", 89 | "name": "letter" 90 | }, 91 | { 92 | "fptr": { 93 | "type": "bool", 94 | "name": "callback", 95 | "var": { 96 | "type": "int", 97 | "name": "code" 98 | } 99 | } 100 | } 101 | ] 102 | }, 103 | { 104 | "type": "void", 105 | "name": "process_func", 106 | "qualifier": "static" 107 | }, 108 | { 109 | "type": "void", 110 | "name": "func_ellipsis", 111 | "qualifier": "static", 112 | "var": [ 113 | { 114 | "type": "const char*", 115 | "name": "str" 116 | }, 117 | { 118 | "type": "..." 119 | }, 120 | { 121 | "name": "ignored_var" 122 | } 123 | ] 124 | } 125 | ], 126 | "fptr": [ 127 | { 128 | "type": "int", 129 | "name": "func_ptr" 130 | }, 131 | { 132 | "type": "void", 133 | "name": "f_ptr", 134 | "var": 135 | { 136 | "type": "const int*", 137 | "name": "fmt" 138 | }, 139 | "fptr": 140 | { 141 | "type": "void", 142 | "name": "fptr_arg", 143 | "var": 144 | { 145 | "type": "char&" 146 | } 147 | } 148 | } 149 | ], 150 | "struct": { 151 | "name": "GenericStruct", 152 | "attribute": [ {"name": "C-ABI"}, {"name": "DERIVE-DBG"} ], 153 | "var": [ 154 | { 155 | "type": "int", 156 | "name": "x" 157 | }, 158 | { 159 | "type": "int", 160 | "name": "y" 161 | }, 162 | { 163 | "type": "int", 164 | "qualifier": "OPTION", 165 | "name": "opt_var" 166 | } 167 | ], 168 | "func": 169 | { 170 | "type": "int", 171 | "name": "return_int", 172 | "var": 173 | { 174 | "type": "float", 175 | "name": "arg" 176 | }, 177 | "fptr": 178 | { 179 | "type": "float", 180 | "name": "opt_func", 181 | "qualifier": "OPTION_FPTR" 182 | } 183 | }, 184 | "fptr": [ 185 | { 186 | "type": "void", 187 | "name": "internal_ptr", 188 | "var": { 189 | "type": "const char*", 190 | "name": "arg" 191 | } 192 | }, 193 | { 194 | "type": "void", 195 | "name": "opt_fptr", 196 | "qualifier": "OPTION_FPTR", 197 | "var": { 198 | "type": "const char*", 199 | "name": "arg" 200 | } 201 | } 202 | ], 203 | "struct": { 204 | "name": "SubStruct", 205 | "attribute" : "C-ABI", 206 | "enum": { 207 | "name": "SubEnum", 208 | "attribute": "DERIVE-DBG", 209 | "var": [ 210 | { "name": "SubElement1" }, 211 | { "name": "SubElement2" } 212 | ] 213 | }, 214 | "func": { 215 | "qualifier": "static", 216 | "type": "char", 217 | "name": "do_magic", 218 | "var": { 219 | "type": "int", 220 | "name": "magic_number", 221 | "value": "42" 222 | } 223 | } 224 | } 225 | }, 226 | "some_item": { 227 | "name": "unsupported", 228 | "type": "whatever" 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/string_gen/cpp.rs: -------------------------------------------------------------------------------- 1 | use raw_code::{CodeItem}; 2 | use string_gen::{AUTOGEN_HEADER, AUTOGEN_FOOTER}; 3 | use string_gen::keywords::*; 4 | 5 | pub fn convert(code_items: &Vec, num_tabs: u8, tab_char: char) -> String { 6 | let mut code_str = format!("// {}", AUTOGEN_HEADER); 7 | for i in code_items.iter() { 8 | code_str = format!("{}{}", code_str, parse_item(i, 0, num_tabs, tab_char)); 9 | } 10 | code_str.push_str(format!("// {}", AUTOGEN_FOOTER).as_str()); 11 | code_str 12 | } 13 | 14 | fn parse_item(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 15 | match e.name.as_ref() { 16 | ENUM => make_enum(e, depth, num_tabs, tab_char), 17 | VAR => make_variable(e, depth, num_tabs, tab_char), 18 | FUNC => make_function(e, depth, num_tabs, tab_char, false, false), 19 | FPTR => make_function(e, depth, num_tabs, tab_char, true, false), 20 | STRUCT => make_struct(e, depth, num_tabs, tab_char), 21 | BITFLAGS => make_enum(e, depth, num_tabs, tab_char), 22 | _ => String::from(""), 23 | } 24 | } 25 | 26 | fn make_enum(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 27 | let mut start_indent = String::from(""); 28 | let mut spaces_str = String::from(""); 29 | for _ in 0..num_tabs*depth { 30 | start_indent.push(tab_char); 31 | spaces_str.push(tab_char); 32 | } 33 | for _ in 0..num_tabs { 34 | spaces_str.push(tab_char); 35 | } 36 | 37 | let mut e_name = String::from(""); 38 | for a in e.attributes.iter() { 39 | match a.0.as_ref() { 40 | NAME => if !a.1.is_empty() { e_name = format!(" {}", a.1) }, 41 | _ => {} 42 | } 43 | } 44 | 45 | let mut enum_str = format!("\n{}enum{}{}", start_indent, e_name, " {\n"); 46 | 47 | for c in e.children.iter() { 48 | match c.name.as_ref() { 49 | VAR => { 50 | let mut n = String::from(""); 51 | let mut v = String::from(""); 52 | for a in c.attributes.iter() { 53 | match a.0.as_ref() { 54 | NAME => n = format!("{}", a.1), 55 | VALUE => v = format!("{}", a.1), 56 | _ => {} 57 | }; 58 | } 59 | if v.is_empty() { 60 | enum_str.push_str(format!("{}{},\n", spaces_str, n).as_str()); 61 | } 62 | else { 63 | enum_str.push_str(format!("{}{} = {},\n", spaces_str, n, v).as_str()); 64 | } 65 | }, 66 | // ignore attributes 67 | ATTRIBUTE => {}, 68 | _ => panic!("Illegal enum child: {}", c.name), 69 | } 70 | } 71 | 72 | enum_str.push_str(format!("{}{}", start_indent, "};\n\n").as_str()); 73 | enum_str 74 | } 75 | 76 | fn make_variable(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 77 | let mut start_indent = String::from(""); 78 | for _ in 0..num_tabs*depth { 79 | start_indent.push(tab_char); 80 | } 81 | 82 | let mut n = String::from(""); 83 | let mut t = String::from(""); 84 | let mut v = String::from(""); 85 | let mut q = String::from(""); 86 | for a in e.attributes.iter() { 87 | match a.0.as_ref() { 88 | NAME => n = format!("{}", a.1), 89 | TYPE => t = format!("{}", a.1), 90 | VALUE => if !a.1.is_empty() { v = format!(" = {}", a.1) }, 91 | QUALIFIER => if !a.1.is_empty() { q = format!("{} ", a.1) }, 92 | _ => {} 93 | } 94 | } 95 | 96 | // var type undefined or empty (ignored)? skip it 97 | if !t.is_empty() { 98 | format!("{}{}{} {}{};\n", start_indent, q, t, n, v) 99 | } 100 | else { 101 | String::from("") 102 | } 103 | } 104 | 105 | fn make_function(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char, fptr: bool, is_arg: bool) -> String { 106 | let mut start_indent = String::from(""); 107 | 108 | if !is_arg { 109 | for _ in 0..num_tabs*depth { 110 | start_indent.push(tab_char); 111 | } 112 | } 113 | 114 | let mut f_name = String::from(""); 115 | let mut f_type = String::from(""); 116 | let mut f_qual = String::from(""); 117 | let fptr_prefix = if fptr { "(*" } else { "" }; 118 | let fptr_postfix = if fptr { ")" } else { "" }; 119 | 120 | for a in e.attributes.iter() { 121 | match a.0.as_ref() { 122 | NAME => if !a.1.is_empty() { f_name = format!(" {}{}{}", fptr_prefix, a.1, fptr_postfix) }, 123 | TYPE => f_type = format!("{}", a.1), 124 | QUALIFIER => if !a.1.is_empty() { f_qual = format!("{} ", a.1) }, 125 | _ => {} 126 | } 127 | } 128 | 129 | let mut func_str = format!("{}{}{}{}(", start_indent, f_qual, f_type, f_name); 130 | let comma = e.children.len() > 1; 131 | let mut first_arg = true; 132 | 133 | for c in e.children.iter() { 134 | match c.name.as_ref() { 135 | VAR => { 136 | let mut n = String::from(""); 137 | let mut t = String::from(""); 138 | let mut v = String::from(""); 139 | for a in c.attributes.iter() { 140 | match a.0.as_ref() { 141 | NAME => if !a.1.is_empty() { n = format!(" {}", a.1) }, 142 | TYPE => t = format!("{}{}", if comma && !first_arg { ", " } else { "" }, a.1), 143 | VALUE => if !a.1.is_empty() { v = format!(" = {}", a.1) }, 144 | _ => {} 145 | }; 146 | } 147 | 148 | for vc in c.children.iter() { 149 | match vc.name.as_ref() { 150 | FPTR => { 151 | let separator = if comma && !first_arg { ", " } else { "" }; 152 | let fptr_str = make_function(vc, depth, num_tabs, tab_char, true, true); 153 | func_str.push_str(format!("{}{}", separator, fptr_str).as_str()); 154 | first_arg = false; 155 | }, 156 | _ => {} 157 | } 158 | } 159 | 160 | // var type undefined or empty (ignored)? skip it 161 | if !t.is_empty() { 162 | func_str.push_str(format!("{}{}{}", t, n, v).as_str()); 163 | first_arg = false; 164 | } 165 | }, 166 | FPTR => { 167 | let separator = if comma && !first_arg { ", " } else { "" }; 168 | let fptr_str = make_function(c, depth, num_tabs, tab_char, true, true); 169 | func_str.push_str(format!("{}{}", separator, fptr_str).as_str()); 170 | first_arg = false; 171 | }, 172 | FUNC => panic!("Illegal func child: {} (did you mean {})?", FUNC, FPTR), 173 | _ => panic!("Illegal func child: {}", c.name), 174 | } 175 | } 176 | 177 | if is_arg { 178 | func_str.push_str(")"); 179 | } 180 | else { 181 | func_str.push_str(");\n"); 182 | } 183 | 184 | func_str 185 | } 186 | 187 | fn make_struct(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 188 | let mut start_indent = String::from(""); 189 | let mut spaces_str = String::from(""); 190 | for _ in 0..num_tabs*depth { 191 | start_indent.push(tab_char); 192 | spaces_str.push(tab_char); 193 | } 194 | for _ in 0..num_tabs { 195 | spaces_str.push(tab_char); 196 | } 197 | 198 | let mut s_name = String::from(""); 199 | for a in e.attributes.iter() { 200 | match a.0.as_ref() { 201 | NAME => if !a.1.is_empty() { s_name = format!(" {}", a.1) }, 202 | _ => {} 203 | } 204 | } 205 | 206 | let mut struct_str = format!("\n{}typedef struct{}{}", start_indent, s_name, " {\n"); 207 | 208 | for c in e.children.iter() { 209 | struct_str.push_str(parse_item(c, depth+1, num_tabs, tab_char).as_str()); 210 | } 211 | 212 | struct_str.push_str(format!("{}{}", start_indent, format!("{}{}{}", "}", s_name, ";\n\n")).as_str()); 213 | struct_str 214 | } 215 | -------------------------------------------------------------------------------- /examples/sample.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 54 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 84 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /src/raw_code.rs: -------------------------------------------------------------------------------- 1 | //! Structures and generators for abstract code data. 2 | use std::fmt; 3 | use std::collections::HashMap; 4 | use fmt_code::{FormattedCode, Lang}; 5 | use string_gen::keywords::{NAME, TYPE, VALUE}; 6 | use error::{CodeSpawnError, Result}; 7 | 8 | // (element name, attributes (Vec), depth in API file structure) 9 | #[doc(hidden)] 10 | pub type CodeData = (String, Vec<(String, String)>, u8); 11 | 12 | /// Representation of a single code element. 13 | #[derive(Clone)] 14 | pub struct CodeItem { 15 | /// Name of the element. 16 | pub name: String, 17 | /// List of element's attributes and properties. 18 | pub attributes: Vec<(String, String)>, 19 | /// List of child elements. 20 | pub children: Vec 21 | } 22 | 23 | impl CodeItem { 24 | #[doc(hidden)] 25 | pub fn new(item_name: &str, item_attribs: Vec<(String, String)>) -> CodeItem { 26 | CodeItem { 27 | name: String::from(item_name), 28 | attributes: item_attribs, 29 | children: Vec::::new() 30 | } 31 | } 32 | } 33 | 34 | /// Language-specific configuration map. 35 | pub struct CodeConfig { 36 | /// Name of the configuration (can be `cpp` or `rust`). 37 | pub name: String, 38 | /// Maps abstract type to overriden, language-specific type. Can be empty if no overrides are specified in config. 39 | pub type_dict: HashMap, 40 | /// Maps abstract name to overriden, language-specific name (variables, functions, attributes etc.). Can be empty if no overrides are specified in config. 41 | pub name_dict: HashMap, 42 | /// List of global configuration data for given language. 43 | pub global_cfg: HashMap 44 | } 45 | 46 | impl CodeConfig { 47 | #[doc(hidden)] 48 | pub fn new(cfg_name: &str) -> CodeConfig { 49 | CodeConfig { 50 | name: String::from(cfg_name), 51 | type_dict: HashMap::::new(), 52 | name_dict: HashMap::::new(), 53 | global_cfg: HashMap::::new() 54 | } 55 | } 56 | } 57 | 58 | /// Abstract code data representation. 59 | /// Object of this type can be used to generate desired code. 60 | pub struct RawCode { 61 | /// Map of language-specific configurations. 62 | pub configs: HashMap, 63 | /// A vector of all code elements. 64 | pub elements: Vec, 65 | supported_langs: HashMap 66 | } 67 | 68 | impl RawCode { 69 | #[doc(hidden)] 70 | pub fn new() -> RawCode { 71 | let mut rc = RawCode { 72 | configs: HashMap::::new(), 73 | elements: Vec::::new(), 74 | supported_langs: HashMap::::new() 75 | }; 76 | 77 | rc.supported_langs.insert(Lang::Cpp, String::from("cpp")); 78 | rc.supported_langs.insert(Lang::Rust, String::from("rust")); 79 | 80 | rc 81 | } 82 | 83 | /// Converts `RawCode` into C++ `FormattedCode` 84 | /// 85 | /// # Examples 86 | /// 87 | /// ``` 88 | /// extern crate codespawn; 89 | /// 90 | /// let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 91 | /// let cpp_code = raw_code.to_cpp(); 92 | /// ``` 93 | pub fn to_cpp(&self) -> Result { 94 | self.to_lang(Lang::Cpp) 95 | } 96 | 97 | /// Converts `RawCode` into Rust `FormattedCode` 98 | /// 99 | /// # Examples 100 | /// 101 | /// ``` 102 | /// extern crate codespawn; 103 | /// 104 | /// let raw_code = codespawn::from_xml("examples/sample.xml").unwrap(); 105 | /// let cpp_code = raw_code.to_rust(); 106 | /// ``` 107 | pub fn to_rust(&self) -> Result { 108 | self.to_lang(Lang::Rust) 109 | } 110 | 111 | fn to_lang(&self, lang: Lang) -> Result { 112 | let lang_idx = some_get!(self.supported_langs.get(&lang)); 113 | FormattedCode::new(lang, &self.configs.get(lang_idx), &self.elements) 114 | } 115 | } 116 | 117 | impl fmt::Display for RawCode { 118 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 119 | let _ = write!(f, "\n"); 120 | let _ = write!(f, "*\n"); 121 | for e in self.elements.iter() { 122 | let mut empty_spaces = Vec::::new(); 123 | print_code_item(e, f, 0, &mut empty_spaces); 124 | } 125 | write!(f, "*\n") 126 | } 127 | } 128 | 129 | impl fmt::Display for CodeConfig { 130 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 131 | let _ = write!(f, "Config: {}\n", self.name); 132 | if self.global_cfg.len() > 0 { 133 | let _ = write!(f, "Settings:\n"); 134 | } 135 | for (k, v) in &self.global_cfg { 136 | let _ = write!(f, " {} = {}\n", k, match v.as_str() { 137 | " " => "", " " => "", _ => v.as_str() }); 138 | } 139 | if self.name_dict.len() > 0 { 140 | let _ = write!(f, "Names:\n"); 141 | } 142 | for (k, v) in &self.name_dict { 143 | let _ = write!(f, " {} = {}\n", k, v); 144 | } 145 | if self.type_dict.len() > 0 { 146 | let _ = write!(f, "Types:\n"); 147 | } 148 | for (k, v) in &self.type_dict { 149 | let _ = write!(f, " {} = {}\n", k, v); 150 | } 151 | write!(f, "") 152 | } 153 | } 154 | 155 | // used by Display trait to print the tree of elements 156 | #[doc(hidden)] 157 | pub fn print_code_item(e: &CodeItem, f: &mut fmt::Formatter, depth: u8, empty_spaces: &mut Vec) { 158 | // indentation 159 | for i in 0..depth { 160 | let mut separator = "|"; 161 | for j in empty_spaces.iter() { 162 | if i == *j { 163 | separator = " "; 164 | break; 165 | } 166 | } 167 | let _ = write!(f, "{} ", separator); 168 | } 169 | 170 | let _ = write!(f, "|--{}", e.name); 171 | // print attributes 172 | if e.attributes.len() > 0 { 173 | let _ = write!(f, " ["); 174 | for a in 0..e.attributes.len() { 175 | if a > 0 && a < e.attributes.len() { 176 | let _ = write!(f, ", "); 177 | } 178 | let _ = write!(f, "{}:\"{}\"", e.attributes[a].0, e.attributes[a].1); 179 | } 180 | let _ = write!(f, "]"); 181 | } 182 | let _ = write!(f, "\n"); 183 | 184 | // child processing 185 | if e.children.len() > 0 { 186 | for c in 0..e.children.len() { 187 | if (e.children.len() - c) == 1 { 188 | empty_spaces.push(depth+1); 189 | } 190 | else { 191 | empty_spaces.sort(); 192 | let idx = empty_spaces.binary_search(&(depth+1)); 193 | match idx { Ok(_) => { empty_spaces.remove(idx.unwrap()); }, _ => {} }; 194 | } 195 | print_code_item(&e.children[c], f, depth+1, empty_spaces); 196 | } 197 | 198 | // reset space directory when topmost child is reached 199 | if depth == 1 { 200 | empty_spaces.clear(); 201 | } 202 | } 203 | } 204 | 205 | // create RawCode element from pre-parsed data 206 | #[doc(hidden)] 207 | pub fn generate_raw(data: &Vec, config_data: &Vec) -> Result { 208 | let mut raw_code = RawCode::new(); 209 | 210 | for i in config_data.iter() { 211 | if !raw_code.configs.contains_key(&i.0) { 212 | raw_code.configs.insert(i.0.clone(), CodeConfig::new(&i.0)); 213 | } 214 | 215 | let mut n = String::new(); 216 | let mut t = String::new(); 217 | let mut v = String::new(); 218 | // process all config attributes 219 | for j in i.1.iter() { 220 | match j.0.as_str() { 221 | NAME => n = j.1.clone(), 222 | TYPE => t = j.1.clone(), 223 | VALUE => v = j.1.clone(), 224 | _ => { some_get!(raw_code.configs.get_mut(&i.0)).global_cfg.insert(j.0.clone(), j.1.clone()); } 225 | } 226 | } 227 | 228 | if n.len() > 0 { 229 | some_get!(raw_code.configs.get_mut(&i.0)).name_dict.insert(n.clone(), v.clone()); 230 | } 231 | if t.len() > 0 { 232 | some_get!(raw_code.configs.get_mut(&i.0)).type_dict.insert(t.clone(), v.clone()); 233 | } 234 | } 235 | 236 | for i in data.iter() { 237 | // if at depth 0, it's a root element, so add it to the main list 238 | if i.2 == 0 { 239 | raw_code.elements.push(CodeItem::new(&i.0, i.1.clone())); 240 | } 241 | else { 242 | // recursively process children of a code element 243 | fn process_kids(item: &mut CodeItem, depth: u8, name: &str, attribs: &Vec<(String, String)>) -> Result<()> { 244 | if depth > 1 { process_kids(some_get!(item.children.last_mut()), depth-1, name, attribs)?; } 245 | else { item.children.push(CodeItem::new(name, attribs.clone())); } 246 | 247 | Ok(()) 248 | } 249 | 250 | let parent = some_get!(raw_code.elements.last_mut()); 251 | process_kids(parent, i.2, &i.0, &i.1)?; 252 | } 253 | } 254 | 255 | Ok(raw_code) 256 | } 257 | -------------------------------------------------------------------------------- /src/string_gen/rust.rs: -------------------------------------------------------------------------------- 1 | use raw_code::{CodeItem}; 2 | use string_gen::{AUTOGEN_HEADER, AUTOGEN_FOOTER}; 3 | use string_gen::keywords::*; 4 | 5 | pub fn convert(code_items: &Vec, num_tabs: u8, tab_char: char) -> String { 6 | let mut code_str = format!("// {}", AUTOGEN_HEADER); 7 | for i in code_items.iter() { 8 | code_str = format!("{}{}", code_str, parse_item(i, 0, num_tabs, tab_char, false)); 9 | } 10 | code_str.push_str(format!("// {}", AUTOGEN_FOOTER).as_str()); 11 | code_str 12 | } 13 | 14 | fn parse_item(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char, struct_item: bool) -> String { 15 | match e.name.as_ref() { 16 | ENUM => make_enum(e, depth, num_tabs, tab_char), 17 | VAR => make_variable(e, depth, num_tabs, tab_char, struct_item), 18 | FUNC => make_function(e, depth, num_tabs, tab_char, struct_item, false, false), 19 | FPTR => make_function(e, depth, num_tabs, tab_char, struct_item, true, false), 20 | STRUCT => make_struct(e, depth, num_tabs, tab_char), 21 | BITFLAGS => make_bitflags(e, depth, num_tabs, tab_char), 22 | _ => String::from(""), 23 | } 24 | } 25 | 26 | fn make_enum(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 27 | let mut start_indent = String::from(""); 28 | let mut spaces_str = String::from(""); 29 | for _ in 0..num_tabs*depth { 30 | start_indent.push(tab_char); 31 | spaces_str.push(tab_char); 32 | } 33 | for _ in 0..num_tabs { 34 | spaces_str.push(tab_char); 35 | } 36 | 37 | let mut e_name = String::from(""); 38 | let mut e_attr = String::from(""); 39 | for a in e.attributes.iter() { 40 | match a.0.as_ref() { 41 | NAME => if !a.1.is_empty() { e_name = format!(" {}", a.1) }, 42 | ATTRIBUTE => if !a.1.is_empty() { e_attr = format!("{}{}\n", start_indent, a.1) }, 43 | _ => {} 44 | } 45 | } 46 | 47 | let mut attrib_str = String::from(""); 48 | let mut enum_str = format!("\n{}{}pub enum{}{}", e_attr, start_indent, e_name, " {\n"); 49 | 50 | for c in e.children.iter() { 51 | match c.name.as_ref() { 52 | VAR => { 53 | let mut n = String::from(""); 54 | let mut v = String::from(""); 55 | let mut t = String::from(""); 56 | for a in c.attributes.iter() { 57 | match a.0.as_ref() { 58 | NAME => n = format!("{}", a.1), 59 | VALUE => v = format!("{}", a.1), 60 | TYPE => if !a.1.is_empty() { t = format!(" as {}", a.1) }, 61 | _ => {} 62 | }; 63 | } 64 | if v.is_empty() { 65 | enum_str.push_str(format!("{}{},\n", spaces_str, n).as_str()); 66 | } 67 | else { 68 | enum_str.push_str(format!("{}{} = {}{},\n", spaces_str, n, v, t).as_str()); 69 | } 70 | }, 71 | ATTRIBUTE => { 72 | for a in c.attributes.iter() { 73 | attrib_str.push_str(format!("\n{}{}", start_indent, a.1).as_str()); 74 | } 75 | }, 76 | _ => panic!("Illegal enum child: {}", c.name), 77 | } 78 | } 79 | 80 | enum_str.push_str(format!("{}{}", start_indent, "}\n\n").as_str()); 81 | format!("{}{}", attrib_str, enum_str) 82 | } 83 | 84 | fn make_variable(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char, struct_var: bool) -> String { 85 | let mut start_indent = String::from(""); 86 | for _ in 0..num_tabs*depth { 87 | start_indent.push(tab_char); 88 | } 89 | 90 | let mut n = String::from(""); 91 | let mut t = String::from(""); 92 | let mut v = String::from(""); 93 | let mut q = String::from("const"); // default qualifier for non-struct variable 94 | for a in e.attributes.iter() { 95 | match a.0.as_ref() { 96 | NAME => n = format!("{}", a.1), 97 | TYPE => t = format!("{}", a.1), 98 | VALUE => if !a.1.is_empty() { v = format!(" = {}", a.1) }, 99 | QUALIFIER => q = format!("{}", a.1), 100 | _ => {} 101 | } 102 | } 103 | 104 | // var type undefined or empty (ignored)? skip it 105 | if !t.is_empty() { 106 | // qualifier containing a '<' will be interpreted as a generic, so need to append a '>' 107 | if let Some(_) = q.find('<') { 108 | return format!("{}pub {}: {}{}{}>;\n", start_indent, n, q, t, v); 109 | } 110 | else if !q.is_empty() { 111 | q.push(' '); 112 | } 113 | 114 | if struct_var { 115 | format!("{}pub {}: {}{},\n", start_indent, n, t, v) 116 | } 117 | else { 118 | format!("{}pub {}{}: {}{};\n", start_indent, q, n, t, v) 119 | } 120 | } 121 | else { 122 | String::from("") 123 | } 124 | } 125 | 126 | fn make_function(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char, struct_func: bool, fptr: bool, is_arg: bool) -> String { 127 | let mut start_indent = String::from(""); 128 | 129 | if !is_arg { 130 | for _ in 0..num_tabs*depth { 131 | start_indent.push(tab_char); 132 | } 133 | } 134 | 135 | let mut f_name = String::from(""); 136 | let mut f_type = String::from(""); 137 | let mut f_qual = String::from(""); 138 | let mut opt_suffix = String::from(""); 139 | for a in e.attributes.iter() { 140 | match a.0.as_ref() { 141 | NAME => f_name = format!("{}", a.1), 142 | TYPE => f_type = format!("{}", a.1), 143 | QUALIFIER => if !a.1.is_empty() { f_qual = format!("{}", a.1) }, 144 | _ => {} 145 | } 146 | } 147 | 148 | // function pointers are external by default 149 | f_qual = if fptr && f_qual.is_empty() { String::from("extern") } else { f_qual }; 150 | 151 | // qualifier containing a '<' will be interpreted as a generic, so need to append a '>' 152 | if let Some(_) = f_qual.find('<') { 153 | opt_suffix.push_str(">"); 154 | } 155 | else if !f_qual.is_empty() { 156 | f_qual.push(' '); 157 | } 158 | 159 | // if used as function arg, skip the 'pub' keyword 160 | let is_pub = if !is_arg { "pub " } else { "" }; 161 | 162 | let mut func_str = format!("{}{}{}: {}fn(", start_indent, is_pub, f_name, f_qual); 163 | let comma = e.children.len() > 1; 164 | let mut first_arg = true; 165 | 166 | for c in e.children.iter() { 167 | match c.name.as_ref() { 168 | VAR => { 169 | let mut n = String::from(""); 170 | let mut t = String::from(""); 171 | for a in c.attributes.iter() { 172 | match a.0.as_ref() { 173 | NAME => if !a.1.is_empty() { n = format!("{}: ", a.1) }, 174 | TYPE => t = format!("{}", a.1), 175 | _ => {} 176 | }; 177 | } 178 | 179 | for vc in c.children.iter() { 180 | match vc.name.as_ref() { 181 | FPTR => { 182 | let separator = if comma && !first_arg { ", " } else { "" }; 183 | let fptr_str = make_function(vc, depth, num_tabs, tab_char, false, true, true); 184 | func_str.push_str(format!("{}{}", separator, fptr_str).as_str()); 185 | first_arg = false; 186 | }, 187 | _ => {} 188 | } 189 | } 190 | 191 | // var type undefined or empty (ignored)? skip it 192 | if !t.is_empty() { 193 | func_str.push_str(format!("{}{}{}", if comma && !first_arg { ", " } else { "" }, n, t).as_str()); 194 | first_arg = false; 195 | } 196 | }, 197 | FPTR => { 198 | let separator = if comma && !first_arg { ", " } else { "" }; 199 | let fptr_str = make_function(c, depth, num_tabs, tab_char, struct_func, true, true); 200 | func_str.push_str(format!("{}{}", separator, fptr_str).as_str()); 201 | first_arg = false; 202 | }, 203 | FUNC => panic!("Illegal func child: {} (did you mean {})?", FUNC, FPTR), 204 | _ => panic!("Illegal func child: {}", c.name), 205 | } 206 | } 207 | 208 | let ret_type = if f_type.is_empty() { f_type } else { format!(" -> {}", f_type) }; 209 | let delim = if struct_func { "," } else { ";" }; 210 | 211 | func_str.push_str(format!("){}{}{}{}", ret_type, opt_suffix, 212 | if is_arg { "" } else { delim }, 213 | if is_arg { "" } else { "\n" }).as_str()); 214 | func_str 215 | } 216 | 217 | fn make_struct(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 218 | let mut start_indent = String::from(""); 219 | let mut spaces_str = String::from(""); 220 | for _ in 0..num_tabs*depth { 221 | start_indent.push(tab_char); 222 | spaces_str.push(tab_char); 223 | } 224 | for _ in 0..num_tabs { 225 | spaces_str.push(tab_char); 226 | } 227 | 228 | let mut s_name = String::from(""); 229 | let mut s_attr = String::from(""); 230 | for a in e.attributes.iter() { 231 | match a.0.as_ref() { 232 | NAME => if !a.1.is_empty() { s_name = format!(" {}", a.1) }, 233 | ATTRIBUTE => if !a.1.is_empty() { s_attr = format!("{}{}\n", start_indent, a.1) }, 234 | _ => {} 235 | } 236 | } 237 | 238 | let mut attrib_str = String::from(""); 239 | let mut struct_str = format!("\n{}{}pub struct{}{}", s_attr, start_indent, s_name, " {\n"); 240 | 241 | for c in e.children.iter() { 242 | match c.name.as_ref() { 243 | ATTRIBUTE => { 244 | for a in c.attributes.iter() { 245 | attrib_str.push_str(format!("\n{}{}", start_indent, a.1).as_str()); 246 | } 247 | }, 248 | _ => { struct_str.push_str(parse_item(c, depth+1, num_tabs, tab_char, true).as_str()); } 249 | } 250 | } 251 | 252 | struct_str.push_str(format!("{}{}", start_indent, "}\n\n").as_str()); 253 | format!("{}{}", attrib_str, struct_str) 254 | } 255 | 256 | fn make_bitflags(e: &CodeItem, depth: u8, num_tabs: u8, tab_char: char) -> String { 257 | let mut start_indent = String::from(""); 258 | let mut spaces_str = String::from(""); 259 | for _ in 0..num_tabs*depth { 260 | start_indent.push(tab_char); 261 | spaces_str.push(tab_char); 262 | } 263 | for _ in 0..num_tabs { 264 | spaces_str.push(tab_char); 265 | } 266 | 267 | let mut bf_name = String::from(""); 268 | let mut bf_type = String::from(""); 269 | let mut bf_attr = String::from(""); 270 | for a in e.attributes.iter() { 271 | match a.0.as_ref() { 272 | NAME => if !a.1.is_empty() { bf_name = format!(" {}", a.1) }, 273 | TYPE => if !a.1.is_empty( ) { bf_type = format!(" {}", a.1) }, 274 | ATTRIBUTE => if !a.1.is_empty() { bf_attr = format!("{}{}\n", spaces_str, a.1) }, 275 | _ => {} 276 | } 277 | } 278 | 279 | let mut attrib_str = String::from(""); 280 | let mut bf_str = format!("\n{}{}flags{}:{}{}", bf_attr, spaces_str, bf_name, bf_type, " {\n"); 281 | 282 | for c in e.children.iter() { 283 | match c.name.as_ref() { 284 | VAR => { 285 | let mut n = String::from(""); 286 | let mut v = String::from(""); 287 | let mut t = String::from(""); 288 | for a in c.attributes.iter() { 289 | match a.0.as_ref() { 290 | NAME => n = format!("{}", a.1), 291 | VALUE => v = format!("{}", a.1), 292 | TYPE => if !a.1.is_empty() { t = format!(" as {}", a.1) }, 293 | _ => {} 294 | }; 295 | } 296 | bf_str.push_str(format!("{}{}const {} = {}{},\n", spaces_str, spaces_str, n.to_uppercase(), v, t).as_str()); 297 | }, 298 | ATTRIBUTE => { 299 | for a in c.attributes.iter() { 300 | attrib_str.push_str(format!("\n{}{}", spaces_str, a.1).as_str()); 301 | } 302 | }, 303 | _ => panic!("Illegal bitflag child: {}", c.name), 304 | } 305 | } 306 | 307 | bf_str.push_str(format!("{}{}", spaces_str, "}\n").as_str()); 308 | format!("{}{}{}{}{}{}", start_indent, "bitflags! {", attrib_str, bf_str, start_indent, "}\n\n") 309 | } 310 | --------------------------------------------------------------------------------