Compare commits
3 Commits
1d49bdf2cf
...
7d1a41aca9
Author | SHA1 | Date |
---|---|---|
Benedikt Peetz | 7d1a41aca9 | |
Benedikt Peetz | 0744c8468d | |
Benedikt Peetz | ed96a50bd4 |
|
@ -2,8 +2,10 @@
|
|||
/target
|
||||
/result
|
||||
|
||||
# direnv
|
||||
# dev env
|
||||
.direnv
|
||||
.ccls-cache
|
||||
|
||||
|
||||
# trixy is a library
|
||||
Cargo.lock
|
||||
|
|
|
@ -102,7 +102,7 @@ comments:
|
|||
commenter:
|
||||
type: line
|
||||
comment_char: "//"
|
||||
trailing_lines: 2
|
||||
trailing_lines: 1
|
||||
|
||||
- extensions:
|
||||
- rs
|
||||
|
@ -124,7 +124,7 @@ comments:
|
|||
start_block_char: "/*\n"
|
||||
end_block_char: "*/\n"
|
||||
per_line_char: "*"
|
||||
trailing_lines: 2
|
||||
trailing_lines: 1
|
||||
|
||||
# In this case extension is singular and a single string extension is provided.
|
||||
- extension: html
|
||||
|
@ -139,7 +139,7 @@ comments:
|
|||
commenter:
|
||||
type: line
|
||||
comment_char: ";;;"
|
||||
trailing_lines: 2
|
||||
trailing_lines: 1
|
||||
|
||||
- extensions:
|
||||
- ebnf
|
||||
|
@ -148,7 +148,7 @@ comments:
|
|||
start_block_char: "#(*\n"
|
||||
end_block_char: "#*)\n"
|
||||
per_line_char: "#"
|
||||
trailing_lines: 2
|
||||
trailing_lines: 1
|
||||
|
||||
# The extension string "any" is special and so will match any file
|
||||
# extensions. Commenter configurations are always checked in the
|
||||
|
@ -162,4 +162,4 @@ comments:
|
|||
commenter:
|
||||
type: line
|
||||
comment_char: '#'
|
||||
trailing_lines: 2
|
||||
trailing_lines: 1
|
||||
|
|
|
@ -22,11 +22,12 @@ name = "trixy"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.6.0"
|
||||
log = "0.4.20"
|
||||
proc-macro2 = "1.0.70"
|
||||
quote = "1.0.33"
|
||||
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
|
||||
thiserror = "1.0.51"
|
||||
# trixy-lang_parser = { path = "./trixy-lang_parser" }
|
||||
# trixy-macros = { path = "./trixy-macros" }
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
|
||||
{
|
||||
description = "A rust crate used to generate multi-language apis for your
|
||||
application";
|
||||
|
@ -122,6 +123,10 @@
|
|||
cargo-edit
|
||||
cargo-expand
|
||||
|
||||
# Used to format the c header files
|
||||
libclang
|
||||
gdb
|
||||
|
||||
ebnf2pdf.outputs.packages."${system}".default
|
||||
];
|
||||
inherit nativeBuildInputs buildInputs;
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
//! This module is responsible for parsing the config passed to the macro call:
|
||||
//! For example:
|
||||
//! ```no_run
|
||||
//! trixy_generate! {
|
||||
//! path: ./trintrix_command_interface.tri
|
||||
//! languages: rust, lua, c
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use proc_macro2::Ident;
|
||||
use syn::{parse::Parse, punctuated::Punctuated, LitStr, Result, Token};
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(path);
|
||||
syn::custom_keyword!(languages);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Language {
|
||||
Rust,
|
||||
Lua,
|
||||
C,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Languages {
|
||||
#[allow(dead_code)]
|
||||
languages: kw::languages,
|
||||
#[allow(dead_code)]
|
||||
colon: Token![:],
|
||||
raw: Punctuated<Language, Token![,]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Path {
|
||||
#[allow(dead_code)]
|
||||
path: kw::path,
|
||||
#[allow(dead_code)]
|
||||
colon: Token![:],
|
||||
raw: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrixyConfig {
|
||||
/// The Path to the base command interface config file
|
||||
path: Path,
|
||||
|
||||
/// The languages the commands should be exposed in
|
||||
languages: Languages,
|
||||
}
|
||||
impl TrixyConfig {
|
||||
pub fn get_path(&self) -> PathBuf {
|
||||
self.path.raw
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for TrixyConfig {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
path: input.parse()?,
|
||||
languages: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Path {
|
||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
||||
let path: kw::path = input.parse()?;
|
||||
let colon: Token![:] = input.parse()?;
|
||||
let raw = PathBuf::from(input.parse::<LitStr>()?.value());
|
||||
Ok(Self { path, colon, raw })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Languages {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let languages: kw::languages = input.parse()?;
|
||||
let colon: Token![:] = input.parse()?;
|
||||
let raw = Punctuated::<Language, Token![,]>::parse_separated_nonempty(input)?;
|
||||
Ok(Self {
|
||||
languages,
|
||||
colon,
|
||||
raw,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Language {
|
||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
||||
let ident: Ident = input.parse()?;
|
||||
match &ident.to_string()[..] {
|
||||
"rust" | "Rust" => Ok(Self::Rust),
|
||||
"lua" | "Lua" => Ok(Self::Lua),
|
||||
"c" | "C" => Ok(Self::C),
|
||||
other => Err(input.error(format!(
|
||||
"The language: `{}` is not a registered language!",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::ffi::c_char;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypeConversionError {
|
||||
#[error("Can't convert this ('{got:#?}') string into a valid rust string; Remember that these must be valid utf-8")]
|
||||
String { got: *const c_char },
|
||||
|
||||
#[error("You passed a null pointer to the conversion function!")]
|
||||
NullPointer,
|
||||
}
|
106
src/lib.rs
106
src/lib.rs
|
@ -18,101 +18,27 @@
|
|||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Trixy contains the types used by the [trixy-macros] crate to provide ffi safe types
|
||||
use std::{ffi::c_char, ops::Deref};
|
||||
|
||||
use command_enum_parsing::DataCommandEnum;
|
||||
use config::TrixyConfig;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
use crate::trixy_lang::parse_trixy_lang;
|
||||
pub mod error;
|
||||
pub mod traits;
|
||||
|
||||
mod command_enum_parsing;
|
||||
mod config;
|
||||
mod generate;
|
||||
mod trixy_lang;
|
||||
// NOTE(@soispha): All types specified here *MUST* be include in the BASE_TYPES constant, otherwise
|
||||
// they are not usable from Trixy code <2023-12-25>
|
||||
|
||||
/// 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<Event>,
|
||||
/// ) -> 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<String, mlua::Error> {
|
||||
/// let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::<String>();
|
||||
/// let tx: core::cell::Ref<tokio::sync::mpsc::Sender<Event>> = 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);
|
||||
#[repr(C)]
|
||||
pub struct String(*const c_char);
|
||||
|
||||
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);
|
||||
/// These are the "primitive" types used in Trixy, you can use any of them to create new structures
|
||||
pub const BASE_TYPES: [&'static str; 2] = ["String", "u8"];
|
||||
|
||||
// let output = quote! {
|
||||
// #command_enum
|
||||
// #lua_wrapper
|
||||
// };
|
||||
// output.into()
|
||||
pub fn to_c_name<T: Deref<Target = str>>(rust_type: T) -> TokenStream {
|
||||
match &*rust_type {
|
||||
"String" => quote!(const char*),
|
||||
other => panic! {"'{}' is not a vaild type name!", other},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use core::slice;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error::Error,
|
||||
ffi::{c_char, c_int},
|
||||
ptr,
|
||||
};
|
||||
|
||||
use log::{error, warn};
|
||||
|
||||
use crate::error::TypeConversionError;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! convert {
|
||||
($input:expr) => {
|
||||
match $input.try_into() {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
trixy::traits::errno::set(err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// This code is heavily inspired by: https://michael-f-bryan.github.io/rust-ffi-guide/errors/return_types.html
|
||||
thread_local! {
|
||||
static LAST_ERROR: RefCell<Option<Box<TypeConversionError>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Update the most recent error, clearing whatever may have been there before.
|
||||
pub fn set(error: TypeConversionError) {
|
||||
error!("Setting LAST_ERROR: {}", error);
|
||||
|
||||
{
|
||||
// Print a pseudo-backtrace for this error, following back each error's
|
||||
// cause until we reach the root error.
|
||||
let mut cause = error.source();
|
||||
while let Some(parent_err) = cause {
|
||||
warn!("Caused by: {}", parent_err);
|
||||
cause = parent_err.source();
|
||||
}
|
||||
}
|
||||
|
||||
LAST_ERROR.with(|prev| {
|
||||
*prev.borrow_mut() = Some(Box::new(error));
|
||||
});
|
||||
}
|
||||
|
||||
/// Retrieve the most recent error, clearing it in the process.
|
||||
pub fn take_last_error() -> Option<Box<TypeConversionError>> {
|
||||
LAST_ERROR.with(|prev| prev.borrow_mut().take())
|
||||
}
|
||||
|
||||
pub const ERROR_FUNCTIONS: &'static str = r#"
|
||||
/// Calculate the number of bytes in the last error's error message **not**
|
||||
/// including any trailing `null` characters.
|
||||
extern int last_error_length();
|
||||
|
||||
/// Write the most recent error message into a caller-provided buffer as a UTF-8
|
||||
/// string, returning the number of bytes written.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This writes a **UTF-8** string into the buffer. Windows users may need to
|
||||
/// convert it to a UTF-16 "unicode" afterwards.
|
||||
///
|
||||
/// If there are no recent errors then this returns `0` (because we wrote 0
|
||||
/// bytes). `-1` is returned if there are any errors, for example when passed a
|
||||
/// null pointer or a buffer of insufficient size.
|
||||
extern int last_error_message(char* buffer, int length);
|
||||
"#;
|
||||
|
||||
/// Calculate the number of bytes in the last error's error message **not**
|
||||
/// including any trailing `null` characters.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn last_error_length() -> c_int {
|
||||
LAST_ERROR.with(|prev| match *prev.borrow() {
|
||||
Some(ref err) => err.to_string().len() as c_int + 1,
|
||||
None => 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write the most recent error message into a caller-provided buffer as a UTF-8
|
||||
/// string, returning the number of bytes written.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This writes a **UTF-8** string into the buffer. Windows users may need to
|
||||
/// convert it to a UTF-16 "unicode" afterwards.
|
||||
///
|
||||
/// If there are no recent errors then this returns `0` (because we wrote 0
|
||||
/// bytes). `-1` is returned if there are any errors, for example when passed a
|
||||
/// null pointer or a buffer of insufficient size.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn last_error_message(buffer: *mut c_char, length: c_int) -> c_int {
|
||||
if buffer.is_null() {
|
||||
warn!("Null pointer passed into last_error_message() as the buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
let last_error = match take_last_error() {
|
||||
Some(err) => err,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
let error_message = last_error.to_string();
|
||||
|
||||
let buffer = slice::from_raw_parts_mut(buffer as *mut u8, length as usize);
|
||||
|
||||
if error_message.len() >= buffer.len() {
|
||||
warn!("Buffer provided for writing the last error message is too small.");
|
||||
warn!(
|
||||
"Expected at least {} bytes but got {}",
|
||||
error_message.len() + 1,
|
||||
buffer.len()
|
||||
);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ptr::copy_nonoverlapping(
|
||||
error_message.as_ptr(),
|
||||
buffer.as_mut_ptr(),
|
||||
error_message.len(),
|
||||
);
|
||||
|
||||
// Add a trailing null so people using the string as a `char *` don't
|
||||
// accidentally read into garbage.
|
||||
buffer[error_message.len()] = 0;
|
||||
|
||||
error_message.len() as c_int
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
use crate::error::TypeConversionError;
|
||||
|
||||
pub mod errno;
|
||||
|
||||
impl TryFrom<crate::String> for &str {
|
||||
type Error = crate::error::TypeConversionError;
|
||||
|
||||
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
|
||||
let ptr = value.0;
|
||||
if ptr.is_null() {
|
||||
Err(TypeConversionError::NullPointer)
|
||||
} else {
|
||||
// SAFETY: We checked for null, which is a huge upside other things that we simply hope
|
||||
// for:
|
||||
// - null terminated
|
||||
// - actually be a valid pointer (`(void*) 2` is *valid* c but not a *valid* pointer)
|
||||
// - be contained in a single allocated object
|
||||
// - the memory must obviously not be mutated, while this &str exists
|
||||
// - the null terminator must be within `isize::MAX` from the start position
|
||||
let str = unsafe { CStr::from_ptr(ptr) };
|
||||
str.to_str()
|
||||
.map_err(|_err| crate::error::TypeConversionError::String { got: value.0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<crate::String> for String {
|
||||
type Error = crate::error::TypeConversionError;
|
||||
|
||||
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
|
||||
let str: &str = value.try_into()?;
|
||||
Ok(str.to_owned())
|
||||
}
|
||||
}
|
|
@ -25,6 +25,17 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4.11", features = ["derive"] }
|
||||
pretty_assertions = "1.4.0"
|
||||
clap = { version = "4.4.11", features = ["derive"], optional = true }
|
||||
convert_case = "0.6.0"
|
||||
thiserror = "1.0.50"
|
||||
trixy = { path = "../../trixy" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
[features]
|
||||
build-binary = ["clap"]
|
||||
|
||||
[[bin]]
|
||||
name = "trixy-parser"
|
||||
required-features = ["build-binary"]
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
This crate contains a parser (and lexer) for the Trixy language.
|
||||
The corresponding grammar is in the grammar file [here](./docs/grammar.ebnf) encoded in [Extended Backus-Naur Form](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form).
|
||||
|
||||
## Testing
|
||||
A binary (`trixy-parser`) exists, which provides easy access to the different library
|
||||
parsing steps
|
||||
|
||||
## Docs
|
||||
Run `./generate_docs` to turn the grammar file into railroad diagrams.
|
||||
|
|
|
@ -19,3 +19,9 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
// an empty comment:
|
||||
//
|
||||
|
||||
//
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
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
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/// other doc comment
|
||||
fn hi(name: String) -> String;
|
||||
|
||||
/// struct comment
|
||||
struct ho {
|
||||
ident: String,
|
||||
/// also a doc comment
|
||||
codebase: Vec<String>,
|
||||
};
|
||||
|
||||
/// 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
|
|
@ -19,9 +19,11 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
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 +58,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 +136,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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,26 +19,16 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
//! This module contains the already type checked types.
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
use crate::lexing::TokenKind;
|
||||
|
||||
use super::unchecked;
|
||||
|
||||
/// These are the "primitive" types used in trixy, you can use any of them to create new structures
|
||||
pub const BASE_TYPES: [ConstIdentifier; 8] = [
|
||||
Identifier::from("Integer"),
|
||||
Identifier::from("Float"),
|
||||
Identifier::from("Decimal"),
|
||||
Identifier::from("String"),
|
||||
Identifier::from("Function"),
|
||||
Identifier::from("Option"),
|
||||
Identifier::from("Result"),
|
||||
Identifier::from("Vec"),
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Namespace {
|
||||
pub name: Identifier,
|
||||
|
@ -91,11 +81,33 @@ pub struct Function {
|
|||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub struct Type {
|
||||
pub identifier: Identifier,
|
||||
pub generic_args: Vec<Type>,
|
||||
}
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let ident = &self.identifier.name;
|
||||
|
||||
f.write_str(ident)?;
|
||||
if !self.generic_args.is_empty() {
|
||||
f.write_char('<')?;
|
||||
let mut first_run = true;
|
||||
for arg in &self.generic_args {
|
||||
if !first_run {
|
||||
f.write_str(", ")?;
|
||||
} else {
|
||||
first_run = false;
|
||||
}
|
||||
write!(f, "{}", arg)?;
|
||||
}
|
||||
f.write_char('>')
|
||||
} else {
|
||||
f.write_str("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct NamedType {
|
||||
|
@ -110,6 +122,15 @@ pub struct DocNamedType {
|
|||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
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<TokenKind> for Identifier {
|
||||
fn from(value: TokenKind) -> Self {
|
||||
match value {
|
||||
|
@ -156,6 +177,14 @@ pub struct DocIdentifier {
|
|||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -19,5 +19,9 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
pub mod checked;
|
||||
pub mod unchecked;
|
||||
|
||||
pub use checked::*;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
//! This module contains the not type checked types.
|
||||
//! These are generated on the first pass of the parser, to be later converted into the checked
|
||||
//! ones.
|
||||
|
|
|
@ -19,21 +19,27 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use std::{error::Error, fmt::Display};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use self::{error::SpannedLexingError, tokenizer::Tokenizer};
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use crate::lexing::{Keyword, Token, TokenKind, TokenSpan};
|
||||
|
||||
use super::TokenStream;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
// This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html
|
||||
|
||||
use crate::{
|
||||
|
@ -148,6 +150,18 @@ impl<'a> Tokenizer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// checks if the next char in the input str is a newline
|
||||
fn end_of_line(text: &str) -> bool {
|
||||
let next = text.chars().next();
|
||||
if let Some('\n') = next {
|
||||
true
|
||||
} else if let Some('\r') = next {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
||||
// every token starts with two slashes
|
||||
let slashes: &str = &text[..2];
|
||||
|
@ -156,6 +170,9 @@ fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
|||
} else {
|
||||
let text: &str = &text[2..];
|
||||
if let Some('/') = text.chars().next() {
|
||||
if end_of_line(&text) {
|
||||
Ok((TokenKind::DocComment("".to_owned()), 1 + 3))
|
||||
} else {
|
||||
let text = &text[1..];
|
||||
let (doc_comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
|
||||
|
||||
|
@ -163,11 +180,15 @@ fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
|||
let doc_comment = doc_comment.trim_start();
|
||||
let doc_comment = doc_comment.trim_end();
|
||||
|
||||
return Ok((
|
||||
Ok((
|
||||
TokenKind::DocComment(doc_comment.to_owned()),
|
||||
chars_read + 3,
|
||||
));
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if end_of_line(&text) {
|
||||
Ok((TokenKind::Comment("".to_owned()), 1 + 2))
|
||||
} else {
|
||||
let (comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
|
||||
|
||||
// trim whitespace
|
||||
|
@ -177,6 +198,8 @@ fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
|||
Ok((TokenKind::Comment(comment.to_owned()), chars_read + 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize_ident(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
||||
let (got, chars_read) = take_while(text, |ch| ch == '_' || ch.is_alphanumeric())?;
|
||||
|
|
|
@ -19,21 +19,25 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
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<CommandSpec, Box<TrixyError>> {
|
||||
let input_tokens = TokenStream::lex(input)
|
||||
.map_err(|err| Box::new(err.into()))?
|
||||
.parse()
|
||||
.map_err(Into::<TrixyError>::into)?
|
||||
.parse_unchecked()
|
||||
.map_err(Into::<TrixyError>::into)?
|
||||
.process(input.to_owned())
|
||||
.map_err(Into::<TrixyError>::into)?;
|
||||
Ok(input_tokens)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
@ -36,6 +38,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 +57,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 +68,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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,13 +19,18 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use std::mem;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use trixy::BASE_TYPES;
|
||||
|
||||
use crate::{
|
||||
command_spec::{
|
||||
checked::{
|
||||
CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, NamedType,
|
||||
Namespace, Structure, Type, BASE_TYPES,
|
||||
Namespace, Structure, Type,
|
||||
},
|
||||
unchecked::{
|
||||
CommandSpec as UncheckedCommandSpec, DocNamedType as UncheckedDocNamedType,
|
||||
|
@ -35,7 +40,7 @@ use crate::{
|
|||
},
|
||||
},
|
||||
error::ErrorContext,
|
||||
lexing::{TokenKind, TokenStream},
|
||||
lexing::{TokenKind, TokenSpan},
|
||||
};
|
||||
|
||||
use self::error::{ParsingError, SpannedParsingError};
|
||||
|
@ -51,29 +56,6 @@ struct Parser {
|
|||
original_file: String,
|
||||
}
|
||||
|
||||
impl TokenStream {
|
||||
pub fn parse(mut self) -> Result<CommandSpec, SpannedParsingError> {
|
||||
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<CommandSpec, SpannedParsingError> {
|
||||
let checked = Parser {
|
||||
|
@ -111,6 +93,7 @@ impl Parser {
|
|||
&mut self,
|
||||
namespace: UncheckedNamespace,
|
||||
) -> Result<Namespace, ParsingError> {
|
||||
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 +106,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 +169,27 @@ impl Parser {
|
|||
fn process_enumeration(
|
||||
&mut self,
|
||||
mut enumeration: UncheckedEnumeration,
|
||||
namespace_name: &Identifier,
|
||||
namespace_span: TokenSpan,
|
||||
) -> Result<Enumeration, ParsingError> {
|
||||
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 {
|
||||
|
@ -262,7 +262,7 @@ impl Parser {
|
|||
.iter()
|
||||
.map(|r#enum| Into::<Identifier>::into(r#enum.identifier.kind.clone()))
|
||||
.any(|ident| ident == identifier)
|
||||
&& !BASE_TYPES.iter().any(|ident| ident.name == identifier.name)
|
||||
&& !BASE_TYPES.iter().any(|ident| ident == &identifier.name)
|
||||
{
|
||||
return Err(ParsingError::TypeNotDeclared {
|
||||
r#type: identifier,
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use crate::command_spec::checked::{
|
||||
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
|
||||
NamedType, Namespace, Structure, Type,
|
||||
|
|
|
@ -19,5 +19,7 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
pub mod checked;
|
||||
mod unchecked;
|
||||
pub mod unchecked;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use std::{error::Error, fmt::Display};
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use std::mem;
|
||||
|
||||
use crate::{
|
||||
|
@ -254,7 +256,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;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::{
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# build
|
||||
/target
|
||||
/result
|
||||
|
||||
# This crate is a library
|
||||
Cargo.lock
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
#
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
[package]
|
||||
name = "trixy-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
convert_case = "0.6.0"
|
||||
prettyplease = "0.2.15"
|
||||
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 = { path = "../../trixy" }
|
|
@ -0,0 +1,7 @@
|
|||
# build
|
||||
/target
|
||||
/result
|
||||
/dist
|
||||
|
||||
# This crate is a library
|
||||
Cargo.lock
|
|
@ -0,0 +1,35 @@
|
|||
# Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
#
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
[package]
|
||||
name = "main-example"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
trixy = { path = "../../../../trixy" }
|
||||
env_logger = { version = "0.10.1" }
|
||||
log = "0.4.20"
|
||||
|
||||
[build-dependencies]
|
||||
trixy-macros = {path = "../../../trixy-macros"}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
use trixy_macros::config::TrixyConfig;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=./src/api.tri");
|
||||
TrixyConfig::new("callback")
|
||||
.trixy_path("./src/api.tri")
|
||||
.dist_dir_path("./dist")
|
||||
.generate_debug(true)
|
||||
.generate();
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "../dist/interface.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
int two;
|
||||
} a_t;
|
||||
|
||||
int main(void) {
|
||||
fn_alone("hi");
|
||||
|
||||
if (!fn_alone(0x0)) {
|
||||
int error_length = last_error_length();
|
||||
char *error = malloc(error_length);
|
||||
last_error_message(error, error_length);
|
||||
printf("Encountered error: %s\n", error);
|
||||
free(error);
|
||||
}
|
||||
|
||||
one.fn_one();
|
||||
// one.two.fn_two();
|
||||
//
|
||||
// two_t two = one.two;
|
||||
// two.three.fn_three();
|
||||
// one.two.three.fn_three();
|
||||
// one_two_fn_two();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
// nasp trinitrix {
|
||||
// struct Callback {
|
||||
// func: String,
|
||||
// timeout: String,
|
||||
// };
|
||||
//
|
||||
// enum CallbackPriority {
|
||||
// High,
|
||||
// Medium,
|
||||
// Low,
|
||||
// };
|
||||
//
|
||||
// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
||||
// }
|
||||
|
||||
/// Some doc comment
|
||||
fn fn_alone(input: String);
|
||||
/// Some doc comment
|
||||
nasp one {
|
||||
/// Some doc comment
|
||||
fn fn_one();
|
||||
nasp two {
|
||||
fn fn_two();
|
||||
nasp three {
|
||||
fn fn_three();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
|
||||
// vim: syntax=rust
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
macro_rules! callback {
|
||||
($cmd:expr) => {{
|
||||
println!("{:#?}", $cmd);
|
||||
}};
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/api.rs"));
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
fn main() {
|
||||
let input = include_str!(concat!(env!("OUT_DIR"), "/interface.h"));
|
||||
println!("{}", input);
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
//! This module is responsible for the config passed to trixy.
|
||||
//! It works using the popular builder syntax:
|
||||
//! ```no_run
|
||||
//! use trixy_macros::config::{Language, TrixyConfig};
|
||||
//!# fn main() {
|
||||
//! let config = TrixyConfig::new()
|
||||
//! .set_input_path("path/to/trixy/api.tri")
|
||||
//! .set_output_path("api.rs")
|
||||
//! .set_languages(vec![Language::Rust, Language::C])
|
||||
//! .set_generate_debug(false);
|
||||
//!# }
|
||||
//! ```
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Language {
|
||||
Rust,
|
||||
C,
|
||||
Lua,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TrixyConfig {
|
||||
/// The Path to the base command interface config file
|
||||
pub trixy_path: PathBuf,
|
||||
|
||||
/// The name of the outputted host code (rust and c bindings)
|
||||
/// This file is written in $OUT_DIR
|
||||
pub host_code_name: PathBuf,
|
||||
/// The name of the c header
|
||||
/// This file is written in $OUT_DIR
|
||||
pub c_header_name: PathBuf,
|
||||
|
||||
/// The path from the root to the distribution directory.
|
||||
/// Things like the c headers are copied in this dir
|
||||
/// When this is [None] no dist dir will be generated
|
||||
pub dist_dir_path: Option<PathBuf>,
|
||||
|
||||
/// Whether to check if the dist dir is empty before writing something to it
|
||||
pub check_dist_dir: bool,
|
||||
|
||||
/// Should the macro generate Debug trait implementation for each enum
|
||||
/// These are very useful but completely obscure the `cargo expand` output
|
||||
pub generate_debug: bool,
|
||||
|
||||
/// This function is executed whenever an API function is called.
|
||||
/// The only argument is the command (encoded as the `Commands`) enum
|
||||
/// which is represented by this function
|
||||
/// Because of rust limitations this is supposed to be the name of a macro acting as said
|
||||
/// function. This macro must be in scope of the generated code
|
||||
pub callback_function: String,
|
||||
}
|
||||
|
||||
impl TrixyConfig {
|
||||
pub fn new<T: Into<String>>(callback_function: T) -> Self {
|
||||
Self {
|
||||
callback_function: callback_function.into(),
|
||||
host_code_name: "api.rs".into(),
|
||||
c_header_name: "interface.h".into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trixy_path<T: Into<PathBuf>>(self, input_path: T) -> Self {
|
||||
Self {
|
||||
trixy_path: input_path.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_debug(self, generate_debug: bool) -> Self {
|
||||
Self {
|
||||
generate_debug,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dist_dir_path<T: Into<PathBuf>>(self, dist_dir_path: T) -> Self {
|
||||
Self {
|
||||
dist_dir_path: Some(dist_dir_path.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_dist_dir(self, check_dist_dir: bool) -> Self {
|
||||
Self {
|
||||
check_dist_dir,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_code_name<T: Into<PathBuf>>(self, output_path: T) -> Self {
|
||||
Self {
|
||||
host_code_name: output_path.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn c_header_name<T: Into<PathBuf>>(self, output_path: T) -> Self {
|
||||
Self {
|
||||
c_header_name: output_path.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn callback_function<T: Into<String>>(self, callback_function: T) -> Self {
|
||||
Self {
|
||||
callback_function: callback_function.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
//! This module generates the c header
|
||||
//! It works by firstly listing the functions and then by grouping them into structures, effectively
|
||||
//! simulating namespaces in c.
|
||||
|
||||
use proc_macro2::{Ident, Punct, Spacing, TokenStream as TokenStream2};
|
||||
use quote::{format_ident, quote};
|
||||
use trixy_lang_parser::command_spec::{
|
||||
Attribute, CommandSpec, Function, Identifier, NamedType, Namespace,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::TrixyConfig,
|
||||
generate::{c_api::mangle_c_function_ident, identifier_to_rust, type_to_rust},
|
||||
};
|
||||
|
||||
const BEGIN_HEADER_GUARD: &'static str = r"#ifndef TRIXY_MAIN_HEADER
|
||||
#define TRIXY_MAIN_HEADER";
|
||||
const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
|
||||
|
||||
/// This function acts as the core transformative aspect, turning this trixy code into the
|
||||
/// following c header:
|
||||
///
|
||||
/// *Trixy:*
|
||||
/// ```text
|
||||
/// fn fn_alone();
|
||||
/// nasp one {
|
||||
/// fn fn_one();
|
||||
/// nasp two {
|
||||
/// fn fn_two();
|
||||
/// nasp three {
|
||||
/// fn fn_three();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// *C header:*
|
||||
/// ```text
|
||||
/// #ifndef TRIXY_MAIN_HEADER
|
||||
/// #define TRIXY_MAIN_HEADER
|
||||
///
|
||||
/// extern int fn_alone();
|
||||
/// extern int one_fn_one();
|
||||
/// extern int one_two_fn_two();
|
||||
/// extern int one_two_three_fn_three();
|
||||
///
|
||||
/// typedef struct {
|
||||
/// void (*fn_three)(void);
|
||||
/// } three_t;
|
||||
/// typedef struct {
|
||||
/// void (*fn_two)(void);
|
||||
/// three_t three;
|
||||
/// } two_t;
|
||||
/// typedef struct {
|
||||
/// void (*fn_one)(void);
|
||||
/// two_t two;
|
||||
/// } one_t;
|
||||
///
|
||||
/// const three_t three = {
|
||||
/// .fn_three = one_two_three_fn_three,
|
||||
/// };
|
||||
/// const two_t two = {
|
||||
/// .fn_two = one_two_fn_two,
|
||||
/// .three = three,
|
||||
/// };
|
||||
/// const one_t one = {
|
||||
/// .fn_one = one_fn_one,
|
||||
/// .two = two,
|
||||
/// };
|
||||
/// #endif // ifndef TRIXY_MAIN_HEADER
|
||||
/// ```
|
||||
pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> String {
|
||||
let functions: String = trixy
|
||||
.functions
|
||||
.iter()
|
||||
.map(|r#fn| function_to_header(r#fn, &[]))
|
||||
.collect();
|
||||
let namespaces: String = trixy
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_header(nasp, &vec![]))
|
||||
.collect();
|
||||
|
||||
let type_defs: String = trixy
|
||||
.namespaces
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|nasp| namespace_to_full_typedef(nasp))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let struct_initializer: TokenStream2 = trixy
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_full_struct_init(nasp, &vec![]))
|
||||
.collect();
|
||||
|
||||
let output = quote! {
|
||||
#struct_initializer
|
||||
}
|
||||
.to_string();
|
||||
format!(
|
||||
"{}\n\n{}\n\n{}\n{}\n{}\n{}\n\n{}",
|
||||
BEGIN_HEADER_GUARD,
|
||||
trixy::traits::errno::ERROR_FUNCTIONS,
|
||||
functions,
|
||||
namespaces,
|
||||
type_defs,
|
||||
output,
|
||||
END_HEADER_GUARD
|
||||
)
|
||||
}
|
||||
|
||||
fn function_to_header(function: &Function, namespaces: &[&Identifier]) -> String {
|
||||
let doc_comments: String = function
|
||||
.attributes
|
||||
.iter()
|
||||
.map(attribute_to_doc_comment)
|
||||
.collect::<String>();
|
||||
let ident = mangle_c_function_ident(function, namespaces);
|
||||
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_c).collect();
|
||||
|
||||
let output = quote! {
|
||||
extern int #ident(#(#inputs),*);
|
||||
};
|
||||
format!("{}{}\n", doc_comments, output)
|
||||
}
|
||||
|
||||
fn attribute_to_doc_comment(attribute: &Attribute) -> String {
|
||||
let Attribute::doc(doc_comment) = attribute;
|
||||
format!("/// {}\n", doc_comment)
|
||||
}
|
||||
|
||||
fn named_type_to_c(named_type: &NamedType) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&named_type.name);
|
||||
let r#type = type_to_rust(&named_type.r#type);
|
||||
let c_type = trixy::to_c_name(r#type.to_string());
|
||||
quote! {
|
||||
#c_type #ident
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> TokenStream2 {
|
||||
let mut input_namespaces = namespaces.clone();
|
||||
input_namespaces.push(&nasp.name);
|
||||
|
||||
let ident = identifier_to_rust(&nasp.name);
|
||||
let type_ident = format_ident!("{}_t", ident.to_string());
|
||||
let functions: TokenStream2 = nasp
|
||||
.functions
|
||||
.iter()
|
||||
.map(|r#fn| function_to_struct_init(r#fn, &input_namespaces))
|
||||
.collect();
|
||||
let namespaces: TokenStream2 = nasp
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(namespace_to_struct_init)
|
||||
.collect();
|
||||
|
||||
let next_namespace: TokenStream2 = nasp
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_full_struct_init(nasp, &input_namespaces))
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
#next_namespace
|
||||
|
||||
const #type_ident #ident = {
|
||||
#functions
|
||||
#namespaces
|
||||
};
|
||||
}
|
||||
}
|
||||
fn function_to_struct_init(function: &Function, namespaces: &[&Identifier]) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&function.identifier);
|
||||
let full_ident = mangle_c_function_ident(function, namespaces);
|
||||
|
||||
quote! {
|
||||
. #ident = #full_ident,
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_to_struct_init(namespace: &Namespace) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&namespace.name);
|
||||
|
||||
quote! {
|
||||
. #ident = #ident ,
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_to_full_typedef(nasp: &Namespace) -> String {
|
||||
let ident = format_ident!("{}_t", nasp.name.name);
|
||||
let doc_comments = nasp
|
||||
.attributes
|
||||
.iter()
|
||||
.map(attribute_to_doc_comment)
|
||||
.collect::<String>();
|
||||
|
||||
let functions: TokenStream2 = nasp
|
||||
.functions
|
||||
.iter()
|
||||
.map(|r#fn| function_to_typedef(r#fn))
|
||||
.collect();
|
||||
let namespaces: TokenStream2 = nasp
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_typedef(nasp))
|
||||
.collect();
|
||||
let next_namespace: String = nasp
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_full_typedef(nasp))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let namespace = quote! {
|
||||
typedef struct {
|
||||
#functions
|
||||
#namespaces
|
||||
} #ident;
|
||||
};
|
||||
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
|
||||
}
|
||||
|
||||
fn function_to_typedef(function: &Function) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&function.identifier);
|
||||
|
||||
let output = if let Some(output) = &function.output {
|
||||
let output = output.to_string();
|
||||
quote! { #output, }
|
||||
} else {
|
||||
TokenStream2::default()
|
||||
};
|
||||
|
||||
let inputs: Vec<TokenStream2> = if function.inputs.is_empty() {
|
||||
vec![quote! { void }]
|
||||
} else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
int (* #ident ) (#output #(#inputs),*);
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_to_typedef(namespace: &Namespace) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&namespace.name);
|
||||
let type_ident = format_ident!("{}_t", ident.to_string());
|
||||
|
||||
quote! {
|
||||
#type_ident #ident ;
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_to_header(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> String {
|
||||
let mut nasps = namespaces.clone();
|
||||
nasps.push(&nasp.name);
|
||||
|
||||
let functions: String = nasp
|
||||
.functions
|
||||
.iter()
|
||||
.map(|r#fn| function_to_header(r#fn, &nasps))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
let namespaces: String = nasp
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_header(nasp, &nasps))
|
||||
.collect();
|
||||
|
||||
format! {"{}\n{}", functions, namespaces}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use trixy_lang_parser::command_spec::{CommandSpec, Function, Identifier, NamedType, Namespace};
|
||||
|
||||
use crate::{
|
||||
config::TrixyConfig,
|
||||
generate::{
|
||||
c_api::mangle_c_function_ident, identifier_to_rust, named_type_to_rust, type_to_rust,
|
||||
},
|
||||
};
|
||||
|
||||
/// This function generates the main c API provided by Trixy.
|
||||
/// This works for example like this:
|
||||
/// Turning this:
|
||||
/// ```text
|
||||
/// nasp trinitrix {
|
||||
/// struct Callback {
|
||||
/// func: String,
|
||||
/// timeout: String,
|
||||
/// };
|
||||
///
|
||||
/// enum CallbackPriority {
|
||||
/// High,
|
||||
/// Medium,
|
||||
/// Low,
|
||||
/// };
|
||||
///
|
||||
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
||||
/// }
|
||||
/// ```
|
||||
/// to this:
|
||||
/// ```no_run
|
||||
/// pub extern "C" fn exectute_callback(callback: Callback, priority: CallbackPriority) {
|
||||
/// /* Here we simply call your handler function, with the command of the function */
|
||||
/// }
|
||||
/// ```
|
||||
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
|
||||
let functions: TokenStream2 = trixy
|
||||
.functions
|
||||
.iter()
|
||||
.map(|r#fn| function_to_c(r#fn, &config, &vec![]))
|
||||
.collect();
|
||||
let namespaced_functions: TokenStream2 = trixy
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_c(&nasp, &config, &vec![]))
|
||||
.collect();
|
||||
quote! {
|
||||
#functions
|
||||
#namespaced_functions
|
||||
}
|
||||
}
|
||||
|
||||
fn namespace_to_c(
|
||||
namespace: &Namespace,
|
||||
config: &TrixyConfig,
|
||||
namespaces: &Vec<&Identifier>,
|
||||
) -> TokenStream2 {
|
||||
let mut namespaces = namespaces.clone();
|
||||
namespaces.push(&namespace.name);
|
||||
|
||||
let functions: TokenStream2 = namespace
|
||||
.functions
|
||||
.iter()
|
||||
.map(|r#fn| function_to_c(&r#fn, &config, &namespaces))
|
||||
.collect();
|
||||
let additional_functions: TokenStream2 = namespace
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|nasp| namespace_to_c(&nasp, &config, &namespaces))
|
||||
.collect();
|
||||
quote! {
|
||||
#functions
|
||||
#additional_functions
|
||||
}
|
||||
}
|
||||
|
||||
fn function_to_c(
|
||||
function: &Function,
|
||||
config: &TrixyConfig,
|
||||
namespaces: &Vec<&Identifier>,
|
||||
) -> TokenStream2 {
|
||||
let ident = mangle_c_function_ident(function, namespaces);
|
||||
let inputs: Vec<TokenStream2> = function
|
||||
.inputs
|
||||
.iter()
|
||||
.map(named_type_to_rust_trixy)
|
||||
.collect();
|
||||
|
||||
let callback_function = format_ident!("{}", config.callback_function);
|
||||
|
||||
let command_value: TokenStream2 = function_path_to_rust(&namespaces, &function);
|
||||
|
||||
if let Some(r#type) = &function.output {
|
||||
let output = type_to_rust(&r#type);
|
||||
quote! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn #ident(output: *mut #output, #(#inputs),*) -> core::ffi::c_int {
|
||||
#callback_function ! (#command_value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn #ident(#(#inputs),*) -> core::ffi::c_int {
|
||||
#callback_function ! (#command_value);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn named_type_to_rust_trixy(named_type: &NamedType) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&named_type.name);
|
||||
let type_ident = type_to_rust(&named_type.r#type);
|
||||
quote! {
|
||||
#ident : trixy:: #type_ident
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns a function in namespaces to the generated host enum path:
|
||||
/// *trixy code:*
|
||||
/// ```text
|
||||
/// fn fn_alone();
|
||||
/// nasp one {
|
||||
/// fn fn_one();
|
||||
/// nasp two {
|
||||
/// fn fn_two(input: String);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// *rust enum path for fn_alone:*
|
||||
/// ```no_run
|
||||
/// Commands::fn_alone
|
||||
/// ```
|
||||
/// *rust enum path for fn_one:*
|
||||
/// ```no_run
|
||||
/// // `Commands` is just the name for the top-level namespace
|
||||
/// Commands::One(one::One(
|
||||
/// One::fn_one
|
||||
/// ))
|
||||
/// ```
|
||||
/// *rust enum path for fn_two:*
|
||||
/// ```no_run
|
||||
/// Commands::One(one::One(
|
||||
/// one::two::Two(one::two::Two(
|
||||
/// Two::fn_two {input: String}
|
||||
/// ))))
|
||||
/// ```
|
||||
fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) -> TokenStream2 {
|
||||
let function_ident = {
|
||||
let ident = format_ident!("{}", function.identifier.name);
|
||||
if function.inputs.is_empty() {
|
||||
quote! {
|
||||
#ident
|
||||
}
|
||||
} else {
|
||||
let inputs: Vec<TokenStream2> = function
|
||||
.inputs
|
||||
.iter()
|
||||
.map(named_type_to_rust_assignment)
|
||||
.collect();
|
||||
quote! {
|
||||
#ident { #(#inputs),* }
|
||||
}
|
||||
}
|
||||
};
|
||||
if namespaces.is_empty() {
|
||||
quote! {
|
||||
Commands:: #function_ident
|
||||
}
|
||||
} else {
|
||||
let nasp_pascal_ident = format_ident!(
|
||||
"{}",
|
||||
namespaces
|
||||
.last()
|
||||
.expect("We checked")
|
||||
.name
|
||||
.to_case(Case::Pascal)
|
||||
);
|
||||
|
||||
let namespace_path = nasps_to_path(namespaces);
|
||||
|
||||
let function_call = if !function.inputs.is_empty() {
|
||||
let inputs: Vec<TokenStream2> =
|
||||
function.inputs.iter().map(named_type_to_rust).collect();
|
||||
quote! {
|
||||
#namespace_path :: #nasp_pascal_ident :: #function_ident { #(#inputs),* }
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#namespace_path :: #nasp_pascal_ident :: #function_ident
|
||||
}
|
||||
};
|
||||
|
||||
let output: TokenStream2 = namespaces
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.fold(function_call, |acc, (index, nasp)| {
|
||||
nasp_path_one_part(nasp, &acc, &namespaces, index)
|
||||
});
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn named_type_to_rust_assignment(named_type: &NamedType) -> TokenStream2 {
|
||||
let ident = identifier_to_rust(&named_type.name);
|
||||
quote! {
|
||||
#ident : trixy::convert!(#ident)
|
||||
}
|
||||
}
|
||||
|
||||
/// This function add a namespace component to the [input] value like so:
|
||||
/// (taking the example from the [function_path_to_rust] function)
|
||||
/// ```text
|
||||
/// one::two::Two::fn_two [= <input>]
|
||||
/// ->
|
||||
/// one::One::Two(<input>) [= <input>]
|
||||
/// ->
|
||||
/// Commands::One(<input>) [= <input>]
|
||||
/// ```
|
||||
fn nasp_path_one_part(
|
||||
current_nasp: &Identifier,
|
||||
input: &TokenStream2,
|
||||
namespaces: &Vec<&Identifier>,
|
||||
index: usize,
|
||||
) -> TokenStream2 {
|
||||
let namespaces_to_do = &namespaces[..index];
|
||||
|
||||
let ident_pascal = format_ident!("{}", current_nasp.name.to_case(Case::Pascal));
|
||||
|
||||
if index == 0 {
|
||||
quote! {
|
||||
Commands :: #ident_pascal ( #input )
|
||||
}
|
||||
} else {
|
||||
let ident_pascal_next = format_ident!(
|
||||
"{}",
|
||||
namespaces_to_do
|
||||
.last()
|
||||
.expect("We checked the index")
|
||||
.name
|
||||
.to_case(Case::Pascal)
|
||||
);
|
||||
let namespace_path = nasps_to_path(namespaces_to_do);
|
||||
quote! {
|
||||
#namespace_path :: #ident_pascal_next :: #ident_pascal ( #input )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nasps_to_path(namespaces: &[&Identifier]) -> TokenStream2 {
|
||||
namespaces
|
||||
.iter()
|
||||
.fold(TokenStream2::default(), |acc, nasp| {
|
||||
let ident = format_ident!("{}", nasp.name);
|
||||
if acc.is_empty() {
|
||||
quote! {
|
||||
#ident
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#acc :: #ident
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
use proc_macro2::Ident;
|
||||
use quote::format_ident;
|
||||
use trixy_lang_parser::command_spec::{Function, Identifier};
|
||||
|
||||
pub mod header;
|
||||
pub mod host;
|
||||
|
||||
pub fn mangle_c_function_ident(function: &Function, namespaces: &[&Identifier]) -> Ident {
|
||||
let namespace_str = namespaces.iter().fold(String::default(), |acc, nasp| {
|
||||
if acc.is_empty() {
|
||||
nasp.name.clone()
|
||||
} else {
|
||||
format!("{}_{}", acc, nasp.name)
|
||||
}
|
||||
});
|
||||
|
||||
if namespace_str.is_empty() {
|
||||
format_ident!("{}", &function.identifier.name)
|
||||
} else {
|
||||
format_ident!("{}_{}", namespace_str, &function.identifier.name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
//! 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, Namespace,
|
||||
Structure,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::TrixyConfig,
|
||||
generate::{identifier_to_rust, named_type_to_rust},
|
||||
};
|
||||
|
||||
thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
|
||||
|
||||
/// This function turns, for example, the following trixy input into this rust code:
|
||||
/// ```text
|
||||
/// nasp trinitrix {
|
||||
/// struct Callback {
|
||||
/// func: String,
|
||||
/// timeout: String,
|
||||
/// };
|
||||
///
|
||||
/// enum CallbackPriority {
|
||||
/// High,
|
||||
/// Medium,
|
||||
/// Low,
|
||||
/// };
|
||||
///
|
||||
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
||||
/// }
|
||||
/// // wrong but helps with syntax highlight:
|
||||
/// // vim: syntax=rust
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// #[derive(Debug)]
|
||||
/// pub enum Commands {
|
||||
/// Trinitrix(trinitrix::Trinitrix),
|
||||
/// }
|
||||
/// pub mod trinitrix {
|
||||
/// #[allow(non_camel_case_types)]
|
||||
/// #[derive(Debug)]
|
||||
/// struct Callback {
|
||||
/// func: String,
|
||||
/// timeout: String,
|
||||
/// }
|
||||
/// #[allow(non_camel_case_types)]
|
||||
/// #[derive(Debug)]
|
||||
/// enum CallbackPriority {
|
||||
/// High,
|
||||
/// Medium,
|
||||
/// Low,
|
||||
/// }
|
||||
/// #[derive(Debug)]
|
||||
/// pub enum Trinitrix {
|
||||
/// #[allow(non_camel_case_types)]
|
||||
/// 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<TokenStream2> = trixy.functions.iter().map(function_to_rust).collect();
|
||||
let namespace_modules: Vec<TokenStream2> = 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<TokenStream2> = namespace.functions.iter().map(function_to_rust).collect();
|
||||
let namespace_modules: Vec<TokenStream2> = 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<TokenStream2> = function.inputs.iter().map(named_type_to_rust).collect();
|
||||
|
||||
if inputs.is_empty() {
|
||||
quote! {
|
||||
#doc_comments
|
||||
#[allow(non_camel_case_types)]
|
||||
#ident
|
||||
}
|
||||
} else {
|
||||
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<TokenStream2> = enumeration
|
||||
.states
|
||||
.iter()
|
||||
.map(doc_identifier_to_rust)
|
||||
.collect();
|
||||
|
||||
let debug = get_debug_sate();
|
||||
|
||||
quote! {
|
||||
#doc_comments
|
||||
#[allow(non_camel_case_types)]
|
||||
#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<TokenStream2> = structure
|
||||
.contents
|
||||
.iter()
|
||||
.map(doc_named_type_to_rust)
|
||||
.collect();
|
||||
|
||||
let debug = get_debug_sate();
|
||||
|
||||
quote! {
|
||||
#doc_comments
|
||||
#[allow(non_camel_case_types)]
|
||||
#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 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use trixy_lang_parser::command_spec::{CommandSpec, Identifier, NamedType, Type};
|
||||
|
||||
use crate::config::TrixyConfig;
|
||||
|
||||
pub mod c_api;
|
||||
pub mod host;
|
||||
|
||||
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
|
||||
// Build the language wrappers
|
||||
let c_host_api = c_api::host::generate(&trixy, &config);
|
||||
|
||||
// Build the final rust hosting code
|
||||
let host_rust_code = host::generate(&trixy, &config);
|
||||
|
||||
quote! {
|
||||
#host_rust_code
|
||||
/* C api */
|
||||
#c_host_api
|
||||
}
|
||||
}
|
||||
|
||||
fn identifier_to_rust(identifier: &Identifier) -> TokenStream2 {
|
||||
let ident = format_ident!("{}", &identifier.name);
|
||||
quote! {
|
||||
#ident
|
||||
}
|
||||
}
|
||||
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);
|
||||
if r#type.generic_args.is_empty() {
|
||||
quote! {
|
||||
#ident
|
||||
}
|
||||
} else {
|
||||
let generics: Vec<TokenStream2> = r#type.generic_args.iter().map(type_to_rust).collect();
|
||||
quote! {
|
||||
#ident <#(#generics),*>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
use std::{env, fs, io::Write, path::PathBuf, process::Command};
|
||||
|
||||
use trixy_lang_parser::parse_trixy_lang;
|
||||
|
||||
use crate::config::TrixyConfig;
|
||||
|
||||
pub mod config;
|
||||
mod generate;
|
||||
|
||||
const VIM_LINE_RUST: &'static str = "// vim: filetype=rust\n";
|
||||
const VIM_LINE_C: &'static str = "// vim: filetype=c\n";
|
||||
|
||||
impl TrixyConfig {
|
||||
/// This is the heart of Trixy
|
||||
/// It mainly does one thing:
|
||||
/// - Generate a tree of modules from the input trixy file
|
||||
///
|
||||
pub fn generate(&self) {
|
||||
let source_code = fs::read_to_string(&self.trixy_path).unwrap_or_else(|err| {
|
||||
panic! {"Can't read file at path: '{}'. The Error is: '{}'",
|
||||
self.trixy_path.display(), err};
|
||||
});
|
||||
|
||||
let trixy_code = parse_trixy_lang(&source_code).unwrap_or_else(|err| {
|
||||
panic! {"Parsing of the trixy file failed: \n{}", err}
|
||||
});
|
||||
|
||||
// host code
|
||||
let host_code = prettyplease::unparse(
|
||||
&syn::parse2(generate::generate(&trixy_code, &self))
|
||||
.expect("This code was generated, it should also be parsable"),
|
||||
);
|
||||
let mut host_code_out = fs::File::create(PathBuf::from(format!(
|
||||
"{}/{}",
|
||||
env::var("OUT_DIR").expect("The build script should have this define"),
|
||||
&self.host_code_name.display()
|
||||
)))
|
||||
.expect("This file should always be free to use");
|
||||
write!(host_code_out, "{}\n{}", host_code, VIM_LINE_RUST).expect("Write should work");
|
||||
|
||||
// c header
|
||||
let c_header = generate::c_api::header::generate(&trixy_code, &self);
|
||||
let c_header_path = PathBuf::from(format!(
|
||||
"{}/{}",
|
||||
env::var("OUT_DIR").expect("The build script should have this define"),
|
||||
&self.c_header_name.display()
|
||||
));
|
||||
let mut c_header_out =
|
||||
fs::File::create(&c_header_path).expect("This file should always be free to use");
|
||||
write!(c_header_out, "{}\n{}", c_header, VIM_LINE_C).expect("Write should work");
|
||||
|
||||
Command::new("clang-format")
|
||||
.args(["-i", &c_header_path.to_str().unwrap()])
|
||||
.status()
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to format the c header file with `clang-format`; Error: `{}`",
|
||||
err
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(dist_dir) = &self.dist_dir_path {
|
||||
if !dist_dir.is_dir() {
|
||||
fs::create_dir(dist_dir).unwrap_or_else(|err| {
|
||||
panic! {
|
||||
"Failed to create the dist directory ('{}') because of: `{}`",
|
||||
dist_dir.display(), err}
|
||||
});
|
||||
}
|
||||
if self.check_dist_dir {
|
||||
if dist_dir.read_dir().iter().count() != 1 {
|
||||
panic!("Your specified dist dir already has something in it! Set `check_dist_dir` to `false` to override this check");
|
||||
}
|
||||
}
|
||||
let c_header_dist = PathBuf::from(format!(
|
||||
"{}/{}",
|
||||
dist_dir.display(),
|
||||
self.c_header_name.display()
|
||||
));
|
||||
fs::copy(c_header_path, c_header_dist).unwrap_or_else(
|
||||
|err| panic! {"Failed to copy the c header to the dist dir because of: `{}`", err},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue