feat(treewide): Provide a c api
This commit is contained in:
parent
0744c8468d
commit
7d1a41aca9
|
@ -2,8 +2,10 @@
|
||||||
/target
|
/target
|
||||||
/result
|
/result
|
||||||
|
|
||||||
# direnv
|
# dev env
|
||||||
.direnv
|
.direnv
|
||||||
|
.ccls-cache
|
||||||
|
|
||||||
|
|
||||||
# trixy is a library
|
# trixy is a library
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
|
@ -102,7 +102,7 @@ comments:
|
||||||
commenter:
|
commenter:
|
||||||
type: line
|
type: line
|
||||||
comment_char: "//"
|
comment_char: "//"
|
||||||
trailing_lines: 2
|
trailing_lines: 1
|
||||||
|
|
||||||
- extensions:
|
- extensions:
|
||||||
- rs
|
- rs
|
||||||
|
@ -124,7 +124,7 @@ comments:
|
||||||
start_block_char: "/*\n"
|
start_block_char: "/*\n"
|
||||||
end_block_char: "*/\n"
|
end_block_char: "*/\n"
|
||||||
per_line_char: "*"
|
per_line_char: "*"
|
||||||
trailing_lines: 2
|
trailing_lines: 1
|
||||||
|
|
||||||
# In this case extension is singular and a single string extension is provided.
|
# In this case extension is singular and a single string extension is provided.
|
||||||
- extension: html
|
- extension: html
|
||||||
|
@ -139,7 +139,7 @@ comments:
|
||||||
commenter:
|
commenter:
|
||||||
type: line
|
type: line
|
||||||
comment_char: ";;;"
|
comment_char: ";;;"
|
||||||
trailing_lines: 2
|
trailing_lines: 1
|
||||||
|
|
||||||
- extensions:
|
- extensions:
|
||||||
- ebnf
|
- ebnf
|
||||||
|
@ -148,7 +148,7 @@ comments:
|
||||||
start_block_char: "#(*\n"
|
start_block_char: "#(*\n"
|
||||||
end_block_char: "#*)\n"
|
end_block_char: "#*)\n"
|
||||||
per_line_char: "#"
|
per_line_char: "#"
|
||||||
trailing_lines: 2
|
trailing_lines: 1
|
||||||
|
|
||||||
# The extension string "any" is special and so will match any file
|
# The extension string "any" is special and so will match any file
|
||||||
# extensions. Commenter configurations are always checked in the
|
# extensions. Commenter configurations are always checked in the
|
||||||
|
@ -162,4 +162,4 @@ comments:
|
||||||
commenter:
|
commenter:
|
||||||
type: line
|
type: line
|
||||||
comment_char: '#'
|
comment_char: '#'
|
||||||
trailing_lines: 2
|
trailing_lines: 1
|
||||||
|
|
|
@ -22,13 +22,12 @@ name = "trixy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
|
log = "0.4.20"
|
||||||
proc-macro2 = "1.0.70"
|
proc-macro2 = "1.0.70"
|
||||||
quote = "1.0.33"
|
quote = "1.0.33"
|
||||||
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
|
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
|
||||||
trixy-lang_parser = { path = "./trixy-lang_parser" }
|
thiserror = "1.0.51"
|
||||||
trixy-macros = { path = "./trixy-macros" }
|
# trixy-lang_parser = { path = "./trixy-lang_parser" }
|
||||||
|
# trixy-macros = { path = "./trixy-macros" }
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
# If not, see <https://www.gnu.org/licenses/>.
|
# If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
description = "A rust crate used to generate multi-language apis for your
|
description = "A rust crate used to generate multi-language apis for your
|
||||||
application";
|
application";
|
||||||
|
@ -122,6 +123,10 @@
|
||||||
cargo-edit
|
cargo-edit
|
||||||
cargo-expand
|
cargo-expand
|
||||||
|
|
||||||
|
# Used to format the c header files
|
||||||
|
libclang
|
||||||
|
gdb
|
||||||
|
|
||||||
ebnf2pdf.outputs.packages."${system}".default
|
ebnf2pdf.outputs.packages."${system}".default
|
||||||
];
|
];
|
||||||
inherit nativeBuildInputs buildInputs;
|
inherit nativeBuildInputs buildInputs;
|
||||||
|
|
|
@ -18,23 +18,15 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::ffi::c_char;
|
||||||
|
|
||||||
nasp trinitrix {
|
use thiserror::Error;
|
||||||
struct Callback {
|
|
||||||
func: String,
|
|
||||||
timeout: String,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CallbackPriority {
|
#[derive(Error, Debug)]
|
||||||
High,
|
pub enum TypeConversionError {
|
||||||
Medium,
|
#[error("Can't convert this ('{got:#?}') string into a valid rust string; Remember that these must be valid utf-8")]
|
||||||
Low,
|
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
|
|
25
src/lib.rs
25
src/lib.rs
|
@ -17,3 +17,28 @@
|
||||||
* and the Lesser GNU General Public License along with this program.
|
* and the Lesser GNU General Public License along with this program.
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* 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 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<T: Deref<Target = str>>(rust_type: T) -> TokenStream {
|
||||||
|
match &*rust_type {
|
||||||
|
"String" => quote!(const char*),
|
||||||
|
other => panic! {"'{}' is not a vaild type name!", other},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::slice;
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
error::Error,
|
||||||
|
ffi::{c_char, c_int},
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use log::{error, warn};
|
||||||
|
|
||||||
|
use crate::error::TypeConversionError;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! convert {
|
||||||
|
($input:expr) => {
|
||||||
|
match $input.try_into() {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(err) => {
|
||||||
|
trixy::traits::errno::set(err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code is heavily inspired by: https://michael-f-bryan.github.io/rust-ffi-guide/errors/return_types.html
|
||||||
|
thread_local! {
|
||||||
|
static LAST_ERROR: RefCell<Option<Box<TypeConversionError>>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the most recent error, clearing whatever may have been there before.
|
||||||
|
pub fn set(error: TypeConversionError) {
|
||||||
|
error!("Setting LAST_ERROR: {}", error);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Print a pseudo-backtrace for this error, following back each error's
|
||||||
|
// cause until we reach the root error.
|
||||||
|
let mut cause = error.source();
|
||||||
|
while let Some(parent_err) = cause {
|
||||||
|
warn!("Caused by: {}", parent_err);
|
||||||
|
cause = parent_err.source();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LAST_ERROR.with(|prev| {
|
||||||
|
*prev.borrow_mut() = Some(Box::new(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the most recent error, clearing it in the process.
|
||||||
|
pub fn take_last_error() -> Option<Box<TypeConversionError>> {
|
||||||
|
LAST_ERROR.with(|prev| prev.borrow_mut().take())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ERROR_FUNCTIONS: &'static str = r#"
|
||||||
|
/// Calculate the number of bytes in the last error's error message **not**
|
||||||
|
/// including any trailing `null` characters.
|
||||||
|
extern int last_error_length();
|
||||||
|
|
||||||
|
/// Write the most recent error message into a caller-provided buffer as a UTF-8
|
||||||
|
/// string, returning the number of bytes written.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This writes a **UTF-8** string into the buffer. Windows users may need to
|
||||||
|
/// convert it to a UTF-16 "unicode" afterwards.
|
||||||
|
///
|
||||||
|
/// If there are no recent errors then this returns `0` (because we wrote 0
|
||||||
|
/// bytes). `-1` is returned if there are any errors, for example when passed a
|
||||||
|
/// null pointer or a buffer of insufficient size.
|
||||||
|
extern int last_error_message(char* buffer, int length);
|
||||||
|
"#;
|
||||||
|
|
||||||
|
/// Calculate the number of bytes in the last error's error message **not**
|
||||||
|
/// including any trailing `null` characters.
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn last_error_length() -> c_int {
|
||||||
|
LAST_ERROR.with(|prev| match *prev.borrow() {
|
||||||
|
Some(ref err) => err.to_string().len() as c_int + 1,
|
||||||
|
None => 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the most recent error message into a caller-provided buffer as a UTF-8
|
||||||
|
/// string, returning the number of bytes written.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This writes a **UTF-8** string into the buffer. Windows users may need to
|
||||||
|
/// convert it to a UTF-16 "unicode" afterwards.
|
||||||
|
///
|
||||||
|
/// If there are no recent errors then this returns `0` (because we wrote 0
|
||||||
|
/// bytes). `-1` is returned if there are any errors, for example when passed a
|
||||||
|
/// null pointer or a buffer of insufficient size.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn last_error_message(buffer: *mut c_char, length: c_int) -> c_int {
|
||||||
|
if buffer.is_null() {
|
||||||
|
warn!("Null pointer passed into last_error_message() as the buffer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_error = match take_last_error() {
|
||||||
|
Some(err) => err,
|
||||||
|
None => return 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let error_message = last_error.to_string();
|
||||||
|
|
||||||
|
let buffer = slice::from_raw_parts_mut(buffer as *mut u8, length as usize);
|
||||||
|
|
||||||
|
if error_message.len() >= buffer.len() {
|
||||||
|
warn!("Buffer provided for writing the last error message is too small.");
|
||||||
|
warn!(
|
||||||
|
"Expected at least {} bytes but got {}",
|
||||||
|
error_message.len() + 1,
|
||||||
|
buffer.len()
|
||||||
|
);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
error_message.as_ptr(),
|
||||||
|
buffer.as_mut_ptr(),
|
||||||
|
error_message.len(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a trailing null so people using the string as a `char *` don't
|
||||||
|
// accidentally read into garbage.
|
||||||
|
buffer[error_message.len()] = 0;
|
||||||
|
|
||||||
|
error_message.len() as c_int
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
|
use crate::error::TypeConversionError;
|
||||||
|
|
||||||
|
pub mod errno;
|
||||||
|
|
||||||
|
impl TryFrom<crate::String> for &str {
|
||||||
|
type Error = crate::error::TypeConversionError;
|
||||||
|
|
||||||
|
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
|
||||||
|
let ptr = value.0;
|
||||||
|
if ptr.is_null() {
|
||||||
|
Err(TypeConversionError::NullPointer)
|
||||||
|
} else {
|
||||||
|
// SAFETY: We checked for null, which is a huge upside other things that we simply hope
|
||||||
|
// for:
|
||||||
|
// - null terminated
|
||||||
|
// - actually be a valid pointer (`(void*) 2` is *valid* c but not a *valid* pointer)
|
||||||
|
// - be contained in a single allocated object
|
||||||
|
// - the memory must obviously not be mutated, while this &str exists
|
||||||
|
// - the null terminator must be within `isize::MAX` from the start position
|
||||||
|
let str = unsafe { CStr::from_ptr(ptr) };
|
||||||
|
str.to_str()
|
||||||
|
.map_err(|_err| crate::error::TypeConversionError::String { got: value.0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<crate::String> for String {
|
||||||
|
type Error = crate::error::TypeConversionError;
|
||||||
|
|
||||||
|
fn try_from(value: crate::String) -> Result<Self, Self::Error> {
|
||||||
|
let str: &str = value.try_into()?;
|
||||||
|
Ok(str.to_owned())
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,17 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4.11", features = ["derive"] }
|
clap = { version = "4.4.11", features = ["derive"], optional = true }
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
pretty_assertions = "1.4.0"
|
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
|
trixy = { path = "../../trixy" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
build-binary = ["clap"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "trixy-parser"
|
||||||
|
required-features = ["build-binary"]
|
||||||
|
|
|
@ -2,5 +2,9 @@
|
||||||
This crate contains a parser (and lexer) for the Trixy language.
|
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).
|
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
|
## Docs
|
||||||
Run `./generate_docs` to turn the grammar file into railroad diagrams.
|
Run `./generate_docs` to turn the grammar file into railroad diagrams.
|
||||||
|
|
|
@ -19,3 +19,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// an empty comment:
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use std::{fs, process::exit};
|
use std::{fs, process::exit};
|
||||||
|
|
||||||
use trixy_lang_parser::{lexing::TokenStream, parse_trixy_lang};
|
use trixy_lang_parser::{lexing::TokenStream, parse_trixy_lang};
|
|
@ -18,26 +18,17 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//! This module contains the already type checked types.
|
//! This module contains the already type checked types.
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
use crate::lexing::TokenKind;
|
use crate::lexing::TokenKind;
|
||||||
|
|
||||||
use super::unchecked;
|
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)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Namespace {
|
pub struct Namespace {
|
||||||
pub name: Identifier,
|
pub name: Identifier,
|
||||||
|
@ -95,6 +86,28 @@ pub struct Type {
|
||||||
pub identifier: Identifier,
|
pub identifier: Identifier,
|
||||||
pub generic_args: Vec<Type>,
|
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)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct NamedType {
|
pub struct NamedType {
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub mod checked;
|
pub mod checked;
|
||||||
pub mod unchecked;
|
pub mod unchecked;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//! This module contains the not type checked types.
|
//! 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
|
//! These are generated on the first pass of the parser, to be later converted into the checked
|
||||||
//! ones.
|
//! ones.
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use self::{error::SpannedLexingError, tokenizer::Tokenizer};
|
use self::{error::SpannedLexingError, tokenizer::Tokenizer};
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use crate::lexing::{Keyword, Token, TokenKind, TokenSpan};
|
use crate::lexing::{Keyword, Token, TokenKind, TokenSpan};
|
||||||
|
|
||||||
use super::TokenStream;
|
use super::TokenStream;
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html
|
// This code is heavily inspired by: https://michael-f-bryan.github.io/static-analyser-in-rust/book/lex.html
|
||||||
|
|
||||||
use crate::{
|
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> {
|
fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
||||||
// every token starts with two slashes
|
// every token starts with two slashes
|
||||||
let slashes: &str = &text[..2];
|
let slashes: &str = &text[..2];
|
||||||
|
@ -155,6 +170,9 @@ fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
||||||
} else {
|
} else {
|
||||||
let text: &str = &text[2..];
|
let text: &str = &text[2..];
|
||||||
if let Some('/') = text.chars().next() {
|
if let Some('/') = text.chars().next() {
|
||||||
|
if end_of_line(&text) {
|
||||||
|
Ok((TokenKind::DocComment("".to_owned()), 1 + 3))
|
||||||
|
} else {
|
||||||
let text = &text[1..];
|
let text = &text[1..];
|
||||||
let (doc_comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
|
let (doc_comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
|
||||||
|
|
||||||
|
@ -162,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_start();
|
||||||
let doc_comment = doc_comment.trim_end();
|
let doc_comment = doc_comment.trim_end();
|
||||||
|
|
||||||
return Ok((
|
Ok((
|
||||||
TokenKind::DocComment(doc_comment.to_owned()),
|
TokenKind::DocComment(doc_comment.to_owned()),
|
||||||
chars_read + 3,
|
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')?;
|
let (comment, chars_read) = take_while(text, |ch| ch != '\n' && ch != '\r')?;
|
||||||
|
|
||||||
// trim whitespace
|
// trim whitespace
|
||||||
|
@ -176,6 +198,8 @@ fn tokenize_comment(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
||||||
Ok((TokenKind::Comment(comment.to_owned()), chars_read + 2))
|
Ok((TokenKind::Comment(comment.to_owned()), chars_read + 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tokenize_ident(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
fn tokenize_ident(text: &str) -> Result<(TokenKind, usize), LexingError> {
|
||||||
let (got, chars_read) = take_while(text, |ch| ch == '_' || ch.is_alphanumeric())?;
|
let (got, chars_read) = take_while(text, |ch| ch == '_' || ch.is_alphanumeric())?;
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use error::TrixyError;
|
use error::TrixyError;
|
||||||
|
|
||||||
use crate::lexing::TokenStream;
|
use crate::lexing::TokenStream;
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
|
@ -18,15 +18,19 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
|
use trixy::BASE_TYPES;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_spec::{
|
command_spec::{
|
||||||
checked::{
|
checked::{
|
||||||
CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, NamedType,
|
CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, NamedType,
|
||||||
Namespace, Structure, Type, BASE_TYPES,
|
Namespace, Structure, Type,
|
||||||
},
|
},
|
||||||
unchecked::{
|
unchecked::{
|
||||||
CommandSpec as UncheckedCommandSpec, DocNamedType as UncheckedDocNamedType,
|
CommandSpec as UncheckedCommandSpec, DocNamedType as UncheckedDocNamedType,
|
||||||
|
@ -258,7 +262,7 @@ impl Parser {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r#enum| Into::<Identifier>::into(r#enum.identifier.kind.clone()))
|
.map(|r#enum| Into::<Identifier>::into(r#enum.identifier.kind.clone()))
|
||||||
.any(|ident| ident == identifier)
|
.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 {
|
return Err(ParsingError::TypeNotDeclared {
|
||||||
r#type: identifier,
|
r#type: identifier,
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use crate::command_spec::checked::{
|
use crate::command_spec::checked::{
|
||||||
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
|
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
|
||||||
NamedType, Namespace, Structure, Type,
|
NamedType, Namespace, Structure, Type,
|
||||||
|
|
|
@ -18,5 +18,8 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub mod checked;
|
pub mod checked;
|
||||||
pub mod unchecked;
|
pub mod unchecked;
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use std::{error::Error, fmt::Display};
|
use std::{error::Error, fmt::Display};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -22,12 +22,11 @@ name = "trixy-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
|
prettyplease = "0.2.15"
|
||||||
proc-macro2 = "1.0.70"
|
proc-macro2 = "1.0.70"
|
||||||
quote = "1.0.33"
|
quote = "1.0.33"
|
||||||
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
|
syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] }
|
||||||
trixy-lang_parser = { path = "../trixy-lang_parser" }
|
trixy-lang_parser = { path = "../trixy-lang_parser" }
|
||||||
|
trixy = { path = "../../trixy" }
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# build
|
||||||
|
/target
|
||||||
|
/result
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# This crate is a library
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
#
|
||||||
|
# This file is part of the Trixy crate for Trinitrix.
|
||||||
|
#
|
||||||
|
# Trixy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the Lesser GNU General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of
|
||||||
|
# the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# and the Lesser GNU General Public License along with this program.
|
||||||
|
# If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "main-example"
|
||||||
|
version = "0.0.0"
|
||||||
|
publish = false
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
trixy = { path = "../../../../trixy" }
|
||||||
|
env_logger = { version = "0.10.1" }
|
||||||
|
log = "0.4.20"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
trixy-macros = {path = "../../../trixy-macros"}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
use trixy_macros::config::TrixyConfig;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=./src/api.tri");
|
||||||
|
TrixyConfig::new("callback")
|
||||||
|
.trixy_path("./src/api.tri")
|
||||||
|
.dist_dir_path("./dist")
|
||||||
|
.generate_debug(true)
|
||||||
|
.generate();
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "../dist/interface.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int two;
|
||||||
|
} a_t;
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
fn_alone("hi");
|
||||||
|
|
||||||
|
if (!fn_alone(0x0)) {
|
||||||
|
int error_length = last_error_length();
|
||||||
|
char *error = malloc(error_length);
|
||||||
|
last_error_message(error, error_length);
|
||||||
|
printf("Encountered error: %s\n", error);
|
||||||
|
free(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
one.fn_one();
|
||||||
|
// one.two.fn_two();
|
||||||
|
//
|
||||||
|
// two_t two = one.two;
|
||||||
|
// two.three.fn_three();
|
||||||
|
// one.two.three.fn_three();
|
||||||
|
// one_two_fn_two();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// nasp trinitrix {
|
||||||
|
// struct Callback {
|
||||||
|
// func: String,
|
||||||
|
// timeout: String,
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// enum CallbackPriority {
|
||||||
|
// High,
|
||||||
|
// Medium,
|
||||||
|
// Low,
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Some doc comment
|
||||||
|
fn fn_alone(input: String);
|
||||||
|
/// Some doc comment
|
||||||
|
nasp one {
|
||||||
|
/// Some doc comment
|
||||||
|
fn fn_one();
|
||||||
|
nasp two {
|
||||||
|
fn fn_two();
|
||||||
|
nasp three {
|
||||||
|
fn fn_three();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
|
||||||
|
// vim: syntax=rust
|
|
@ -19,12 +19,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
fn print(message: CommandTransferValue);
|
|
||||||
|
|
||||||
nasp trinitrix { {}
|
|
||||||
fn hi honner(name: String) -> String; ;
|
macro_rules! callback {
|
||||||
|
($cmd:expr) => {{
|
||||||
|
println!("{:#?}", $cmd);
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/api.rs"));
|
||||||
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
|
|
||||||
// vim: syntax=rust
|
|
|
@ -18,12 +18,8 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use trixy_macros::trixy_generate;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
trixy_generate! {
|
let input = include_str!(concat!(env!("OUT_DIR"), "/interface.h"));
|
||||||
path: "./examples/main/api.tri"
|
println!("{}", input);
|
||||||
languages: rust, lua, c
|
|
||||||
generate_debug: false
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1 +0,0 @@
|
||||||
api_correct.tri
|
|
|
@ -18,138 +18,119 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//! 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
|
//! ```no_run
|
||||||
//! trixy_generate! {
|
//! use trixy_macros::config::{Language, TrixyConfig};
|
||||||
//! path: "./path/to/trixy/file.tri"
|
//!# fn main() {
|
||||||
//! languages: rust, lua, c
|
//! let config = TrixyConfig::new()
|
||||||
//! generate_debug: false
|
//! .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 std::path::{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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Language {
|
pub enum Language {
|
||||||
Rust,
|
Rust,
|
||||||
Lua,
|
|
||||||
C,
|
C,
|
||||||
|
Lua,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Default, 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)]
|
|
||||||
struct GenerateDebug {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
generate_debug: kw::generate_debug,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
colon: Token![:],
|
|
||||||
raw: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TrixyConfig {
|
pub struct TrixyConfig {
|
||||||
/// The Path to the base command interface config file
|
/// The Path to the base command interface config file
|
||||||
path: Path,
|
pub trixy_path: PathBuf,
|
||||||
|
|
||||||
/// The languages the commands should be exposed in
|
/// The name of the outputted host code (rust and c bindings)
|
||||||
languages: Languages,
|
/// 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
|
/// Should the macro generate Debug trait implementation for each enum
|
||||||
/// These are very useful but completely obscure the `cargo expand` ouput
|
/// These are very useful but completely obscure the `cargo expand` output
|
||||||
generate_debug: GenerateDebug,
|
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 {
|
impl TrixyConfig {
|
||||||
pub fn get_path(&self) -> &std::path::Path {
|
pub fn new<T: Into<String>>(callback_function: T) -> Self {
|
||||||
&self.path.raw
|
Self {
|
||||||
}
|
callback_function: callback_function.into(),
|
||||||
pub fn generate_debug(&self) -> bool {
|
host_code_name: "api.rs".into(),
|
||||||
self.generate_debug.raw
|
c_header_name: "interface.h".into(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for TrixyConfig {
|
pub fn trixy_path<T: Into<PathBuf>>(self, input_path: T) -> Self {
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
Self {
|
||||||
Ok(Self {
|
trixy_path: input_path.into(),
|
||||||
path: input.parse()?,
|
..self
|
||||||
languages: input.parse()?,
|
|
||||||
generate_debug: input.parse()?,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for GenerateDebug {
|
pub fn generate_debug(self, generate_debug: bool) -> Self {
|
||||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
Self {
|
||||||
let kw: kw::generate_debug = input.parse()?;
|
generate_debug,
|
||||||
let colon: Token![:] = input.parse()?;
|
..self
|
||||||
let raw = input.parse::<LitBool>()?.value();
|
|
||||||
Ok(Self {
|
|
||||||
generate_debug: kw,
|
|
||||||
colon,
|
|
||||||
raw,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Path {
|
pub fn dist_dir_path<T: Into<PathBuf>>(self, dist_dir_path: T) -> Self {
|
||||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
Self {
|
||||||
let path: kw::path = input.parse()?;
|
dist_dir_path: Some(dist_dir_path.into()),
|
||||||
let colon: Token![:] = input.parse()?;
|
..self
|
||||||
let raw = PathBuf::from(input.parse::<LitStr>()?.value());
|
|
||||||
Ok(Self { path, colon, raw })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Languages {
|
pub fn check_dist_dir(self, check_dist_dir: bool) -> Self {
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
Self {
|
||||||
let languages: kw::languages = input.parse()?;
|
check_dist_dir,
|
||||||
let colon: Token![:] = input.parse()?;
|
..self
|
||||||
let raw = Punctuated::<Language, Token![,]>::parse_separated_nonempty(input)?;
|
|
||||||
Ok(Self {
|
|
||||||
languages,
|
|
||||||
colon,
|
|
||||||
raw,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Language {
|
pub fn host_code_name<T: Into<PathBuf>>(self, output_path: T) -> Self {
|
||||||
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
|
Self {
|
||||||
let ident: Ident = input.parse()?;
|
host_code_name: output_path.into(),
|
||||||
match &ident.to_string()[..] {
|
..self
|
||||||
"rust" | "Rust" => Ok(Self::Rust),
|
}
|
||||||
"lua" | "Lua" => Ok(Self::Lua),
|
}
|
||||||
"c" | "C" => Ok(Self::C),
|
|
||||||
other => Err(input.error(format!(
|
pub fn c_header_name<T: Into<PathBuf>>(self, output_path: T) -> Self {
|
||||||
"The language: `{}` is not a registered language!",
|
Self {
|
||||||
other
|
c_header_name: output_path.into(),
|
||||||
))),
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn callback_function<T: Into<String>>(self, callback_function: T) -> Self {
|
||||||
|
Self {
|
||||||
|
callback_function: callback_function.into(),
|
||||||
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//! This module generates the c header
|
||||||
|
//! It works by firstly listing the functions and then by grouping them into structures, effectively
|
||||||
|
//! simulating namespaces in c.
|
||||||
|
|
||||||
|
use proc_macro2::{Ident, Punct, Spacing, TokenStream as TokenStream2};
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use trixy_lang_parser::command_spec::{
|
||||||
|
Attribute, CommandSpec, Function, Identifier, NamedType, Namespace,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::TrixyConfig,
|
||||||
|
generate::{c_api::mangle_c_function_ident, identifier_to_rust, type_to_rust},
|
||||||
|
};
|
||||||
|
|
||||||
|
const BEGIN_HEADER_GUARD: &'static str = r"#ifndef TRIXY_MAIN_HEADER
|
||||||
|
#define TRIXY_MAIN_HEADER";
|
||||||
|
const END_HEADER_GUARD: &'static str = r"#endif // ifndef TRIXY_MAIN_HEADER";
|
||||||
|
|
||||||
|
/// This function acts as the core transformative aspect, turning this trixy code into the
|
||||||
|
/// following c header:
|
||||||
|
///
|
||||||
|
/// *Trixy:*
|
||||||
|
/// ```text
|
||||||
|
/// fn fn_alone();
|
||||||
|
/// nasp one {
|
||||||
|
/// fn fn_one();
|
||||||
|
/// nasp two {
|
||||||
|
/// fn fn_two();
|
||||||
|
/// nasp three {
|
||||||
|
/// fn fn_three();
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// *C header:*
|
||||||
|
/// ```text
|
||||||
|
/// #ifndef TRIXY_MAIN_HEADER
|
||||||
|
/// #define TRIXY_MAIN_HEADER
|
||||||
|
///
|
||||||
|
/// extern int fn_alone();
|
||||||
|
/// extern int one_fn_one();
|
||||||
|
/// extern int one_two_fn_two();
|
||||||
|
/// extern int one_two_three_fn_three();
|
||||||
|
///
|
||||||
|
/// typedef struct {
|
||||||
|
/// void (*fn_three)(void);
|
||||||
|
/// } three_t;
|
||||||
|
/// typedef struct {
|
||||||
|
/// void (*fn_two)(void);
|
||||||
|
/// three_t three;
|
||||||
|
/// } two_t;
|
||||||
|
/// typedef struct {
|
||||||
|
/// void (*fn_one)(void);
|
||||||
|
/// two_t two;
|
||||||
|
/// } one_t;
|
||||||
|
///
|
||||||
|
/// const three_t three = {
|
||||||
|
/// .fn_three = one_two_three_fn_three,
|
||||||
|
/// };
|
||||||
|
/// const two_t two = {
|
||||||
|
/// .fn_two = one_two_fn_two,
|
||||||
|
/// .three = three,
|
||||||
|
/// };
|
||||||
|
/// const one_t one = {
|
||||||
|
/// .fn_one = one_fn_one,
|
||||||
|
/// .two = two,
|
||||||
|
/// };
|
||||||
|
/// #endif // ifndef TRIXY_MAIN_HEADER
|
||||||
|
/// ```
|
||||||
|
pub fn generate(trixy: &CommandSpec, _config: &TrixyConfig) -> String {
|
||||||
|
let functions: String = trixy
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.map(|r#fn| function_to_header(r#fn, &[]))
|
||||||
|
.collect();
|
||||||
|
let namespaces: String = trixy
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_header(nasp, &vec![]))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let type_defs: String = trixy
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|nasp| namespace_to_full_typedef(nasp))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let struct_initializer: TokenStream2 = trixy
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_full_struct_init(nasp, &vec![]))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
#struct_initializer
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
format!(
|
||||||
|
"{}\n\n{}\n\n{}\n{}\n{}\n{}\n\n{}",
|
||||||
|
BEGIN_HEADER_GUARD,
|
||||||
|
trixy::traits::errno::ERROR_FUNCTIONS,
|
||||||
|
functions,
|
||||||
|
namespaces,
|
||||||
|
type_defs,
|
||||||
|
output,
|
||||||
|
END_HEADER_GUARD
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_to_header(function: &Function, namespaces: &[&Identifier]) -> String {
|
||||||
|
let doc_comments: String = function
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.map(attribute_to_doc_comment)
|
||||||
|
.collect::<String>();
|
||||||
|
let ident = mangle_c_function_ident(function, namespaces);
|
||||||
|
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_c).collect();
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
extern int #ident(#(#inputs),*);
|
||||||
|
};
|
||||||
|
format!("{}{}\n", doc_comments, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute_to_doc_comment(attribute: &Attribute) -> String {
|
||||||
|
let Attribute::doc(doc_comment) = attribute;
|
||||||
|
format!("/// {}\n", doc_comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named_type_to_c(named_type: &NamedType) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&named_type.name);
|
||||||
|
let r#type = type_to_rust(&named_type.r#type);
|
||||||
|
let c_type = trixy::to_c_name(r#type.to_string());
|
||||||
|
quote! {
|
||||||
|
#c_type #ident
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> TokenStream2 {
|
||||||
|
let mut input_namespaces = namespaces.clone();
|
||||||
|
input_namespaces.push(&nasp.name);
|
||||||
|
|
||||||
|
let ident = identifier_to_rust(&nasp.name);
|
||||||
|
let type_ident = format_ident!("{}_t", ident.to_string());
|
||||||
|
let functions: TokenStream2 = nasp
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.map(|r#fn| function_to_struct_init(r#fn, &input_namespaces))
|
||||||
|
.collect();
|
||||||
|
let namespaces: TokenStream2 = nasp
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(namespace_to_struct_init)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let next_namespace: TokenStream2 = nasp
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_full_struct_init(nasp, &input_namespaces))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#next_namespace
|
||||||
|
|
||||||
|
const #type_ident #ident = {
|
||||||
|
#functions
|
||||||
|
#namespaces
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn function_to_struct_init(function: &Function, namespaces: &[&Identifier]) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&function.identifier);
|
||||||
|
let full_ident = mangle_c_function_ident(function, namespaces);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
. #ident = #full_ident,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namespace_to_struct_init(namespace: &Namespace) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&namespace.name);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
. #ident = #ident ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namespace_to_full_typedef(nasp: &Namespace) -> String {
|
||||||
|
let ident = format_ident!("{}_t", nasp.name.name);
|
||||||
|
let doc_comments = nasp
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.map(attribute_to_doc_comment)
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let functions: TokenStream2 = nasp
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.map(|r#fn| function_to_typedef(r#fn))
|
||||||
|
.collect();
|
||||||
|
let namespaces: TokenStream2 = nasp
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_typedef(nasp))
|
||||||
|
.collect();
|
||||||
|
let next_namespace: String = nasp
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_full_typedef(nasp))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
let namespace = quote! {
|
||||||
|
typedef struct {
|
||||||
|
#functions
|
||||||
|
#namespaces
|
||||||
|
} #ident;
|
||||||
|
};
|
||||||
|
format! {"{}\n{}{}\n", next_namespace, doc_comments, namespace}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_to_typedef(function: &Function) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&function.identifier);
|
||||||
|
|
||||||
|
let output = if let Some(output) = &function.output {
|
||||||
|
let output = output.to_string();
|
||||||
|
quote! { #output, }
|
||||||
|
} else {
|
||||||
|
TokenStream2::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inputs: Vec<TokenStream2> = if function.inputs.is_empty() {
|
||||||
|
vec![quote! { void }]
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
int (* #ident ) (#output #(#inputs),*);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namespace_to_typedef(namespace: &Namespace) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&namespace.name);
|
||||||
|
let type_ident = format_ident!("{}_t", ident.to_string());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#type_ident #ident ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namespace_to_header(nasp: &Namespace, namespaces: &Vec<&Identifier>) -> String {
|
||||||
|
let mut nasps = namespaces.clone();
|
||||||
|
nasps.push(&nasp.name);
|
||||||
|
|
||||||
|
let functions: String = nasp
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.map(|r#fn| function_to_header(r#fn, &nasps))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
let namespaces: String = nasp
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_header(nasp, &nasps))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
format! {"{}\n{}", functions, namespaces}
|
||||||
|
}
|
|
@ -0,0 +1,291 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use trixy_lang_parser::command_spec::{CommandSpec, Function, Identifier, NamedType, Namespace};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::TrixyConfig,
|
||||||
|
generate::{
|
||||||
|
c_api::mangle_c_function_ident, identifier_to_rust, named_type_to_rust, type_to_rust,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This function generates the main c API provided by Trixy.
|
||||||
|
/// This works for example like this:
|
||||||
|
/// Turning this:
|
||||||
|
/// ```text
|
||||||
|
/// nasp trinitrix {
|
||||||
|
/// struct Callback {
|
||||||
|
/// func: String,
|
||||||
|
/// timeout: String,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// enum CallbackPriority {
|
||||||
|
/// High,
|
||||||
|
/// Medium,
|
||||||
|
/// Low,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// to this:
|
||||||
|
/// ```no_run
|
||||||
|
/// pub extern "C" fn exectute_callback(callback: Callback, priority: CallbackPriority) {
|
||||||
|
/// /* Here we simply call your handler function, with the command of the function */
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
|
||||||
|
let functions: TokenStream2 = trixy
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.map(|r#fn| function_to_c(r#fn, &config, &vec![]))
|
||||||
|
.collect();
|
||||||
|
let namespaced_functions: TokenStream2 = trixy
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_c(&nasp, &config, &vec![]))
|
||||||
|
.collect();
|
||||||
|
quote! {
|
||||||
|
#functions
|
||||||
|
#namespaced_functions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namespace_to_c(
|
||||||
|
namespace: &Namespace,
|
||||||
|
config: &TrixyConfig,
|
||||||
|
namespaces: &Vec<&Identifier>,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
let mut namespaces = namespaces.clone();
|
||||||
|
namespaces.push(&namespace.name);
|
||||||
|
|
||||||
|
let functions: TokenStream2 = namespace
|
||||||
|
.functions
|
||||||
|
.iter()
|
||||||
|
.map(|r#fn| function_to_c(&r#fn, &config, &namespaces))
|
||||||
|
.collect();
|
||||||
|
let additional_functions: TokenStream2 = namespace
|
||||||
|
.namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nasp| namespace_to_c(&nasp, &config, &namespaces))
|
||||||
|
.collect();
|
||||||
|
quote! {
|
||||||
|
#functions
|
||||||
|
#additional_functions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_to_c(
|
||||||
|
function: &Function,
|
||||||
|
config: &TrixyConfig,
|
||||||
|
namespaces: &Vec<&Identifier>,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
let ident = mangle_c_function_ident(function, namespaces);
|
||||||
|
let inputs: Vec<TokenStream2> = function
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(named_type_to_rust_trixy)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let callback_function = format_ident!("{}", config.callback_function);
|
||||||
|
|
||||||
|
let command_value: TokenStream2 = function_path_to_rust(&namespaces, &function);
|
||||||
|
|
||||||
|
if let Some(r#type) = &function.output {
|
||||||
|
let output = type_to_rust(&r#type);
|
||||||
|
quote! {
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn #ident(output: *mut #output, #(#inputs),*) -> core::ffi::c_int {
|
||||||
|
#callback_function ! (#command_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn #ident(#(#inputs),*) -> core::ffi::c_int {
|
||||||
|
#callback_function ! (#command_value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named_type_to_rust_trixy(named_type: &NamedType) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&named_type.name);
|
||||||
|
let type_ident = type_to_rust(&named_type.r#type);
|
||||||
|
quote! {
|
||||||
|
#ident : trixy:: #type_ident
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns a function in namespaces to the generated host enum path:
|
||||||
|
/// *trixy code:*
|
||||||
|
/// ```text
|
||||||
|
/// fn fn_alone();
|
||||||
|
/// nasp one {
|
||||||
|
/// fn fn_one();
|
||||||
|
/// nasp two {
|
||||||
|
/// fn fn_two(input: String);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// *rust enum path for fn_alone:*
|
||||||
|
/// ```no_run
|
||||||
|
/// Commands::fn_alone
|
||||||
|
/// ```
|
||||||
|
/// *rust enum path for fn_one:*
|
||||||
|
/// ```no_run
|
||||||
|
/// // `Commands` is just the name for the top-level namespace
|
||||||
|
/// Commands::One(one::One(
|
||||||
|
/// One::fn_one
|
||||||
|
/// ))
|
||||||
|
/// ```
|
||||||
|
/// *rust enum path for fn_two:*
|
||||||
|
/// ```no_run
|
||||||
|
/// Commands::One(one::One(
|
||||||
|
/// one::two::Two(one::two::Two(
|
||||||
|
/// Two::fn_two {input: String}
|
||||||
|
/// ))))
|
||||||
|
/// ```
|
||||||
|
fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) -> TokenStream2 {
|
||||||
|
let function_ident = {
|
||||||
|
let ident = format_ident!("{}", function.identifier.name);
|
||||||
|
if function.inputs.is_empty() {
|
||||||
|
quote! {
|
||||||
|
#ident
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let inputs: Vec<TokenStream2> = function
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(named_type_to_rust_assignment)
|
||||||
|
.collect();
|
||||||
|
quote! {
|
||||||
|
#ident { #(#inputs),* }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if namespaces.is_empty() {
|
||||||
|
quote! {
|
||||||
|
Commands:: #function_ident
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let nasp_pascal_ident = format_ident!(
|
||||||
|
"{}",
|
||||||
|
namespaces
|
||||||
|
.last()
|
||||||
|
.expect("We checked")
|
||||||
|
.name
|
||||||
|
.to_case(Case::Pascal)
|
||||||
|
);
|
||||||
|
|
||||||
|
let namespace_path = nasps_to_path(namespaces);
|
||||||
|
|
||||||
|
let function_call = if !function.inputs.is_empty() {
|
||||||
|
let inputs: Vec<TokenStream2> =
|
||||||
|
function.inputs.iter().map(named_type_to_rust).collect();
|
||||||
|
quote! {
|
||||||
|
#namespace_path :: #nasp_pascal_ident :: #function_ident { #(#inputs),* }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#namespace_path :: #nasp_pascal_ident :: #function_ident
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output: TokenStream2 = namespaces
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.rev()
|
||||||
|
.fold(function_call, |acc, (index, nasp)| {
|
||||||
|
nasp_path_one_part(nasp, &acc, &namespaces, index)
|
||||||
|
});
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named_type_to_rust_assignment(named_type: &NamedType) -> TokenStream2 {
|
||||||
|
let ident = identifier_to_rust(&named_type.name);
|
||||||
|
quote! {
|
||||||
|
#ident : trixy::convert!(#ident)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function add a namespace component to the [input] value like so:
|
||||||
|
/// (taking the example from the [function_path_to_rust] function)
|
||||||
|
/// ```text
|
||||||
|
/// one::two::Two::fn_two [= <input>]
|
||||||
|
/// ->
|
||||||
|
/// one::One::Two(<input>) [= <input>]
|
||||||
|
/// ->
|
||||||
|
/// Commands::One(<input>) [= <input>]
|
||||||
|
/// ```
|
||||||
|
fn nasp_path_one_part(
|
||||||
|
current_nasp: &Identifier,
|
||||||
|
input: &TokenStream2,
|
||||||
|
namespaces: &Vec<&Identifier>,
|
||||||
|
index: usize,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
let namespaces_to_do = &namespaces[..index];
|
||||||
|
|
||||||
|
let ident_pascal = format_ident!("{}", current_nasp.name.to_case(Case::Pascal));
|
||||||
|
|
||||||
|
if index == 0 {
|
||||||
|
quote! {
|
||||||
|
Commands :: #ident_pascal ( #input )
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let ident_pascal_next = format_ident!(
|
||||||
|
"{}",
|
||||||
|
namespaces_to_do
|
||||||
|
.last()
|
||||||
|
.expect("We checked the index")
|
||||||
|
.name
|
||||||
|
.to_case(Case::Pascal)
|
||||||
|
);
|
||||||
|
let namespace_path = nasps_to_path(namespaces_to_do);
|
||||||
|
quote! {
|
||||||
|
#namespace_path :: #ident_pascal_next :: #ident_pascal ( #input )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nasps_to_path(namespaces: &[&Identifier]) -> TokenStream2 {
|
||||||
|
namespaces
|
||||||
|
.iter()
|
||||||
|
.fold(TokenStream2::default(), |acc, nasp| {
|
||||||
|
let ident = format_ident!("{}", nasp.name);
|
||||||
|
if acc.is_empty() {
|
||||||
|
quote! {
|
||||||
|
#ident
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#acc :: #ident
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
|
||||||
|
*
|
||||||
|
* This file is part of the Trixy crate for Trinitrix.
|
||||||
|
*
|
||||||
|
* Trixy is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the Lesser GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* and the Lesser GNU General Public License along with this program.
|
||||||
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use quote::format_ident;
|
||||||
|
use trixy_lang_parser::command_spec::{Function, Identifier};
|
||||||
|
|
||||||
|
pub mod header;
|
||||||
|
pub mod host;
|
||||||
|
|
||||||
|
pub fn mangle_c_function_ident(function: &Function, namespaces: &[&Identifier]) -> Ident {
|
||||||
|
let namespace_str = namespaces.iter().fold(String::default(), |acc, nasp| {
|
||||||
|
if acc.is_empty() {
|
||||||
|
nasp.name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}_{}", acc, nasp.name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if namespace_str.is_empty() {
|
||||||
|
format_ident!("{}", &function.identifier.name)
|
||||||
|
} else {
|
||||||
|
format_ident!("{}_{}", namespace_str, &function.identifier.name)
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,9 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//! This module is responsible for generating the rust code used to interface with the api.
|
//! 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
|
//! That includes the structs and enums declared in the trixy file and the enum used to describe the
|
||||||
//! command being executed.
|
//! command being executed.
|
||||||
|
@ -28,20 +31,23 @@ use convert_case::{Case, Casing};
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use trixy_lang_parser::command_spec::{
|
use trixy_lang_parser::command_spec::{
|
||||||
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier,
|
Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Namespace,
|
||||||
NamedType, Namespace, Structure, Type,
|
Structure,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::config::TrixyConfig;
|
use crate::{
|
||||||
|
config::TrixyConfig,
|
||||||
|
generate::{identifier_to_rust, named_type_to_rust},
|
||||||
|
};
|
||||||
|
|
||||||
thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
|
thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
|
||||||
|
|
||||||
/// This function turns, for example, the following trixy input into this rust code:
|
/// This function turns, for example, the following trixy input into this rust code:
|
||||||
/// ```rust
|
/// ```text
|
||||||
/// nasp trinitrix {
|
/// nasp trinitrix {
|
||||||
/// struct Callback {
|
/// struct Callback {
|
||||||
/// func: Function,
|
/// func: String,
|
||||||
/// timeout: Integer,
|
/// timeout: String,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// enum CallbackPriority {
|
/// enum CallbackPriority {
|
||||||
|
@ -52,20 +58,22 @@ thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
|
||||||
///
|
///
|
||||||
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
/// fn execute_callback(callback: Callback, priority: CallbackPriority);
|
||||||
/// }
|
/// }
|
||||||
|
/// // wrong but helps with syntax highlight:
|
||||||
|
/// // vim: syntax=rust
|
||||||
/// ```
|
/// ```
|
||||||
/// ```rust
|
/// ```no_run
|
||||||
/// #[derive(Debug)]
|
/// #[derive(Debug)]
|
||||||
/// pub enum Commands {
|
/// pub enum Commands {
|
||||||
/// Trinitrix(trinitrix::Trinitrix),
|
/// Trinitrix(trinitrix::Trinitrix),
|
||||||
/// }
|
/// }
|
||||||
/// pub mod trinitrix {
|
/// pub mod trinitrix {
|
||||||
/// #[allow(non_camel_case)]
|
/// #[allow(non_camel_case_types)]
|
||||||
/// #[derive(Debug)]
|
/// #[derive(Debug)]
|
||||||
/// struct Callback {
|
/// struct Callback {
|
||||||
/// func: Function,
|
/// func: String,
|
||||||
/// timeout: Integer,
|
/// timeout: String,
|
||||||
/// }
|
/// }
|
||||||
/// #[allow(non_camel_case)]
|
/// #[allow(non_camel_case_types)]
|
||||||
/// #[derive(Debug)]
|
/// #[derive(Debug)]
|
||||||
/// enum CallbackPriority {
|
/// enum CallbackPriority {
|
||||||
/// High,
|
/// High,
|
||||||
|
@ -74,14 +82,14 @@ thread_local! {static DEBUG: OnceCell<TokenStream2> = OnceCell::new();}
|
||||||
/// }
|
/// }
|
||||||
/// #[derive(Debug)]
|
/// #[derive(Debug)]
|
||||||
/// pub enum Trinitrix {
|
/// pub enum Trinitrix {
|
||||||
/// #[allow(non_camel_case)]
|
/// #[allow(non_camel_case_types)]
|
||||||
/// execute_callback { callback: Callback, priority: CallbackPriority },
|
/// execute_callback { callback: Callback, priority: CallbackPriority },
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
|
pub fn generate(trixy: &CommandSpec, config: &TrixyConfig) -> TokenStream2 {
|
||||||
DEBUG.with(|d| {
|
DEBUG.with(|d| {
|
||||||
d.set(if config.generate_debug() {
|
d.set(if config.generate_debug {
|
||||||
quote! {
|
quote! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
}
|
}
|
||||||
|
@ -190,14 +198,23 @@ fn function_to_rust(function: &Function) -> TokenStream2 {
|
||||||
.map(attribute_to_doc_comment)
|
.map(attribute_to_doc_comment)
|
||||||
.collect();
|
.collect();
|
||||||
let ident = identifier_to_rust(&function.identifier);
|
let ident = identifier_to_rust(&function.identifier);
|
||||||
|
|
||||||
let inputs: Vec<TokenStream2> = function.inputs.iter().map(named_type_to_rust).collect();
|
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! {
|
quote! {
|
||||||
#doc_comments
|
#doc_comments
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#ident {#(#inputs),*}
|
#ident {#(#inputs),*}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 {
|
fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 {
|
||||||
let doc_comments: TokenStream2 = enumeration
|
let doc_comments: TokenStream2 = enumeration
|
||||||
|
@ -216,7 +233,7 @@ fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 {
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#doc_comments
|
#doc_comments
|
||||||
#[allow(non_camel_case)]
|
#[allow(non_camel_case_types)]
|
||||||
#debug
|
#debug
|
||||||
enum #ident {
|
enum #ident {
|
||||||
#(#states),*
|
#(#states),*
|
||||||
|
@ -241,7 +258,7 @@ fn structure_to_rust(structure: &Structure) -> TokenStream2 {
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#doc_comments
|
#doc_comments
|
||||||
#[allow(non_camel_case)]
|
#[allow(non_camel_case_types)]
|
||||||
#debug
|
#debug
|
||||||
struct #ident {
|
struct #ident {
|
||||||
#(#contents),*
|
#(#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 {
|
fn doc_named_type_to_rust(doc_named_type: &DocNamedType) -> TokenStream2 {
|
||||||
let doc_comments: TokenStream2 = doc_named_type
|
let doc_comments: TokenStream2 = doc_named_type
|
||||||
.attributes
|
.attributes
|
||||||
|
@ -282,19 +292,3 @@ fn doc_named_type_to_rust(doc_named_type: &DocNamedType) -> TokenStream2 {
|
||||||
#named_type
|
#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<TokenStream2> = r#type.generic_args.iter().map(type_to_rust).collect();
|
|
||||||
quote! {
|
|
||||||
#ident <#(#generics),*>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,4 +18,55 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* 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 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),*>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,39 +18,91 @@
|
||||||
* If not, see <https://www.gnu.org/licenses/>.
|
* If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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 trixy_lang_parser::parse_trixy_lang;
|
||||||
|
|
||||||
use crate::config::TrixyConfig;
|
use crate::config::TrixyConfig;
|
||||||
|
|
||||||
mod config;
|
pub mod config;
|
||||||
mod generate;
|
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
|
/// This is the heart of Trixy
|
||||||
/// It mainly does one thing:
|
/// It mainly does one thing:
|
||||||
/// - Generate a tree of modules from the input trixy file
|
/// - Generate a tree of modules from the input trixy file
|
||||||
///
|
///
|
||||||
#[proc_macro]
|
pub fn generate(&self) {
|
||||||
pub fn trixy_generate(input: TokenStream) -> TokenStream {
|
let source_code = fs::read_to_string(&self.trixy_path).unwrap_or_else(|err| {
|
||||||
let input = parse_macro_input!(input as TrixyConfig);
|
panic! {"Can't read file at path: '{}'. The Error is: '{}'",
|
||||||
|
self.trixy_path.display(), err};
|
||||||
let source_code = fs::read_to_string(input.get_path()).unwrap_or_else(|err| {
|
|
||||||
panic! {"Can't read file at path: '{}'. The Error is: '{}'", input.get_path().display(), err};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let trixy_code = parse_trixy_lang(&source_code).unwrap_or_else(|err| {
|
let trixy_code = parse_trixy_lang(&source_code).unwrap_or_else(|err| {
|
||||||
panic! {"Parsing of the trixy file failed: \n{}", err}
|
panic! {"Parsing of the trixy file failed: \n{}", err}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build the final rust hosting code
|
// host code
|
||||||
let host_rust_code = generate::host::generate(&trixy_code, &input);
|
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! {
|
// c header
|
||||||
#host_rust_code
|
let c_header = generate::c_api::header::generate(&trixy_code, &self);
|
||||||
};
|
let c_header_path = PathBuf::from(format!(
|
||||||
output.into()
|
"{}/{}",
|
||||||
|
env::var("OUT_DIR").expect("The build script should have this define"),
|
||||||
|
&self.c_header_name.display()
|
||||||
|
));
|
||||||
|
let mut c_header_out =
|
||||||
|
fs::File::create(&c_header_path).expect("This file should always be free to use");
|
||||||
|
write!(c_header_out, "{}\n{}", c_header, VIM_LINE_C).expect("Write should work");
|
||||||
|
|
||||||
|
Command::new("clang-format")
|
||||||
|
.args(["-i", &c_header_path.to_str().unwrap()])
|
||||||
|
.status()
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
panic!(
|
||||||
|
"Failed to format the c header file with `clang-format`; Error: `{}`",
|
||||||
|
err
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(dist_dir) = &self.dist_dir_path {
|
||||||
|
if !dist_dir.is_dir() {
|
||||||
|
fs::create_dir(dist_dir).unwrap_or_else(|err| {
|
||||||
|
panic! {
|
||||||
|
"Failed to create the dist directory ('{}') because of: `{}`",
|
||||||
|
dist_dir.display(), err}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if self.check_dist_dir {
|
||||||
|
if dist_dir.read_dir().iter().count() != 1 {
|
||||||
|
panic!("Your specified dist dir already has something in it! Set `check_dist_dir` to `false` to override this check");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let c_header_dist = PathBuf::from(format!(
|
||||||
|
"{}/{}",
|
||||||
|
dist_dir.display(),
|
||||||
|
self.c_header_name.display()
|
||||||
|
));
|
||||||
|
fs::copy(c_header_path, c_header_dist).unwrap_or_else(
|
||||||
|
|err| panic! {"Failed to copy the c header to the dist dir because of: `{}`", err},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue