Compare commits

...

3 Commits

52 changed files with 2062 additions and 291 deletions

4
.gitignore vendored
View File

@ -2,8 +2,10 @@
/target
/result
# direnv
# dev env
.direnv
.ccls-cache
# trixy is a library
Cargo.lock

View File

@ -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

View File

@ -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" }

View File

@ -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;

View File

@ -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
))),
}
}
}

32
src/error/mod.rs Normal file
View File

@ -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,
}

View File

@ -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},
}
}

152
src/traits/errno.rs Normal file
View File

@ -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
}

55
src/traits/mod.rs Normal file
View File

@ -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())
}
}

View File

@ -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"]

View File

@ -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.

View File

@ -19,3 +19,9 @@
*/
// an empty comment:
//
//

View File

@ -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

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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 {

View File

@ -19,5 +19,9 @@
*/
pub mod checked;
pub mod unchecked;
pub use checked::*;

View File

@ -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.

View File

@ -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.

View File

@ -19,6 +19,8 @@
*/
use std::{error::Error, fmt::Display};
use thiserror::Error;

View File

@ -19,6 +19,8 @@
*/
use std::fmt::Display;
use self::{error::SpannedLexingError, tokenizer::Tokenizer};

View File

@ -19,6 +19,8 @@
*/
use crate::lexing::{Keyword, Token, TokenKind, TokenSpan};
use super::TokenStream;

View File

@ -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
@ -176,6 +197,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> {

View File

@ -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)
}

View File

@ -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(),
}
}
}

View File

@ -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,

View File

@ -19,6 +19,8 @@
*/
use crate::command_spec::checked::{
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
NamedType, Namespace, Structure, Type,

View File

@ -19,5 +19,7 @@
*/
pub mod checked;
mod unchecked;
pub mod unchecked;

View File

@ -19,6 +19,8 @@
*/
use std::{error::Error, fmt::Display};
use thiserror::Error;

View File

@ -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;

View File

@ -19,6 +19,8 @@
*/
use pretty_assertions::assert_eq;
use crate::{

6
trixy-macros/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# build
/target
/result
# This crate is a library
Cargo.lock

32
trixy-macros/Cargo.toml Normal file
View File

@ -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" }

7
trixy-macros/example/main/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# build
/target
/result
/dist
# This crate is a library
Cargo.lock

View File

@ -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"}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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

View File

@ -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"));

View File

@ -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);
}

View File

@ -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
}
}
}

View File

@ -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}
}

View File

@ -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
}
}
})
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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),*>
}
}
}

108
trixy-macros/src/lib.rs Normal file
View File

@ -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},
);
}
}
}