diff --git a/.gitignore b/.gitignore
index 39ffcc7..1787bff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,8 +2,10 @@
/target
/result
-# direnv
+# dev env
.direnv
+.ccls-cache
+
# trixy is a library
Cargo.lock
diff --git a/.licensure.yml b/.licensure.yml
index a0789e6..b61088b 100644
--- a/.licensure.yml
+++ b/.licensure.yml
@@ -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
diff --git a/Cargo.toml b/Cargo.toml
index 2770481..18d6742 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,13 +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"] }
-trixy-lang_parser = { path = "./trixy-lang_parser" }
-trixy-macros = { path = "./trixy-macros" }
+thiserror = "1.0.51"
+# trixy-lang_parser = { path = "./trixy-lang_parser" }
+# trixy-macros = { path = "./trixy-macros" }
diff --git a/flake.nix b/flake.nix
index 1ca9c2d..25dcfbe 100644
--- a/flake.nix
+++ b/flake.nix
@@ -17,6 +17,7 @@
# If not, see .
+
{
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;
diff --git a/trixy-macros/examples/main/api_correct.tri b/src/error/mod.rs
similarity index 69%
rename from trixy-macros/examples/main/api_correct.tri
rename to src/error/mod.rs
index 44f64f3..612b235 100644
--- a/trixy-macros/examples/main/api_correct.tri
+++ b/src/error/mod.rs
@@ -18,23 +18,15 @@
* If not, see .
*/
+use std::ffi::c_char;
-nasp trinitrix {
- struct Callback {
- func: String,
- timeout: String,
- };
+use thiserror::Error;
- enum CallbackPriority {
- High,
- Medium,
- Low,
- };
+#[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 },
- fn execute_callback(callback: Callback, priority: CallbackPriority);
+ #[error("You passed a null pointer to the conversion function!")]
+ NullPointer,
}
-
-
-
-// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
-// vim: syntax=rust
diff --git a/src/lib.rs b/src/lib.rs
index 914c2f8..e0e7dcb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,3 +17,28 @@
* and the Lesser GNU General Public License along with this program.
* If not, see .
*/
+
+//! Trixy contains the types used by the [trixy-macros] crate to provide ffi safe types
+use std::{ffi::c_char, ops::Deref};
+
+use proc_macro2::TokenStream;
+use quote::quote;
+
+pub mod error;
+pub mod traits;
+
+// 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>
+
+#[repr(C)]
+pub struct String(*const c_char);
+
+/// 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"];
+
+pub fn to_c_name>(rust_type: T) -> TokenStream {
+ match &*rust_type {
+ "String" => quote!(const char*),
+ other => panic! {"'{}' is not a vaild type name!", other},
+ }
+}
diff --git a/src/traits/errno.rs b/src/traits/errno.rs
new file mode 100644
index 0000000..a8ef7b4
--- /dev/null
+++ b/src/traits/errno.rs
@@ -0,0 +1,152 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+use 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>> = 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> {
+ 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
+}
diff --git a/src/traits/mod.rs b/src/traits/mod.rs
new file mode 100644
index 0000000..8e70861
--- /dev/null
+++ b/src/traits/mod.rs
@@ -0,0 +1,55 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+use std::ffi::CStr;
+
+use crate::error::TypeConversionError;
+
+pub mod errno;
+
+impl TryFrom for &str {
+ type Error = crate::error::TypeConversionError;
+
+ fn try_from(value: crate::String) -> Result {
+ 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 for String {
+ type Error = crate::error::TypeConversionError;
+
+ fn try_from(value: crate::String) -> Result {
+ let str: &str = value.try_into()?;
+ Ok(str.to_owned())
+ }
+}
diff --git a/trixy-lang_parser/Cargo.toml b/trixy-lang_parser/Cargo.toml
index 9aa2415..69ec984 100644
--- a/trixy-lang_parser/Cargo.toml
+++ b/trixy-lang_parser/Cargo.toml
@@ -25,7 +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"] }
+clap = { version = "4.4.11", features = ["derive"], optional = true }
convert_case = "0.6.0"
-pretty_assertions = "1.4.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"]
diff --git a/trixy-lang_parser/README.md b/trixy-lang_parser/README.md
index 0a6f6bb..e48f30f 100644
--- a/trixy-lang_parser/README.md
+++ b/trixy-lang_parser/README.md
@@ -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.
diff --git a/trixy-lang_parser/example/empty.tri b/trixy-lang_parser/example/empty.tri
index 0da6daa..332e35f 100644
--- a/trixy-lang_parser/example/empty.tri
+++ b/trixy-lang_parser/example/empty.tri
@@ -19,3 +19,9 @@
*/
+
+
+// an empty comment:
+//
+
+//
diff --git a/trixy-lang_parser/src/main.rs b/trixy-lang_parser/src/bin/trixy-parser.rs
similarity index 99%
rename from trixy-lang_parser/src/main.rs
rename to trixy-lang_parser/src/bin/trixy-parser.rs
index 2004949..f14ef39 100644
--- a/trixy-lang_parser/src/main.rs
+++ b/trixy-lang_parser/src/bin/trixy-parser.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use std::{fs, process::exit};
use trixy_lang_parser::{lexing::TokenStream, parse_trixy_lang};
diff --git a/trixy-lang_parser/src/command_spec/checked.rs b/trixy-lang_parser/src/command_spec/checked.rs
index d38ba5d..8c75c93 100644
--- a/trixy-lang_parser/src/command_spec/checked.rs
+++ b/trixy-lang_parser/src/command_spec/checked.rs
@@ -18,26 +18,17 @@
* If not, see .
*/
+
+
+
//! 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,
@@ -95,6 +86,28 @@ pub struct Type {
pub identifier: Identifier,
pub generic_args: Vec,
}
+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 {
diff --git a/trixy-lang_parser/src/command_spec/mod.rs b/trixy-lang_parser/src/command_spec/mod.rs
index a5ec343..e113a55 100644
--- a/trixy-lang_parser/src/command_spec/mod.rs
+++ b/trixy-lang_parser/src/command_spec/mod.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
pub mod checked;
pub mod unchecked;
diff --git a/trixy-lang_parser/src/command_spec/unchecked.rs b/trixy-lang_parser/src/command_spec/unchecked.rs
index 56f8468..2703db9 100644
--- a/trixy-lang_parser/src/command_spec/unchecked.rs
+++ b/trixy-lang_parser/src/command_spec/unchecked.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
//! 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.
diff --git a/trixy-lang_parser/src/error.rs b/trixy-lang_parser/src/error.rs
index 4b4657a..c0ff457 100644
--- a/trixy-lang_parser/src/error.rs
+++ b/trixy-lang_parser/src/error.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use core::fmt;
use thiserror::Error;
diff --git a/trixy-lang_parser/src/lexing/error.rs b/trixy-lang_parser/src/lexing/error.rs
index 32a129c..722dfa1 100644
--- a/trixy-lang_parser/src/lexing/error.rs
+++ b/trixy-lang_parser/src/lexing/error.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use std::{error::Error, fmt::Display};
use thiserror::Error;
diff --git a/trixy-lang_parser/src/lexing/mod.rs b/trixy-lang_parser/src/lexing/mod.rs
index e1a30e8..22d0888 100644
--- a/trixy-lang_parser/src/lexing/mod.rs
+++ b/trixy-lang_parser/src/lexing/mod.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use std::fmt::Display;
use self::{error::SpannedLexingError, tokenizer::Tokenizer};
diff --git a/trixy-lang_parser/src/lexing/test.rs b/trixy-lang_parser/src/lexing/test.rs
index 7eb32fe..15060b7 100644
--- a/trixy-lang_parser/src/lexing/test.rs
+++ b/trixy-lang_parser/src/lexing/test.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use crate::lexing::{Keyword, Token, TokenKind, TokenSpan};
use super::TokenStream;
diff --git a/trixy-lang_parser/src/lexing/tokenizer.rs b/trixy-lang_parser/src/lexing/tokenizer.rs
index 104b117..144f725 100644
--- a/trixy-lang_parser/src/lexing/tokenizer.rs
+++ b/trixy-lang_parser/src/lexing/tokenizer.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
// This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html
use crate::{
@@ -147,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];
@@ -155,25 +170,34 @@ fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
} else {
let text: &str = &text[2..];
if let Some('/') = text.chars().next() {
- let text = &text[1..];
- let (doc_comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
+ 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')?;
- // trim whitespace
- let doc_comment = doc_comment.trim_start();
- let doc_comment = doc_comment.trim_end();
+ // trim whitespace
+ let doc_comment = doc_comment.trim_start();
+ let doc_comment = doc_comment.trim_end();
- return Ok((
- TokenKind::DocComment(doc_comment.to_owned()),
- chars_read + 3,
- ));
+ 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
+ let comment = comment.trim_start();
+ let comment = comment.trim_end();
+
+ Ok((TokenKind::Comment(comment.to_owned()), chars_read + 2))
+ }
}
- let (comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
-
- // trim whitespace
- let comment = comment.trim_start();
- let comment = comment.trim_end();
-
- Ok((TokenKind::Comment(comment.to_owned()), chars_read + 2))
}
}
diff --git a/trixy-lang_parser/src/lib.rs b/trixy-lang_parser/src/lib.rs
index e5f7ba8..0a715cf 100644
--- a/trixy-lang_parser/src/lib.rs
+++ b/trixy-lang_parser/src/lib.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use error::TrixyError;
use crate::lexing::TokenStream;
diff --git a/trixy-lang_parser/src/parsing/checked/error.rs b/trixy-lang_parser/src/parsing/checked/error.rs
index ae08ff5..19bd13b 100644
--- a/trixy-lang_parser/src/parsing/checked/error.rs
+++ b/trixy-lang_parser/src/parsing/checked/error.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use thiserror::Error;
use std::{error::Error, fmt::Display};
diff --git a/trixy-lang_parser/src/parsing/checked/mod.rs b/trixy-lang_parser/src/parsing/checked/mod.rs
index 74aff7e..8c2ac09 100644
--- a/trixy-lang_parser/src/parsing/checked/mod.rs
+++ b/trixy-lang_parser/src/parsing/checked/mod.rs
@@ -18,15 +18,19 @@
* If not, see .
*/
+
+
+
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,
@@ -258,7 +262,7 @@ impl Parser {
.iter()
.map(|r#enum| Into::::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,
diff --git a/trixy-lang_parser/src/parsing/checked/test.rs b/trixy-lang_parser/src/parsing/checked/test.rs
index 6037feb..178dbc0 100644
--- a/trixy-lang_parser/src/parsing/checked/test.rs
+++ b/trixy-lang_parser/src/parsing/checked/test.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use crate::command_spec::checked::{
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
NamedType, Namespace, Structure, Type,
diff --git a/trixy-lang_parser/src/parsing/mod.rs b/trixy-lang_parser/src/parsing/mod.rs
index 01c4cea..5db7b1b 100644
--- a/trixy-lang_parser/src/parsing/mod.rs
+++ b/trixy-lang_parser/src/parsing/mod.rs
@@ -18,5 +18,8 @@
* If not, see .
*/
+
+
+
pub mod checked;
pub mod unchecked;
diff --git a/trixy-lang_parser/src/parsing/unchecked/error.rs b/trixy-lang_parser/src/parsing/unchecked/error.rs
index 476d932..7bcac27 100644
--- a/trixy-lang_parser/src/parsing/unchecked/error.rs
+++ b/trixy-lang_parser/src/parsing/unchecked/error.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use std::{error::Error, fmt::Display};
use thiserror::Error;
diff --git a/trixy-lang_parser/src/parsing/unchecked/mod.rs b/trixy-lang_parser/src/parsing/unchecked/mod.rs
index b40d800..e97145d 100644
--- a/trixy-lang_parser/src/parsing/unchecked/mod.rs
+++ b/trixy-lang_parser/src/parsing/unchecked/mod.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use std::mem;
use crate::{
diff --git a/trixy-lang_parser/src/parsing/unchecked/test.rs b/trixy-lang_parser/src/parsing/unchecked/test.rs
index f25083f..c712e12 100644
--- a/trixy-lang_parser/src/parsing/unchecked/test.rs
+++ b/trixy-lang_parser/src/parsing/unchecked/test.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
use pretty_assertions::assert_eq;
use crate::{
diff --git a/trixy-macros/Cargo.toml b/trixy-macros/Cargo.toml
index 75fa66e..24e9964 100644
--- a/trixy-macros/Cargo.toml
+++ b/trixy-macros/Cargo.toml
@@ -22,12 +22,11 @@ name = "trixy-macros"
version = "0.1.0"
edition = "2021"
-[lib]
-proc-macro = true
-
[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" }
diff --git a/trixy-macros/example/main/.gitignore b/trixy-macros/example/main/.gitignore
new file mode 100644
index 0000000..99fbf70
--- /dev/null
+++ b/trixy-macros/example/main/.gitignore
@@ -0,0 +1,7 @@
+# build
+/target
+/result
+/dist
+
+# This crate is a library
+Cargo.lock
diff --git a/trixy-macros/example/main/Cargo.toml b/trixy-macros/example/main/Cargo.toml
new file mode 100644
index 0000000..94ff40b
--- /dev/null
+++ b/trixy-macros/example/main/Cargo.toml
@@ -0,0 +1,35 @@
+# Copyright (C) 2023 The Trinitrix Project
+#
+# This file is part of the Trixy crate for Trinitrix.
+#
+# Trixy is free software: you can redistribute it and/or modify
+# it under the terms of the Lesser GNU General Public License as
+# published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# and the Lesser GNU General Public License along with this program.
+# If not, see .
+
+
+[package]
+name = "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"}
diff --git a/trixy-macros/example/main/build.rs b/trixy-macros/example/main/build.rs
new file mode 100644
index 0000000..cca5cd7
--- /dev/null
+++ b/trixy-macros/example/main/build.rs
@@ -0,0 +1,31 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+
+use 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();
+}
diff --git a/trixy-macros/example/main/c_src/main.c b/trixy-macros/example/main/c_src/main.c
new file mode 100644
index 0000000..12d9332
--- /dev/null
+++ b/trixy-macros/example/main/c_src/main.c
@@ -0,0 +1,50 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+
+#include "../dist/interface.h"
+#include
+#include
+
+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;
+}
diff --git a/trixy-macros/example/main/src/api.tri b/trixy-macros/example/main/src/api.tri
new file mode 100644
index 0000000..e43da58
--- /dev/null
+++ b/trixy-macros/example/main/src/api.tri
@@ -0,0 +1,54 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+
+// nasp trinitrix {
+// struct Callback {
+// func: String,
+// timeout: String,
+// };
+//
+// enum CallbackPriority {
+// High,
+// Medium,
+// Low,
+// };
+//
+// fn execute_callback(callback: Callback, priority: CallbackPriority);
+// }
+
+/// 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
diff --git a/trixy-macros/examples/main/api_failing.tri b/trixy-macros/example/main/src/lib.rs
similarity index 79%
rename from trixy-macros/examples/main/api_failing.tri
rename to trixy-macros/example/main/src/lib.rs
index 5edbc63..753bb37 100644
--- a/trixy-macros/examples/main/api_failing.tri
+++ b/trixy-macros/example/main/src/lib.rs
@@ -19,12 +19,12 @@
*/
-fn print(message: CommandTransferValue);
-nasp trinitrix { {}
- fn hi honner(name: String) -> String; ;
+
+macro_rules! callback {
+ ($cmd:expr) => {{
+ println!("{:#?}", $cmd);
+ }};
}
-
-// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
-// vim: syntax=rust
+include!(concat!(env!("OUT_DIR"), "/api.rs"));
diff --git a/trixy-macros/examples/main/main.rs b/trixy-macros/example/main/src/main.rs
similarity index 83%
rename from trixy-macros/examples/main/main.rs
rename to trixy-macros/example/main/src/main.rs
index f83b38a..bcfee48 100644
--- a/trixy-macros/examples/main/main.rs
+++ b/trixy-macros/example/main/src/main.rs
@@ -18,12 +18,8 @@
* If not, see .
*/
-use trixy_macros::trixy_generate;
fn main() {
- trixy_generate! {
- path: "./examples/main/api.tri"
- languages: rust, lua, c
- generate_debug: false
- }
+ let input = include_str!(concat!(env!("OUT_DIR"), "/interface.h"));
+ println!("{}", input);
}
diff --git a/trixy-macros/examples/main/api.tri b/trixy-macros/examples/main/api.tri
deleted file mode 120000
index 98790fc..0000000
--- a/trixy-macros/examples/main/api.tri
+++ /dev/null
@@ -1 +0,0 @@
-api_correct.tri
\ No newline at end of file
diff --git a/trixy-macros/src/config/mod.rs b/trixy-macros/src/config/mod.rs
index 84907db..8333f89 100644
--- a/trixy-macros/src/config/mod.rs
+++ b/trixy-macros/src/config/mod.rs
@@ -18,138 +18,119 @@
* If not, see .
*/
-//! This module is responsible for parsing the config passed to the macro call:
-//! For example:
+
+
+
+//! This module is responsible for the config passed to trixy.
+//! It works using the popular builder syntax:
//! ```no_run
-//! trixy_generate! {
-//! path: "./path/to/trixy/file.tri"
-//! languages: rust, lua, c
-//! generate_debug: false
-//! }
+//! 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::PathBuf;
-
-use proc_macro2::Ident;
-use syn::{parse::Parse, punctuated::Punctuated, LitBool, LitStr, Result, Token};
-
-mod kw {
- syn::custom_keyword!(path);
- syn::custom_keyword!(languages);
- syn::custom_keyword!(generate_debug);
-}
+use std::path::{Path, PathBuf};
#[derive(Debug)]
pub enum Language {
Rust,
- Lua,
C,
+ Lua,
}
-#[derive(Debug)]
-struct Languages {
- #[allow(dead_code)]
- languages: kw::languages,
- #[allow(dead_code)]
- colon: Token![:],
- raw: Punctuated,
-}
-
-#[derive(Debug)]
-struct Path {
- #[allow(dead_code)]
- path: kw::path,
- #[allow(dead_code)]
- colon: Token![:],
- raw: PathBuf,
-}
-
-#[derive(Debug)]
-struct GenerateDebug {
- #[allow(dead_code)]
- generate_debug: kw::generate_debug,
- #[allow(dead_code)]
- colon: Token![:],
- raw: bool,
-}
-
-#[derive(Debug)]
+#[derive(Default, Debug)]
pub struct TrixyConfig {
/// The Path to the base command interface config file
- path: Path,
+ pub trixy_path: PathBuf,
- /// The languages the commands should be exposed in
- languages: Languages,
+ /// 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,
+
+ /// 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` ouput
- generate_debug: GenerateDebug,
+ /// 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 get_path(&self) -> &std::path::Path {
- &self.path.raw
+ pub fn new>(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 generate_debug(&self) -> bool {
- self.generate_debug.raw
- }
-}
-impl Parse for TrixyConfig {
- fn parse(input: syn::parse::ParseStream) -> syn::Result {
- Ok(Self {
- path: input.parse()?,
- languages: input.parse()?,
- generate_debug: input.parse()?,
- })
+ pub fn trixy_path>(self, input_path: T) -> Self {
+ Self {
+ trixy_path: input_path.into(),
+ ..self
+ }
}
-}
-impl Parse for GenerateDebug {
- fn parse(input: syn::parse::ParseStream) -> Result {
- let kw: kw::generate_debug = input.parse()?;
- let colon: Token![:] = input.parse()?;
- let raw = input.parse::()?.value();
- Ok(Self {
- generate_debug: kw,
- colon,
- raw,
- })
+ pub fn generate_debug(self, generate_debug: bool) -> Self {
+ Self {
+ generate_debug,
+ ..self
+ }
}
-}
-impl Parse for Path {
- fn parse(input: syn::parse::ParseStream) -> Result {
- let path: kw::path = input.parse()?;
- let colon: Token![:] = input.parse()?;
- let raw = PathBuf::from(input.parse::()?.value());
- Ok(Self { path, colon, raw })
+ pub fn dist_dir_path>(self, dist_dir_path: T) -> Self {
+ Self {
+ dist_dir_path: Some(dist_dir_path.into()),
+ ..self
+ }
}
-}
-impl Parse for Languages {
- fn parse(input: syn::parse::ParseStream) -> syn::Result {
- let languages: kw::languages = input.parse()?;
- let colon: Token![:] = input.parse()?;
- let raw = Punctuated::::parse_separated_nonempty(input)?;
- Ok(Self {
- languages,
- colon,
- raw,
- })
+ pub fn check_dist_dir(self, check_dist_dir: bool) -> Self {
+ Self {
+ check_dist_dir,
+ ..self
+ }
}
-}
-impl Parse for Language {
- fn parse(input: syn::parse::ParseStream) -> Result {
- 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
- ))),
+ pub fn host_code_name>(self, output_path: T) -> Self {
+ Self {
+ host_code_name: output_path.into(),
+ ..self
+ }
+ }
+
+ pub fn c_header_name>(self, output_path: T) -> Self {
+ Self {
+ c_header_name: output_path.into(),
+ ..self
+ }
+ }
+
+ pub fn callback_function>(self, callback_function: T) -> Self {
+ Self {
+ callback_function: callback_function.into(),
+ ..self
}
}
}
diff --git a/trixy-macros/src/generate/c_api/header.rs b/trixy-macros/src/generate/c_api/header.rs
new file mode 100644
index 0000000..7e7ac7f
--- /dev/null
+++ b/trixy-macros/src/generate/c_api/header.rs
@@ -0,0 +1,293 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+
+//! 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::>()
+ .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::();
+ let ident = mangle_c_function_ident(function, namespaces);
+ let inputs: Vec = 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::();
+
+ 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::>()
+ .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 = 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::>()
+ .join("\n");
+ let namespaces: String = nasp
+ .namespaces
+ .iter()
+ .map(|nasp| namespace_to_header(nasp, &nasps))
+ .collect();
+
+ format! {"{}\n{}", functions, namespaces}
+}
diff --git a/trixy-macros/src/generate/c_api/host.rs b/trixy-macros/src/generate/c_api/host.rs
new file mode 100644
index 0000000..0e8c617
--- /dev/null
+++ b/trixy-macros/src/generate/c_api/host.rs
@@ -0,0 +1,291 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+
+use 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 = 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 = 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 =
+ 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 [= ]
+/// ->
+/// one::One::Two( ) [= ]
+/// ->
+/// Commands::One( ) [= ]
+/// ```
+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
+ }
+ }
+ })
+}
diff --git a/trixy-macros/src/generate/c_api/mod.rs b/trixy-macros/src/generate/c_api/mod.rs
new file mode 100644
index 0000000..3e25faf
--- /dev/null
+++ b/trixy-macros/src/generate/c_api/mod.rs
@@ -0,0 +1,43 @@
+/*
+* Copyright (C) 2023 The Trinitrix Project
+*
+* This file is part of the Trixy crate for Trinitrix.
+*
+* Trixy is free software: you can redistribute it and/or modify
+* it under the terms of the Lesser GNU General Public License as
+* published by the Free Software Foundation, either version 3 of
+* the License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* and the Lesser GNU General Public License along with this program.
+* If not, see .
+*/
+
+
+use 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)
+ }
+}
diff --git a/trixy-macros/src/generate/host/mod.rs b/trixy-macros/src/generate/host/mod.rs
index 82dd30a..f3f04c2 100644
--- a/trixy-macros/src/generate/host/mod.rs
+++ b/trixy-macros/src/generate/host/mod.rs
@@ -18,6 +18,9 @@
* If not, see .
*/
+
+
+
//! 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.
@@ -28,20 +31,23 @@ use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use trixy_lang_parser::command_spec::{
- Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
- NamedType, Namespace, Structure, Type,
+ Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Namespace,
+ Structure,
};
-use crate::config::TrixyConfig;
+use crate::{
+ config::TrixyConfig,
+ generate::{identifier_to_rust, named_type_to_rust},
+};
thread_local! {static DEBUG: OnceCell = OnceCell::new();}
/// This function turns, for example, the following trixy input into this rust code:
-/// ```rust
+/// ```text
/// nasp trinitrix {
/// struct Callback {
-/// func: Function,
-/// timeout: Integer,
+/// func: String,
+/// timeout: String,
/// };
///
/// enum CallbackPriority {
@@ -52,20 +58,22 @@ thread_local! {static DEBUG: OnceCell = OnceCell::new();}
///
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
/// }
+/// // wrong but helps with syntax highlight:
+/// // vim: syntax=rust
/// ```
-/// ```rust
+/// ```no_run
/// #[derive(Debug)]
/// pub enum Commands {
/// Trinitrix(trinitrix::Trinitrix),
/// }
/// pub mod trinitrix {
-/// #[allow(non_camel_case)]
+/// #[allow(non_camel_case_types)]
/// #[derive(Debug)]
/// struct Callback {
-/// func: Function,
-/// timeout: Integer,
+/// func: String,
+/// timeout: String,
/// }
-/// #[allow(non_camel_case)]
+/// #[allow(non_camel_case_types)]
/// #[derive(Debug)]
/// enum CallbackPriority {
/// High,
@@ -74,14 +82,14 @@ thread_local! {static DEBUG: OnceCell = OnceCell::new();}
/// }
/// #[derive(Debug)]
/// pub enum Trinitrix {
-/// #[allow(non_camel_case)]
+/// #[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() {
+ d.set(if config.generate_debug {
quote! {
#[derive(Debug)]
}
@@ -190,12 +198,21 @@ fn function_to_rust(function: &Function) -> TokenStream2 {
.map(attribute_to_doc_comment)
.collect();
let ident = identifier_to_rust(&function.identifier);
+
let inputs: Vec = function.inputs.iter().map(named_type_to_rust).collect();
- quote! {
- #doc_comments
- #[allow(non_camel_case_types)]
- #ident {#(#inputs),*}
+ if inputs.is_empty() {
+ quote! {
+ #doc_comments
+ #[allow(non_camel_case_types)]
+ #ident
+ }
+ } else {
+ quote! {
+ #doc_comments
+ #[allow(non_camel_case_types)]
+ #ident {#(#inputs),*}
+ }
}
}
@@ -216,7 +233,7 @@ fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 {
quote! {
#doc_comments
- #[allow(non_camel_case)]
+ #[allow(non_camel_case_types)]
#debug
enum #ident {
#(#states),*
@@ -241,7 +258,7 @@ fn structure_to_rust(structure: &Structure) -> TokenStream2 {
quote! {
#doc_comments
- #[allow(non_camel_case)]
+ #[allow(non_camel_case_types)]
#debug
struct #ident {
#(#contents),*
@@ -263,13 +280,6 @@ fn doc_identifier_to_rust(doc_identifier: &DocIdentifier) -> TokenStream2 {
}
}
-fn identifier_to_rust(identifier: &Identifier) -> TokenStream2 {
- let ident = format_ident!("{}", &identifier.name);
- quote! {
- #ident
- }
-}
-
fn doc_named_type_to_rust(doc_named_type: &DocNamedType) -> TokenStream2 {
let doc_comments: TokenStream2 = doc_named_type
.attributes
@@ -282,19 +292,3 @@ fn doc_named_type_to_rust(doc_named_type: &DocNamedType) -> TokenStream2 {
#named_type
}
}
-
-fn named_type_to_rust(named_type: &NamedType) -> TokenStream2 {
- let ident = identifier_to_rust(&named_type.name);
- let r#type = type_to_rust(&named_type.r#type);
- quote! {
- #ident : #r#type
- }
-}
-
-fn type_to_rust(r#type: &Type) -> TokenStream2 {
- let ident = identifier_to_rust(&r#type.identifier);
- let generics: Vec = r#type.generic_args.iter().map(type_to_rust).collect();
- quote! {
- #ident <#(#generics),*>
- }
-}
diff --git a/trixy-macros/src/generate/mod.rs b/trixy-macros/src/generate/mod.rs
index 7d134df..1cb91ef 100644
--- a/trixy-macros/src/generate/mod.rs
+++ b/trixy-macros/src/generate/mod.rs
@@ -18,4 +18,55 @@
* If not, see .
*/
+
+
+
+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 = r#type.generic_args.iter().map(type_to_rust).collect();
+ quote! {
+ #ident <#(#generics),*>
+ }
+ }
+}
diff --git a/trixy-macros/src/lib.rs b/trixy-macros/src/lib.rs
index 91124e4..8468d66 100644
--- a/trixy-macros/src/lib.rs
+++ b/trixy-macros/src/lib.rs
@@ -18,39 +18,91 @@
* If not, see .
*/
-use std::fs;
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::parse_macro_input;
+
+
+use std::{env, fs, io::Write, path::PathBuf, process::Command};
+
use trixy_lang_parser::parse_trixy_lang;
use crate::config::TrixyConfig;
-mod config;
+pub mod config;
mod generate;
-/// This is the heart of Trixy
-/// It mainly does one thing:
-/// - Generate a tree of modules from the input trixy file
-///
-#[proc_macro]
-pub fn trixy_generate(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as TrixyConfig);
+const VIM_LINE_RUST: &'static str = "// vim: filetype=rust\n";
+const VIM_LINE_C: &'static str = "// vim: filetype=c\n";
- let source_code = fs::read_to_string(input.get_path()).unwrap_or_else(|err| {
- panic! {"Can't read file at path: '{}'. The Error is: '{}'", input.get_path().display(), err};
- });
+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}
- });
+ let trixy_code = parse_trixy_lang(&source_code).unwrap_or_else(|err| {
+ panic! {"Parsing of the trixy file failed: \n{}", err}
+ });
- // Build the final rust hosting code
- let host_rust_code = generate::host::generate(&trixy_code, &input);
+ // 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");
- let output = quote! {
- #host_rust_code
- };
- output.into()
+ // 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},
+ );
+ }
+ }
}