diff --git a/Cargo.toml b/Cargo.toml index 210b17b..2770481 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,5 @@ convert_case = "0.6.0" proc-macro2 = "1.0.70" quote = "1.0.33" syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] } +trixy-lang_parser = { path = "./trixy-lang_parser" } +trixy-macros = { path = "./trixy-macros" } diff --git a/src/lib.rs b/src/lib.rs index 6fe5257..e69de29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,118 +0,0 @@ -/* -* Copyright (C) 2023 The Trinitrix Project -* -* This file is part of the Trixy crate for Trinitrix. -* -* Trixy is free software: you can redistribute it and/or modify -* it under the terms of the Lesser GNU General Public License as -* published by the Free Software Foundation, either version 3 of -* the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* and the Lesser GNU General Public License along with this program. -* If not, see . -*/ - - -use command_enum_parsing::DataCommandEnum; -use config::TrixyConfig; -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::parse_macro_input; - -use crate::trixy_lang::parse_trixy_lang; - -mod command_enum_parsing; -mod config; -mod generate; -mod trixy_lang; - -/// This is the heart of the command api -/// It mainly does two things: -/// - Generate a command enum -/// - Wrap the enum in all supported languages (only lua for now) -/// - Generate wrapper lua function for each command -/// - Generate a `add_lua_functions_to_globals` function, which adds -/// the rust wrapper functions to the lua globals. -/// -/// The input and output values of the wrapped functions are derived from the values specified in -/// the input to the `parse_command_enum` proc macro. -/// -/// For example this rust code: -/// ```no_run -/// parse_command_enum! { -/// /// Greets the user -/// greet: fn(String) -> String, -/// } -/// ``` -/// results in this expanded code: -/// ```no_run -/// #[derive(Debug)] -/// pub enum Command { -/// Greet(String), -/// } -/// pub fn add_lua_functions_to_globals( -/// lua: mlua::Lua, -/// tx: tokio::sync::mpsc::Sender, -/// ) -> mlua::Lua { -/// lua.set_app_data(tx); -/// let globals = lua.globals(); -/// { -/// let wrapped_lua_function_greet = lua -/// .create_async_function(greet) -/// .expect( -/// format!( -/// "The function: `{}` should be defined", -/// "greet", -/// ) -/// ); -/// globals -/// .set("greet", wrapped_lua_function_greet) -/// .expect("Setting a static global value should work"); -/// } -/// drop(globals); -/// lua -/// } -/// async fn greet(lua: &mlua::Lua, input: String) -> Result { -/// let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::(); -/// let tx: core::cell::Ref> = lua -/// .app_data_ref() -/// .expect("This should exist, it was set before"); -/// (*tx) -/// .send(Event::CommandEvent(Command::Greet(input.clone()), Some(callback_tx))) -/// .await -/// .expect("This should work, as the receiver is not dropped"); -/// match callback_rx.await { -/// Ok(output) => { -/// return Ok(output); -/// } -/// Err(err) => { -/// return Err(mlua::Error::ExternalError(std::sync::Arc::new(err))); -/// } -/// }; -/// } -/// ``` -#[proc_macro] -pub fn trixy_generate(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as TrixyConfig); - - let trixy_code = parse_trixy_lang(input.get_path()); - todo!() - // // Build the language wrappers - // let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input); - // - // // Build the final enum - // let command_enum = generate::command_enum(&input); - - // let output = quote! { - // #command_enum - // #lua_wrapper - // }; - // output.into() -} diff --git a/trixy-lang_parser/Cargo.toml b/trixy-lang_parser/Cargo.toml index cb22f6b..9aa2415 100644 --- a/trixy-lang_parser/Cargo.toml +++ b/trixy-lang_parser/Cargo.toml @@ -26,5 +26,6 @@ edition = "2021" [dependencies] clap = { version = "4.4.11", features = ["derive"] } +convert_case = "0.6.0" pretty_assertions = "1.4.0" thiserror = "1.0.50" diff --git a/trixy-lang_parser/example/failing_enum_name.tri b/trixy-lang_parser/example/failing_enum_name.tri new file mode 100644 index 0000000..1050587 --- /dev/null +++ b/trixy-lang_parser/example/failing_enum_name.tri @@ -0,0 +1,36 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* This file is part of the Trixy crate for Trinitrix. +* +* Trixy is free software: you can redistribute it and/or modify +* it under the terms of the Lesser GNU General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* and the Lesser GNU General Public License along with this program. +* If not, see . +*/ + + +nasp trinitrix { + /// This enum can't be called Trinitrix, as that's already the name of the namespace + /// (if it's in Pascal case) + enum Trinitrix { + High, + Medium, + Low, + }; + + fn execute_callback(priority: Trinitrix); +} + + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy-lang_parser/example/other_comments.tri b/trixy-lang_parser/example/other_comments.tri new file mode 100644 index 0000000..f17c36f --- /dev/null +++ b/trixy-lang_parser/example/other_comments.tri @@ -0,0 +1,39 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* This file is part of the Trixy crate for Trinitrix. +* +* Trixy is free software: you can redistribute it and/or modify +* it under the terms of the Lesser GNU General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* and the Lesser GNU General Public License along with this program. +* If not, see . +*/ + + +/// other doc comment +fn hi(name: String) -> String; + +/// struct comment +struct ho { + ident: String, + /// also a doc comment + codebase: Vec, +}; + +/// Some doc comment +nasp trinitrix { + fn hi(name: String) -> String; +} + + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy-lang_parser/src/command_spec/checked.rs b/trixy-lang_parser/src/command_spec/checked.rs index 92f1f45..2d0a804 100644 --- a/trixy-lang_parser/src/command_spec/checked.rs +++ b/trixy-lang_parser/src/command_spec/checked.rs @@ -18,7 +18,6 @@ * If not, see . */ - //! This module contains the already type checked types. use std::fmt::Display; @@ -91,7 +90,7 @@ pub struct Function { pub attributes: Vec, } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct Type { pub identifier: Identifier, pub generic_args: Vec, @@ -110,6 +109,12 @@ pub struct DocNamedType { pub attributes: Vec, } +impl From<&DocNamedType> for NamedType { + fn from(value: &DocNamedType) -> Self { + Self { name: value.name.to_owned(), r#type: value.r#type.to_owned() } + } +} + impl From for Identifier { fn from(value: TokenKind) -> Self { match value { @@ -156,6 +161,14 @@ pub struct DocIdentifier { pub attributes: Vec, } +impl From<&DocIdentifier> for Identifier { + fn from(value: &DocIdentifier) -> Self { + Self { + name: value.name.to_owned(), + } + } +} + /// A const version of [Identifier] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ConstIdentifier { diff --git a/trixy-lang_parser/src/command_spec/mod.rs b/trixy-lang_parser/src/command_spec/mod.rs index efec3b6..dfbfab4 100644 --- a/trixy-lang_parser/src/command_spec/mod.rs +++ b/trixy-lang_parser/src/command_spec/mod.rs @@ -21,3 +21,5 @@ pub mod checked; pub mod unchecked; + +pub use checked::*; diff --git a/trixy-lang_parser/src/error.rs b/trixy-lang_parser/src/error.rs index 92bbb7a..4b4657a 100644 --- a/trixy-lang_parser/src/error.rs +++ b/trixy-lang_parser/src/error.rs @@ -18,22 +18,25 @@ * If not, see . */ - use core::fmt; use thiserror::Error; use crate::{ lexing::{error::SpannedLexingError, TokenSpan}, - parsing::checked::error::SpannedParsingError, + parsing::{self}, }; #[derive(Error, Debug)] pub enum TrixyError { #[error(transparent)] Lexing(#[from] SpannedLexingError), + #[error(transparent)] - Parsing(#[from] SpannedParsingError), + Parsing(#[from] parsing::unchecked::error::SpannedParsingError), + + #[error(transparent)] + Processing(#[from] parsing::checked::error::SpannedParsingError), } /// The context of an Error. diff --git a/trixy-lang_parser/src/lib.rs b/trixy-lang_parser/src/lib.rs index ef5e72f..e5f7ba8 100644 --- a/trixy-lang_parser/src/lib.rs +++ b/trixy-lang_parser/src/lib.rs @@ -18,22 +18,23 @@ * If not, see . */ - use error::TrixyError; use crate::lexing::TokenStream; use self::command_spec::checked::CommandSpec; -mod command_spec; +pub mod command_spec; pub mod error; pub mod lexing; pub mod parsing; pub fn parse_trixy_lang(input: &str) -> Result> { let input_tokens = TokenStream::lex(input) - .map_err(|err| Box::new(err.into()))? - .parse() + .map_err(Into::::into)? + .parse_unchecked() + .map_err(Into::::into)? + .process(input.to_owned()) .map_err(Into::::into)?; Ok(input_tokens) } diff --git a/trixy-lang_parser/src/main.rs b/trixy-lang_parser/src/main.rs index d55e593..2004949 100644 --- a/trixy-lang_parser/src/main.rs +++ b/trixy-lang_parser/src/main.rs @@ -18,10 +18,9 @@ * If not, see . */ - use std::{fs, process::exit}; -use trixy_lang_parser::lexing::TokenStream; +use trixy_lang_parser::{lexing::TokenStream, parse_trixy_lang}; use std::path::PathBuf; @@ -56,6 +55,13 @@ pub enum Command { /// The file containing the trixy code to process file: PathBuf, }, + + /// Act on the file as the library would do it + Library { + #[clap(value_parser)] + /// The file containing the trixy code to process + file: PathBuf, + }, } pub fn main() { @@ -127,5 +133,13 @@ pub fn main() { }; println!("{:#?}", processed); } + Command::Library { file } => { + let input = fs::read_to_string(file).unwrap(); + let parsed = parse_trixy_lang(&input).unwrap_or_else(|err| { + eprintln!("{}", err); + exit(1) + }); + println!("{:#?}", parsed); + } } } diff --git a/trixy-lang_parser/src/parsing/checked/error.rs b/trixy-lang_parser/src/parsing/checked/error.rs index d922d63..ae08ff5 100644 --- a/trixy-lang_parser/src/parsing/checked/error.rs +++ b/trixy-lang_parser/src/parsing/checked/error.rs @@ -18,7 +18,6 @@ * If not, see . */ - use thiserror::Error; use std::{error::Error, fmt::Display}; @@ -36,6 +35,18 @@ pub enum ParsingError { TypeNotDeclared { r#type: Identifier, span: TokenSpan }, #[error(transparent)] PreParseError(#[from] OldSpannedParsingError), + #[error("The enum ('{name}') has the same name as it's namespace")] + EnumWithNamespaceName { + name: Identifier, + enum_span: TokenSpan, + namespace_span: TokenSpan, + }, + #[error("The enum ('{name}') has the same name as it's namespace if it's in Pascal case")] + EnumWithNamespaceNamePascal { + name: Identifier, + enum_span: TokenSpan, + namespace_span: TokenSpan, + }, } impl ParsingError { @@ -43,6 +54,8 @@ impl ParsingError { match self { ParsingError::TypeNotDeclared { span, .. } => span, ParsingError::PreParseError(err) => err.source.span(), + ParsingError::EnumWithNamespaceName { enum_span, .. } => enum_span, + ParsingError::EnumWithNamespaceNamePascal { enum_span, .. } => enum_span, } } } @@ -52,6 +65,8 @@ impl AdditionalHelp for ParsingError { match self { ParsingError::TypeNotDeclared { .. } => "This type should have been mentioned in the namespaces above, or in the namespace of this type usage".to_owned(), ParsingError::PreParseError(err) => ErrorContextDisplay::source(err).additional_help(), + ParsingError::EnumWithNamespaceNamePascal {..} + | ParsingError::EnumWithNamespaceName {..} => "Change the name of this Enumeration as the generation process in trixy-macros needs to use this name".to_owned(), } } } diff --git a/trixy-lang_parser/src/parsing/checked/mod.rs b/trixy-lang_parser/src/parsing/checked/mod.rs index ab63d27..74aff7e 100644 --- a/trixy-lang_parser/src/parsing/checked/mod.rs +++ b/trixy-lang_parser/src/parsing/checked/mod.rs @@ -18,9 +18,10 @@ * If not, see . */ - use std::mem; +use convert_case::{Case, Casing}; + use crate::{ command_spec::{ checked::{ @@ -35,7 +36,7 @@ use crate::{ }, }, error::ErrorContext, - lexing::{TokenKind, TokenStream}, + lexing::{TokenKind, TokenSpan}, }; use self::error::{ParsingError, SpannedParsingError}; @@ -51,29 +52,6 @@ struct Parser { original_file: String, } -impl TokenStream { - pub fn parse(mut self) -> Result { - let original_file = mem::take(&mut self.original_file); - - let unchecked = self.parse_unchecked().map_err(|err| { - let span = *err.source.span(); - SpannedParsingError { - source: Box::new(ParsingError::from(err)), - context: ErrorContext::from_span(span, &original_file), - } - })?; - - let checked = Parser { - command_spec: unchecked, - structures: vec![], - enumerations: vec![], - original_file, - } - .parse()?; - Ok(checked) - } -} - impl UncheckedCommandSpec { pub fn process(self, original_file: String) -> Result { let checked = Parser { @@ -111,6 +89,7 @@ impl Parser { &mut self, namespace: UncheckedNamespace, ) -> Result { + let namespace_span = namespace.name.span; let name = match namespace.name.kind { TokenKind::Identifier(ident) => Identifier { name: ident }, // This is not really used, so the value put here does not matter @@ -123,7 +102,7 @@ impl Parser { let mut enumerations = vec![]; let mut enumerations_counter = 0; for enumeration in namespace.enumerations { - enumerations.push(self.process_enumeration(enumeration)?); + enumerations.push(self.process_enumeration(enumeration, &name, namespace_span)?); enumerations_counter += 1; } let mut structures = vec![]; @@ -186,10 +165,27 @@ impl Parser { fn process_enumeration( &mut self, mut enumeration: UncheckedEnumeration, + namespace_name: &Identifier, + namespace_span: TokenSpan, ) -> Result { self.enumerations.push(enumeration.clone()); - let identifier = mem::take(&mut enumeration.identifier.kind).into(); + let enum_span = enumeration.identifier.span; + let identifier: Identifier = mem::take(&mut enumeration.identifier.kind).into(); + if &identifier == namespace_name { + return Err(ParsingError::EnumWithNamespaceName { + name: identifier.clone(), + enum_span, + namespace_span, + }); + } + if identifier.name == namespace_name.name.to_case(Case::Pascal) { + return Err(ParsingError::EnumWithNamespaceNamePascal { + name: identifier.clone(), + enum_span, + namespace_span, + }); + } let mut states = vec![]; for mut state in enumeration.states { diff --git a/trixy-lang_parser/src/parsing/mod.rs b/trixy-lang_parser/src/parsing/mod.rs index 1db68fe..efec3b6 100644 --- a/trixy-lang_parser/src/parsing/mod.rs +++ b/trixy-lang_parser/src/parsing/mod.rs @@ -20,4 +20,4 @@ pub mod checked; -mod unchecked; +pub mod unchecked; diff --git a/trixy-lang_parser/src/parsing/unchecked/mod.rs b/trixy-lang_parser/src/parsing/unchecked/mod.rs index 136c664..bb33524 100644 --- a/trixy-lang_parser/src/parsing/unchecked/mod.rs +++ b/trixy-lang_parser/src/parsing/unchecked/mod.rs @@ -254,7 +254,7 @@ impl Parser { } while self.expect_peek(token![Comma]) { self.expect(token![Comma])?; - if self.expect_peek(token![Ident]) { + if self.expect_peek(token![Ident]) || self.expect_peek(token![DocComment]) { contents.push(self.parse_doc_named_type()?); } else { break; diff --git a/trixy-macros/.gitignore b/trixy-macros/.gitignore new file mode 100644 index 0000000..20c0ba9 --- /dev/null +++ b/trixy-macros/.gitignore @@ -0,0 +1,6 @@ +# build +/target +/result + +# This crate is a library +Cargo.lock diff --git a/trixy-macros/Cargo.toml b/trixy-macros/Cargo.toml new file mode 100644 index 0000000..75fa66e --- /dev/null +++ b/trixy-macros/Cargo.toml @@ -0,0 +1,33 @@ +# Copyright (C) 2023 The Trinitrix Project +# +# This file is part of the Trixy crate for Trinitrix. +# +# Trixy is free software: you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# and the Lesser GNU General Public License along with this program. +# If not, see . + + +[package] +name = "trixy-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +convert_case = "0.6.0" +proc-macro2 = "1.0.70" +quote = "1.0.33" +syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] } +trixy-lang_parser = { path = "../trixy-lang_parser" } diff --git a/trixy-macros/examples/main/api.tri b/trixy-macros/examples/main/api.tri new file mode 120000 index 0000000..98790fc --- /dev/null +++ b/trixy-macros/examples/main/api.tri @@ -0,0 +1 @@ +api_correct.tri \ No newline at end of file diff --git a/trixy-macros/examples/main/api_correct.tri b/trixy-macros/examples/main/api_correct.tri new file mode 100644 index 0000000..44f64f3 --- /dev/null +++ b/trixy-macros/examples/main/api_correct.tri @@ -0,0 +1,40 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* This file is part of the Trixy crate for Trinitrix. +* +* Trixy is free software: you can redistribute it and/or modify +* it under the terms of the Lesser GNU General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* and the Lesser GNU General Public License along with this program. +* If not, see . +*/ + + +nasp trinitrix { + struct Callback { + func: String, + timeout: String, + }; + + enum CallbackPriority { + High, + Medium, + Low, + }; + + fn execute_callback(callback: Callback, priority: CallbackPriority); +} + + + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy-macros/examples/main/api_failing.tri b/trixy-macros/examples/main/api_failing.tri new file mode 100644 index 0000000..5edbc63 --- /dev/null +++ b/trixy-macros/examples/main/api_failing.tri @@ -0,0 +1,30 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* This file is part of the Trixy crate for Trinitrix. +* +* Trixy is free software: you can redistribute it and/or modify +* it under the terms of the Lesser GNU General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* and the Lesser GNU General Public License along with this program. +* If not, see . +*/ + + +fn print(message: CommandTransferValue); + +nasp trinitrix { {} + fn hi honner(name: String) -> String; ; +} + + +// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing: +// vim: syntax=rust diff --git a/trixy-macros/examples/main/main.rs b/trixy-macros/examples/main/main.rs new file mode 100644 index 0000000..d7be117 --- /dev/null +++ b/trixy-macros/examples/main/main.rs @@ -0,0 +1,9 @@ +use trixy_macros::trixy_generate; + +fn main() { + trixy_generate! { + path: "./examples/main/api.tri" + languages: rust, lua, c + generate_debug: false + } +} diff --git a/src/config/mod.rs b/trixy-macros/src/config/mod.rs similarity index 73% rename from src/config/mod.rs rename to trixy-macros/src/config/mod.rs index 2fb6dd7..84907db 100644 --- a/src/config/mod.rs +++ b/trixy-macros/src/config/mod.rs @@ -18,24 +18,25 @@ * If not, see . */ - //! This module is responsible for parsing the config passed to the macro call: //! For example: //! ```no_run //! trixy_generate! { -//! path: ./trintrix_command_interface.tri +//! path: "./path/to/trixy/file.tri" //! languages: rust, lua, c +//! generate_debug: false //! } //! ``` use std::path::PathBuf; use proc_macro2::Ident; -use syn::{parse::Parse, punctuated::Punctuated, LitStr, Result, Token}; +use syn::{parse::Parse, punctuated::Punctuated, LitBool, LitStr, Result, Token}; mod kw { syn::custom_keyword!(path); syn::custom_keyword!(languages); + syn::custom_keyword!(generate_debug); } #[derive(Debug)] @@ -63,6 +64,15 @@ struct Path { raw: PathBuf, } +#[derive(Debug)] +struct GenerateDebug { + #[allow(dead_code)] + generate_debug: kw::generate_debug, + #[allow(dead_code)] + colon: Token![:], + raw: bool, +} + #[derive(Debug)] pub struct TrixyConfig { /// The Path to the base command interface config file @@ -70,10 +80,17 @@ pub struct TrixyConfig { /// The languages the commands should be exposed in languages: Languages, + + /// Should the macro generate Debug trait implementation for each enum + /// These are very useful but completely obscure the `cargo expand` ouput + generate_debug: GenerateDebug, } impl TrixyConfig { - pub fn get_path(&self) -> PathBuf { - self.path.raw + pub fn get_path(&self) -> &std::path::Path { + &self.path.raw + } + pub fn generate_debug(&self) -> bool { + self.generate_debug.raw } } @@ -82,6 +99,20 @@ impl Parse for TrixyConfig { Ok(Self { path: input.parse()?, languages: input.parse()?, + generate_debug: input.parse()?, + }) + } +} + +impl Parse for GenerateDebug { + fn parse(input: syn::parse::ParseStream) -> Result { + let kw: kw::generate_debug = input.parse()?; + let colon: Token![:] = input.parse()?; + let raw = input.parse::()?.value(); + Ok(Self { + generate_debug: kw, + colon, + raw, }) } } diff --git a/src/generate/command_enum/mod.rs b/trixy-macros/src/generate.old/command_enum/mod.rs similarity index 100% rename from src/generate/command_enum/mod.rs rename to trixy-macros/src/generate.old/command_enum/mod.rs diff --git a/src/generate/lua_wrapper/lua_functions_to_globals/mod.rs b/trixy-macros/src/generate.old/lua_wrapper/lua_functions_to_globals/mod.rs similarity index 100% rename from src/generate/lua_wrapper/lua_functions_to_globals/mod.rs rename to trixy-macros/src/generate.old/lua_wrapper/lua_functions_to_globals/mod.rs diff --git a/src/generate/lua_wrapper/mod.rs b/trixy-macros/src/generate.old/lua_wrapper/mod.rs similarity index 100% rename from src/generate/lua_wrapper/mod.rs rename to trixy-macros/src/generate.old/lua_wrapper/mod.rs diff --git a/src/generate/lua_wrapper/rust_wrapper_functions/mod.rs b/trixy-macros/src/generate.old/lua_wrapper/rust_wrapper_functions/mod.rs similarity index 100% rename from src/generate/lua_wrapper/rust_wrapper_functions/mod.rs rename to trixy-macros/src/generate.old/lua_wrapper/rust_wrapper_functions/mod.rs diff --git a/src/generate/mod.rs b/trixy-macros/src/generate.old/mod.rs similarity index 100% rename from src/generate/mod.rs rename to trixy-macros/src/generate.old/mod.rs diff --git a/trixy-macros/src/generate/host/mod.rs b/trixy-macros/src/generate/host/mod.rs new file mode 100644 index 0000000..7b3623f --- /dev/null +++ b/trixy-macros/src/generate/host/mod.rs @@ -0,0 +1,280 @@ +//! This module is responsible for generating the rust code used to interface with the api. +//! That includes the structs and enums declared in the trixy file and the enum used to describe the +//! command being executed. + +use std::cell::OnceCell; + +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use trixy_lang_parser::command_spec::{ + Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, + NamedType, Namespace, Structure, Type, +}; + +use crate::config::TrixyConfig; + +thread_local! {static DEBUG: OnceCell = OnceCell::new();} + +/// This function turns, for example, the following trixy input into this rust code: +/// ```rust +/// nasp trinitrix { +/// struct Callback { +/// func: Function, +/// timeout: Integer, +/// }; +/// +/// enum CallbackPriority { +/// High, +/// Medium, +/// Low, +/// }; +/// +/// fn execute_callback(callback: Callback, priority: CallbackPriority); +/// } +/// ``` +/// ```rust +/// #[derive(Debug)] +/// pub enum Commands { +/// Trinitrix(trinitrix::Trinitrix), +/// } +/// pub mod trinitrix { +/// #[allow(non_camel_case)] +/// #[derive(Debug)] +/// struct Callback { +/// func: Function, +/// timeout: Integer, +/// } +/// #[allow(non_camel_case)] +/// #[derive(Debug)] +/// enum CallbackPriority { +/// High, +/// Medium, +/// Low, +/// } +/// #[derive(Debug)] +/// pub enum Trinitrix { +/// #[allow(non_camel_case)] +/// execute_callback { callback: Callback, priority: CallbackPriority }, +/// } +/// } +/// ``` +pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 { + DEBUG.with(|d| { + d.set(if config.generate_debug() { + quote! { + #[derive(Debug)] + } + } else { + TokenStream2::default() + }) + .expect("The cell should always be empty at this point"); + }); + + let modules: TokenStream2 = trixy.namespaces.iter().map(namespace_to_module).collect(); + let structures: TokenStream2 = trixy.structures.iter().map(structure_to_rust).collect(); + let enumerations: TokenStream2 = trixy.enumerations.iter().map(enumeration_to_rust).collect(); + let functions: Vec = trixy.functions.iter().map(function_to_rust).collect(); + let namespace_modules: Vec = trixy + .namespaces + .iter() + .map(namespace_to_module_enum) + .collect(); + + let debug = get_debug_sate(); + + quote! { + #structures + #enumerations + #debug + pub enum Commands { + #(#functions,)* + #(#namespace_modules),* + } + #modules + } +} + +fn namespace_to_module(namespace: &Namespace) -> TokenStream2 { + let ident = identifier_to_rust(&namespace.name); + let enum_ident = format_ident!("{}", &namespace.name.name.to_case(Case::Pascal)); + + let doc_comments: TokenStream2 = namespace + .attributes + .iter() + .map(attribute_to_doc_comment) + .collect(); + let structures: TokenStream2 = namespace.structures.iter().map(structure_to_rust).collect(); + let enumerations: TokenStream2 = namespace + .enumerations + .iter() + .map(enumeration_to_rust) + .collect(); + let functions: Vec = namespace.functions.iter().map(function_to_rust).collect(); + let namespace_modules: Vec = namespace + .namespaces + .iter() + .map(namespace_to_module_enum) + .collect(); + let namespaces: TokenStream2 = namespace + .namespaces + .iter() + .map(namespace_to_module) + .collect(); + + let debug = get_debug_sate(); + quote! { + #doc_comments + pub mod #ident { + #structures + #enumerations + #debug + pub enum #enum_ident { + #(#functions,)* + #(#namespace_modules),* + } + #namespaces + } + } +} + +fn namespace_to_module_enum(namespace: &Namespace) -> TokenStream2 { + let pascal_ident = format_ident!("{}", namespace.name.name.to_case(Case::Pascal)); + let ident = identifier_to_rust(&namespace.name); + quote! { + #pascal_ident(#ident :: #pascal_ident) + } +} + +fn attribute_to_doc_comment(attribute: &Attribute) -> TokenStream2 { + let Attribute::doc(doc_comment) = attribute; + + quote! { + #[doc = #doc_comment] + } +} + +fn get_debug_sate() -> TokenStream2 { + let debug = DEBUG.with(|d| { + d.get() + .expect("The cell should contain something at this point") + .clone() + }); + debug +} + +fn function_to_rust(function: &Function) -> TokenStream2 { + let doc_comments: TokenStream2 = function + .attributes + .iter() + .map(attribute_to_doc_comment) + .collect(); + let ident = identifier_to_rust(&function.identifier); + let inputs: Vec = function.inputs.iter().map(named_type_to_rust).collect(); + + quote! { + #doc_comments + #[allow(non_camel_case_types)] + #ident {#(#inputs),*} + } +} + +fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 { + let doc_comments: TokenStream2 = enumeration + .attributes + .iter() + .map(attribute_to_doc_comment) + .collect(); + let ident = identifier_to_rust(&enumeration.identifier); + let states: Vec = enumeration + .states + .iter() + .map(doc_identifier_to_rust) + .collect(); + + let debug = get_debug_sate(); + + quote! { + #doc_comments + #[allow(non_camel_case)] + #debug + enum #ident { + #(#states),* + } + } +} + +fn structure_to_rust(structure: &Structure) -> TokenStream2 { + let doc_comments: TokenStream2 = structure + .attributes + .iter() + .map(attribute_to_doc_comment) + .collect(); + let ident = identifier_to_rust(&structure.identifier); + let contents: Vec = structure + .contents + .iter() + .map(doc_named_type_to_rust) + .collect(); + + let debug = get_debug_sate(); + + quote! { + #doc_comments + #[allow(non_camel_case)] + #debug + struct #ident { + #(#contents),* + } + } +} + +fn doc_identifier_to_rust(doc_identifier: &DocIdentifier) -> TokenStream2 { + let doc_comments: TokenStream2 = doc_identifier + .attributes + .iter() + .map(attribute_to_doc_comment) + .collect(); + let identifier = identifier_to_rust(&doc_identifier.into()); + + quote! { + #doc_comments + #identifier + } +} + +fn identifier_to_rust(identifier: &Identifier) -> TokenStream2 { + let ident = format_ident!("{}", &identifier.name); + quote! { + #ident + } +} + +fn doc_named_type_to_rust(doc_named_type: &DocNamedType) -> TokenStream2 { + let doc_comments: TokenStream2 = doc_named_type + .attributes + .iter() + .map(attribute_to_doc_comment) + .collect(); + let named_type = named_type_to_rust(&doc_named_type.into()); + quote! { + #doc_comments + #named_type + } +} + +fn named_type_to_rust(named_type: &NamedType) -> TokenStream2 { + let ident = identifier_to_rust(&named_type.name); + let r#type = type_to_rust(&named_type.r#type); + quote! { + #ident : #r#type + } +} + +fn type_to_rust(r#type: &Type) -> TokenStream2 { + let ident = identifier_to_rust(&r#type.identifier); + let generics: Vec = r#type.generic_args.iter().map(type_to_rust).collect(); + quote! { + #ident <#(#generics),*> + } +} diff --git a/trixy-macros/src/generate/mod.rs b/trixy-macros/src/generate/mod.rs new file mode 100644 index 0000000..5361e40 --- /dev/null +++ b/trixy-macros/src/generate/mod.rs @@ -0,0 +1 @@ +pub mod host; diff --git a/trixy-macros/src/lib.rs b/trixy-macros/src/lib.rs new file mode 100644 index 0000000..91124e4 --- /dev/null +++ b/trixy-macros/src/lib.rs @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2023 The Trinitrix Project +* +* This file is part of the Trixy crate for Trinitrix. +* +* Trixy is free software: you can redistribute it and/or modify +* it under the terms of the Lesser GNU General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* and the Lesser GNU General Public License along with this program. +* If not, see . +*/ + +use std::fs; + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse_macro_input; +use trixy_lang_parser::parse_trixy_lang; + +use crate::config::TrixyConfig; + +mod config; +mod generate; + +/// This is the heart of Trixy +/// It mainly does one thing: +/// - Generate a tree of modules from the input trixy file +/// +#[proc_macro] +pub fn trixy_generate(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as TrixyConfig); + + let source_code = fs::read_to_string(input.get_path()).unwrap_or_else(|err| { + panic! {"Can't read file at path: '{}'. The Error is: '{}'", input.get_path().display(), err}; + }); + + let trixy_code = parse_trixy_lang(&source_code).unwrap_or_else(|err| { + panic! {"Parsing of the trixy file failed: \n{}", err} + }); + + // Build the final rust hosting code + let host_rust_code = generate::host::generate(&trixy_code, &input); + + let output = quote! { + #host_rust_code + }; + output.into() +}