From e52f74b0c1abad552f99d43fbcde394b205a5270 Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 19 Feb 2024 15:50:30 +0100 Subject: [PATCH] feat(treewide): Finalize basic c API See the example under `./example/main/` for more details. --- example/main/.cargo/config | 2 + example/main/.gitignore | 3 + example/main/Cargo.toml | 12 ++ example/main/Makefile | 35 ++++++ example/main/README.md | 2 + example/main/build.rs | 11 ++ example/main/c/main.c | 48 +++++++ example/main/src/bin/api.rs | 4 + example/main/src/bin/main/api.tri | 11 ++ example/main/src/bin/main/main.rs | 40 ++++++ trixy-macros/example/main/Cargo.toml | 36 ------ trixy-macros/example/main/Makefile | 35 ------ trixy-macros/example/main/build.rs | 31 ----- trixy-macros/example/main/c_src/main.c | 52 -------- trixy-macros/example/main/src/api.tri | 54 -------- trixy-macros/example/main/src/main.rs | 38 ------ .../src/generate/c_api/header/pure_header.rs | 4 +- .../src/generate/c_api/header/structs_init.rs | 4 +- trixy-macros/src/generate/c_api/host.rs | 19 ++- trixy-macros/src/generate/c_api/mod.rs | 27 +++- trixy-macros/src/generate/host/mod.rs | 4 +- trixy-parser/src/command_spec/checked.rs | 46 +++++-- trixy-parser/src/parsing/checked/mod.rs | 60 ++++++--- trixy-parser/src/parsing/checked/test.rs | 27 +++- trixy-types/Cargo.toml | 1 + trixy-types/src/error/mod.rs | 6 + trixy-types/src/traits/convert_trait.rs | 117 ++++++++++++------ trixy-types/src/traits/errno.rs | 13 -- trixy-types/src/traits/mod.rs | 2 + trixy-types/src/traits/try_from_impl.rs | 23 ++++ trixy-types/src/types_list.rs | 6 + .../trixy-types-derive}/.gitignore | 5 - trixy-types/trixy-types-derive/Cargo.toml | 14 +++ trixy-types/trixy-types-derive/src/lib.rs | 67 ++++++++++ 34 files changed, 512 insertions(+), 347 deletions(-) create mode 100644 example/main/.cargo/config create mode 100644 example/main/.gitignore create mode 100644 example/main/Cargo.toml create mode 100644 example/main/Makefile create mode 100644 example/main/README.md create mode 100644 example/main/build.rs create mode 100644 example/main/c/main.c create mode 100644 example/main/src/bin/api.rs create mode 100644 example/main/src/bin/main/api.tri create mode 100644 example/main/src/bin/main/main.rs delete mode 100644 trixy-macros/example/main/Cargo.toml delete mode 100644 trixy-macros/example/main/Makefile delete mode 100644 trixy-macros/example/main/build.rs delete mode 100644 trixy-macros/example/main/c_src/main.c delete mode 100644 trixy-macros/example/main/src/api.tri delete mode 100644 trixy-macros/example/main/src/main.rs create mode 100644 trixy-types/src/traits/try_from_impl.rs rename {trixy-macros/example/main => trixy-types/trixy-types-derive}/.gitignore (71%) create mode 100644 trixy-types/trixy-types-derive/Cargo.toml create mode 100644 trixy-types/trixy-types-derive/src/lib.rs diff --git a/example/main/.cargo/config b/example/main/.cargo/config new file mode 100644 index 0000000..52e8b72 --- /dev/null +++ b/example/main/.cargo/config @@ -0,0 +1,2 @@ +[build] +rustflags = ["-C", "link-args=-rdynamic"] diff --git a/example/main/.gitignore b/example/main/.gitignore new file mode 100644 index 0000000..ccaf2b9 --- /dev/null +++ b/example/main/.gitignore @@ -0,0 +1,3 @@ +/target +/result +/dist diff --git a/example/main/Cargo.toml b/example/main/Cargo.toml new file mode 100644 index 0000000..425fdb5 --- /dev/null +++ b/example/main/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "trixy-example" +version = "0.1.0" +edition = "2021" +default-run = "main" + +[dependencies] +libloading = "0.8.1" +trixy = { path = "../../." } + +[build-dependencies] +trixy = { path = "../../." } diff --git a/example/main/Makefile b/example/main/Makefile new file mode 100644 index 0000000..4674515 --- /dev/null +++ b/example/main/Makefile @@ -0,0 +1,35 @@ +BIN_NAME := ./target/plugin.so + +BUILD_DIR := ./target/c_build/ + +SRC := $(wildcard c/*.c) +OBJ := $(SRC:.c=.o) +DEP := $(OBJ:.o=.d) + +LIBS := + +ALL_CFLAGS := -O3 -MMD -Wall -Wextra -Wno-unused-parameter $(CFLAGS) $(CPPFLAGS) +ALL_LDFLAGS := $(addprefix -l,$(LIBS)) -L $(LD_LIBRARY_PATH) $(CFLAGS) $(LDFLAGS) + + +$(BIN_NAME): $(OBJ) + gcc $(addprefix $(BUILD_DIR),$(notdir $(OBJ))) -shared -o $(addprefix $(BUILD_DIR),$(notdir $(BIN_NAME))) $(ALL_CFLAGS) $(ALL_LDFLAGS) + +$(OBJ): $(SRC) + mkdir --parents $(BUILD_DIR) + $(CC) -c -fPIC $< -o $(addprefix $(BUILD_DIR),$(notdir $(OBJ))) $(ALL_CFLAGS) + +.PHONY : clean options +options: + @echo "CC = $(CC)" + @echo "CFLAGS = $(ALL_CFLAGS)" + @echo "LDFLAGS = $(ALL_LDFLAGS)" + @echo "SRC = $(SRC)" + @echo "OBJ = $(OBJ)" + @echo "DEP = $(DEP)" + @echo "" + +clean : + rm $(BIN_NAME) $(OBJ) $(DEP) + rm -r $(BUILD_DIR) + diff --git a/example/main/README.md b/example/main/README.md new file mode 100644 index 0000000..1bbe7df --- /dev/null +++ b/example/main/README.md @@ -0,0 +1,2 @@ +# Reason +This is not a cargo example, as its needs a full build.rs step. diff --git a/example/main/build.rs b/example/main/build.rs new file mode 100644 index 0000000..71c0648 --- /dev/null +++ b/example/main/build.rs @@ -0,0 +1,11 @@ +use trixy::macros::config::TrixyConfig; + +fn main() { + println!("cargo:rerun-if-changed=./dist/*"); + println!("cargo:rerun-if-changed=./src/bin/main/api.tri"); + TrixyConfig::new("handle_cmd") + .trixy_path("./src/bin/main/api.tri") + .dist_dir_path("./dist") + .generate_debug(true) + .generate(); +} diff --git a/example/main/c/main.c b/example/main/c/main.c new file mode 100644 index 0000000..ebd0133 --- /dev/null +++ b/example/main/c/main.c @@ -0,0 +1,48 @@ +#include "../dist/interface.h" +#include +#include +#include +#include + +#define println(args...) \ + printf("(plugin): "); \ + printf(args); \ + printf("\n"); \ + fflush(stdout); +#define eprintln(args...) \ + printf("(plugin): "); \ + printf(args); \ + printf("\n"); \ + fflush(stdout); + +int p_main() { + // You can simply call functions .. + outstanding("James"); + + // .. but they check their inputs: + // This call for example will fail, as a null pointer isn't supported. + // + // A error will be signaled by a 0 return code, then the error can be obtained + // with the `last_error_length` and `last_error_message` functions + if (!outstanding(0x0)) { + int error_length = last_error_length(); + char *error = malloc(error_length); + last_error_message(error, error_length); + eprintln("Encountered error: %s", error); + free(error); + } + + println("Saying hi!"); + string_t hi; + if (!one.hi(&hi, "Adam")) { + int error_length = last_error_length(); + char *error = malloc(error_length); + last_error_message(error, error_length); + eprintln("Encountered error: %s", error); + free(error); + } + println("Rust returned: %s", hi); + string_free(hi); + + return 0; +} diff --git a/example/main/src/bin/api.rs b/example/main/src/bin/api.rs new file mode 100644 index 0000000..0e3a1ff --- /dev/null +++ b/example/main/src/bin/api.rs @@ -0,0 +1,4 @@ +fn main() { + let input = include_str!(concat!(env!("OUT_DIR"), "/api.rs")); + println!("{}", input); +} diff --git a/example/main/src/bin/main/api.tri b/example/main/src/bin/main/api.tri new file mode 100644 index 0000000..887e3c2 --- /dev/null +++ b/example/main/src/bin/main/api.tri @@ -0,0 +1,11 @@ +/// Call out an outstanding person +fn outstanding(name: String); + + +mod one { + /// Say hi to a name + fn hi(name: String) -> String; +} + +// Trixy is a subset of Rust +// vim: syntax=rust cms=//%s diff --git a/example/main/src/bin/main/main.rs b/example/main/src/bin/main/main.rs new file mode 100644 index 0000000..4d685de --- /dev/null +++ b/example/main/src/bin/main/main.rs @@ -0,0 +1,40 @@ +use std::{env, ffi::c_int, mem}; + +use libloading::{Library, Symbol}; + +include!(concat!(env!("OUT_DIR"), "/api.rs")); + +// run `cargo r --bin api > ./src/bin/main/api.rs` to output the generated api +// mod api; +// pub use api::*; + +fn handle_cmd(cmd: Commands) { + match cmd { + Commands::One(one) => match one { + one::One::hi { trixy_output, name } => { + let output = format!("Hi {}!", name); + println!("(rust): {}", output); + trixy_output.send(output.into()).expect("Will work"); + mem::forget(name); + } + }, + Commands::outstanding { name } => { + println!("(rust): {} is outstanding!", name); + mem::forget(name); + } + } +} + +fn main() { + let library_path = env::args().nth(1).expect("USAGE: cargo r -- "); + type AddFunc = unsafe fn() -> c_int; + println!("Loading p_main() from {}", library_path); + + unsafe { + let lib = Library::new(library_path).unwrap(); + let func: Symbol = lib.get(b"p_main").unwrap(); + println!("starting plugin"); + let out = func(); + println!("plugin finished with: {}", out); + } +} diff --git a/trixy-macros/example/main/Cargo.toml b/trixy-macros/example/main/Cargo.toml deleted file mode 100644 index d034080..0000000 --- a/trixy-macros/example/main/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (C) 2023 The Trinitrix Project -# -# This file is part of the Trixy crate for Trinitrix. -# -# Trixy is free software: you can redistribute it and/or modify -# it under the terms of the Lesser GNU General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# and the Lesser GNU General Public License along with this program. -# If not, see . - - -[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" -libloading = "0.8.1" - -[build-dependencies] -trixy-macros = {path = "../../../trixy-macros"} diff --git a/trixy-macros/example/main/Makefile b/trixy-macros/example/main/Makefile deleted file mode 100644 index 458e5b6..0000000 --- a/trixy-macros/example/main/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -PREFIX := /usr/local -BINPREFIX := $(DESTDIR)$(PREFIX)/bin -MANPREFIX := $(DESTDIR)$(PREFIX)/share/man/man1 - -BIN_NAME := libmain.so - -SRC := $(wildcard c_src/*.c) -OBJ := $(SRC:.c=.o) -DEP := $(OBJ:.o=.d) - -LIBS := main-example - -ALL_CFLAGS := -fPIC -O3 -MMD -Wall -Wextra -Wno-unused-parameter $(CFLAGS) $(CPPFLAGS) -ALL_LDFLAGS := -shared $(addprefix -l,$(LIBS)) -L $(LD_LIBRARY_PATH) $(CFLAGS) $(LDFLAGS) - - - -$(BIN_NAME): $(OBJ) - $(CC) -o $@ $+ $(ALL_LDFLAGS) - -.c.o: - $(CC) -o $@ $< -c $(ALL_CFLAGS) - -.PHONY : clean options -options: - @echo "CC = $(CC)" - @echo "CFLAGS = $(ALL_CFLAGS)" - @echo "LDFLAGS = $(ALL_LDFLAGS)" - @echo "SRC = $(SRC)" - @echo "OBJ = $(OBJ)" - @echo "DEP = $(DEP)" - @echo "" - -clean : - rm $(BIN_NAME) $(OBJ) $(DEP) diff --git a/trixy-macros/example/main/build.rs b/trixy-macros/example/main/build.rs deleted file mode 100644 index 23a8a07..0000000 --- a/trixy-macros/example/main/build.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright (C) 2023 The Trinitrix Project -* -* This file is part of the Trixy crate for Trinitrix. -* -* Trixy is free software: you can redistribute it and/or modify -* it under the terms of the Lesser GNU General Public License as -* published by the Free Software Foundation, either version 3 of -* the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* and the Lesser GNU General Public License along with this program. -* If not, see . -*/ - -use trixy_macros::config::TrixyConfig; - -fn main() { - println!("cargo:rustc-link-arg=--relocatable"); - 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 deleted file mode 100644 index 00ac355..0000000 --- a/trixy-macros/example/main/c_src/main.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2023 The Trinitrix Project - * - * This file is part of the Trixy crate for Trinitrix. - * - * Trixy is free software: you can redistribute it and/or modify - * it under the terms of the Lesser GNU General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * and the Lesser GNU General Public License along with this program. - * If not, see . - */ - -#include "../dist/interface.h" -#include -#include - -int main(void) { - // You can simply call functions .. - fn_alone("hi"); - - // .. but they check their inputs: - // This call for example will fail, as a null pointer isn't supported. - // - // A error will be signaled by a 0 return code, then the error can be obtained - // with the `last_error_length` and `last_error_message` functions - 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 deleted file mode 100644 index e43da58..0000000 --- a/trixy-macros/example/main/src/api.tri +++ /dev/null @@ -1,54 +0,0 @@ -/* -* Copyright (C) 2023 The Trinitrix Project -* -* This file is part of the Trixy crate for Trinitrix. -* -* Trixy is free software: you can redistribute it and/or modify -* it under the terms of the Lesser GNU General Public License as -* published by the Free Software Foundation, either version 3 of -* the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* and the Lesser GNU General Public License along with this program. -* If not, see . -*/ - - -// 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/example/main/src/main.rs b/trixy-macros/example/main/src/main.rs deleted file mode 100644 index b2b6628..0000000 --- a/trixy-macros/example/main/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* -* Copyright (C) 2023 The Trinitrix Project -* -* This file is part of the Trixy crate for Trinitrix. -* -* Trixy is free software: you can redistribute it and/or modify -* it under the terms of the Lesser GNU General Public License as -* published by the Free Software Foundation, either version 3 of -* the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* and the Lesser GNU General Public License along with this program. -* If not, see . -*/ - -use std::ffi::c_int; -macro_rules! callback { - ($cmd:expr) => {{ - println!("{:#?}", $cmd); - }}; -} - -include!(concat!(env!("OUT_DIR"), "/api.rs")); - -fn main() { - let out = unsafe { - let lib = libloading::Library::new("./libmain.so").unwrap(); - let func: libloading::Symbol c_int> = lib.get(b"main").unwrap(); - func() - }; - - assert_eq!(out, 0); -} diff --git a/trixy-macros/src/generate/c_api/header/pure_header.rs b/trixy-macros/src/generate/c_api/header/pure_header.rs index a768471..ec9b139 100644 --- a/trixy-macros/src/generate/c_api/header/pure_header.rs +++ b/trixy-macros/src/generate/c_api/header/pure_header.rs @@ -54,7 +54,7 @@ fn structure_to_header(structure: &Structure) -> String { format!( " {} - struct {} {{ + {} {{ {} }};", doc_comments, ident, contents @@ -94,7 +94,7 @@ fn enumeration_to_header(enumeration: &Enumeration) -> String { format!( " {} - enum {} {{ + {} {{ {} }};", doc_comments, ident, states diff --git a/trixy-macros/src/generate/c_api/header/structs_init.rs b/trixy-macros/src/generate/c_api/header/structs_init.rs index 3f25c3b..d980fe9 100644 --- a/trixy-macros/src/generate/c_api/header/structs_init.rs +++ b/trixy-macros/src/generate/c_api/header/structs_init.rs @@ -19,7 +19,7 @@ fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier> input_namespaces.push(&nasp.name); let ident = identifier_to_rust(&nasp.name); - let type_ident = format_ident!("{}_t", ident.to_string()); + let type_ident = format_ident!("{}", ident.to_string()); let functions: TokenStream2 = nasp .functions .iter() @@ -40,7 +40,7 @@ fn namespace_to_full_struct_init(nasp: &Namespace, namespaces: &Vec<&Identifier> quote! { #next_namespace - const #type_ident #ident = { + const struct #type_ident #ident = { #functions #namespaces }; diff --git a/trixy-macros/src/generate/c_api/host.rs b/trixy-macros/src/generate/c_api/host.rs index 65c5873..f6a3a5a 100644 --- a/trixy-macros/src/generate/c_api/host.rs +++ b/trixy-macros/src/generate/c_api/host.rs @@ -27,7 +27,7 @@ use crate::{ config::TrixyConfig, generate::{ c_api::{mangle_c_function_ident, namespaces_to_path, type_to_c_equalivalent}, - function_identifier_to_rust, identifier_to_rust, + function_identifier_to_rust, identifier_to_rust, type_to_rust, }, }; @@ -117,13 +117,16 @@ fn function_to_c( let output_ident = type_to_c_equalivalent(&r#type, &namespaces); quote! { #[no_mangle] - pub unsafe extern "C" fn #ident(output: *mut #output_ident, #(#inputs),*) -> core::ffi::c_int { + pub extern "C" fn #ident(output: *mut #output_ident, #(#inputs),*) -> core::ffi::c_int { let output_val: #output_ident = { let (tx, rx) = trixy::oneshot::channel(); #callback_function (#command_value); - rx.recv().expect("The channel should not be close until this value is received").into() + let recv = rx.recv().expect("The channel should not be closed until this value is received"); + recv.into() }; - output.write(output_val); + unsafe { + std::ptr::write(output, output_val); + } return 1; } } @@ -218,7 +221,13 @@ fn function_path_to_rust(namespaces: &Vec<&Identifier>, function: &Function) -> fn named_type_to_rust_assignment(named_type: &NamedType) -> TokenStream2 { let ident = identifier_to_rust(&named_type.name); quote! { - #ident : trixy::types::convert!(#ident) + #ident : match #ident.try_into() { + Ok(ok) => ok, + Err(err) => { + trixy::types::traits::errno::set(err); + return 0; + } + } } } diff --git a/trixy-macros/src/generate/c_api/mod.rs b/trixy-macros/src/generate/c_api/mod.rs index c46b8d5..ead982e 100644 --- a/trixy-macros/src/generate/c_api/mod.rs +++ b/trixy-macros/src/generate/c_api/mod.rs @@ -21,7 +21,7 @@ use convert_case::{Case, Casing}; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::{format_ident, quote}; -use trixy_parser::command_spec::{Function, Identifier, Type}; +use trixy_parser::command_spec::{Function, Identifier, Type, Variant}; use super::identifier_to_rust; @@ -57,9 +57,28 @@ pub fn type_to_c(r#type: &Type, is_output: bool) -> TokenStream2 { } } pub fn identifier_to_c(identifier: &Identifier) -> TokenStream2 { - let ident = format_ident!("{}_t", identifier.name.to_case(Case::Snake)); - quote! { - #ident + match identifier.variant { + Variant::Structure => { + let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal)); + quote! { + struct #ident + } + } + Variant::Enumeration => { + let ident = format_ident!("{}", identifier.name.to_case(Case::Pascal)); + quote! { + enum #ident + } + } + Variant::Primitive => { + let ident = format_ident!("{}_t", identifier.name.to_case(Case::Snake)); + quote! { + #ident + } + } + other => { + unimplemented!("{:#?}", other) + } } } pub fn type_to_c_equalivalent(r#type: &Type, namespaces: &[&Identifier]) -> TokenStream2 { diff --git a/trixy-macros/src/generate/host/mod.rs b/trixy-macros/src/generate/host/mod.rs index 89d3623..cd98c5d 100644 --- a/trixy-macros/src/generate/host/mod.rs +++ b/trixy-macros/src/generate/host/mod.rs @@ -253,7 +253,7 @@ fn enumeration_to_rust(enumeration: &Enumeration) -> TokenStream2 { #[allow(non_camel_case_types)] #debug #[repr(C)] - #[derive(Convertible, TypeInfo)] + #[derive(Convertible)] pub enum #ident { #(#states),* } @@ -280,7 +280,7 @@ fn structure_to_rust(structure: &Structure) -> TokenStream2 { #[allow(non_camel_case_types)] #debug #[repr(C)] - #[derive(Convertible, TypeInfo)] + #[derive(Convertible)] pub struct #ident { #(#contents),* } diff --git a/trixy-parser/src/command_spec/checked.rs b/trixy-parser/src/command_spec/checked.rs index 4efc6a8..273f6f0 100644 --- a/trixy-parser/src/command_spec/checked.rs +++ b/trixy-parser/src/command_spec/checked.rs @@ -26,6 +26,29 @@ use crate::lexing::TokenKind; use super::unchecked; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Variant { + Structure, + Enumeration, + Namespace, + Function, + NamedType, + Primitive, + DocNamedType, + Void, +} + +impl Default for Variant { + fn default() -> Self { + Self::Void + } +} +impl Variant { + pub fn new() -> Self { + Self::default() + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Namespace { pub name: Identifier, @@ -128,16 +151,20 @@ impl From<&DocNamedType> for NamedType { } } -impl From for Identifier { - fn from(value: TokenKind) -> Self { - match value { - TokenKind::Identifier(ident) => Identifier { name: ident }, +impl TokenKind { + pub fn to_identifier(self: TokenKind, variant: Variant) -> Identifier { + match self { + TokenKind::Identifier(ident) => Identifier { + name: ident, + variant, + }, _ => { panic!( - "Tried to convert a non Identifier TokenKind to a Identefier. This is a bug - Token was: '{}' - ", - value + " + Tried to convert a non Identifier TokenKind to a + Identifier. This is a bug. The token was: '{}' + ", + self ) } } @@ -166,11 +193,13 @@ impl From for Attribute { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct Identifier { pub name: String, + pub variant: Variant, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct DocIdentifier { pub name: String, + pub variant: Variant, pub attributes: Vec, } @@ -178,6 +207,7 @@ impl From<&DocIdentifier> for Identifier { fn from(value: &DocIdentifier) -> Self { Self { name: value.name.to_owned(), + variant: value.variant, } } } diff --git a/trixy-parser/src/parsing/checked/mod.rs b/trixy-parser/src/parsing/checked/mod.rs index f9cf434..b1d4a9d 100644 --- a/trixy-parser/src/parsing/checked/mod.rs +++ b/trixy-parser/src/parsing/checked/mod.rs @@ -35,6 +35,7 @@ use crate::{ NamedType as UncheckedNamedType, Namespace as UncheckedNamespace, Structure as UncheckedStructure, Type as UncheckedType, }, + Variant, }, error::ErrorContext, lexing::{TokenKind, TokenSpan}, @@ -92,10 +93,14 @@ impl Parser { ) -> Result { let namespace_span = namespace.name.span; let name = match namespace.name.kind { - TokenKind::Identifier(ident) => Identifier { name: ident }, + TokenKind::Identifier(ident) => Identifier { + name: ident, + variant: Variant::Namespace, + }, // This is not really used, so the value put here does not matter TokenKind::Dummy => Identifier { name: "".to_owned(), + variant: Variant::Void, }, _ => unreachable!("This should never be more than these two enum veriants"), }; @@ -144,7 +149,7 @@ impl Parser { &mut self, mut function: UncheckedFunction, ) -> Result { - let identifier = mem::take(&mut function.identifier.kind).into(); + let identifier = mem::take(&mut function.identifier.kind).to_identifier(Variant::Function); let mut inputs = vec![]; for input in function.inputs { inputs.push(self.process_named_type(input)?); @@ -172,7 +177,8 @@ impl Parser { self.enumerations.push(enumeration.clone()); let enum_span = enumeration.identifier.span; - let identifier: Identifier = mem::take(&mut enumeration.identifier.kind).into(); + let identifier: Identifier = + mem::take(&mut enumeration.identifier.kind).to_identifier(Variant::Enumeration); if &identifier == namespace_name { return Err(ParsingError::EnumWithNamespaceName { name: identifier.clone(), @@ -191,10 +197,12 @@ impl Parser { let mut states = vec![]; for mut state in enumeration.states { states.push({ - let ident: Identifier = mem::take(&mut state.token.kind).into(); + let ident: Identifier = + mem::take(&mut state.token.kind).to_identifier(Variant::DocNamedType); DocIdentifier { name: ident.name, attributes: pass_attrs_along!(state), + variant: Variant::DocNamedType, } }) } @@ -212,7 +220,8 @@ impl Parser { ) -> Result { self.structures.push(structure.clone()); - let identifier: Identifier = mem::take(&mut structure.identifier.kind).into(); + let identifier: Identifier = + mem::take(&mut structure.identifier.kind).to_identifier(Variant::Structure); let mut contents = vec![]; for named_type in structure.contents { contents.push(self.process_doc_named_type(named_type)?); @@ -229,7 +238,8 @@ impl Parser { &mut self, mut named_type: UncheckedNamedType, ) -> Result { - let name: Identifier = mem::take(&mut named_type.name.kind).into(); + let name: Identifier = + mem::take(&mut named_type.name.kind).to_identifier(Variant::NamedType); let r#type: Type = self.process_type(named_type.r#type)?; Ok(NamedType { name, r#type }) } @@ -237,7 +247,8 @@ impl Parser { &mut self, mut doc_named_type: UncheckedDocNamedType, ) -> Result { - let name: Identifier = mem::take(&mut doc_named_type.name.kind).into(); + let name: Identifier = + mem::take(&mut doc_named_type.name.kind).to_identifier(Variant::DocNamedType); let r#type: Type = self.process_type(doc_named_type.r#type)?; Ok(DocNamedType { name, @@ -247,27 +258,40 @@ impl Parser { } fn process_type(&mut self, mut r#type: UncheckedType) -> Result { - let identifier: Identifier = mem::take(&mut r#type.identifier.kind).into(); + let identifier: Identifier = + mem::take(&mut r#type.identifier.kind).to_identifier(Variant::Void); - if !self + let variant: Variant; + if self .structures .iter() - .map(|r#struct| Into::::into(r#struct.identifier.kind.clone())) + .map(|r#struct| (r#struct.identifier.kind.clone()).to_identifier(Variant::Void)) .any(|ident| ident == identifier) - && !self - .enumerations - .iter() - .map(|r#enum| Into::::into(r#enum.identifier.kind.clone())) - .any(|ident| ident == identifier) - && !BASE_TYPES - .iter() - .any(|(ident, _)| ident == &identifier.name) { + variant = Variant::Structure; + } else if self + .enumerations + .iter() + .map(|r#enum| (r#enum.identifier.kind.clone()).to_identifier(Variant::Void)) + .any(|ident| ident == identifier) + { + variant = Variant::Enumeration; + } else if BASE_TYPES + .iter() + .any(|(ident, _)| ident == &identifier.name) + { + variant = Variant::Primitive; + } else { return Err(ParsingError::TypeNotDeclared { r#type: identifier, span: r#type.identifier.span, }); } + let identifier: Identifier = Identifier { + name: identifier.name, + variant, + }; + { let fitting_types: Vec<&usize> = BASE_TYPES .iter() diff --git a/trixy-parser/src/parsing/checked/test.rs b/trixy-parser/src/parsing/checked/test.rs index 35c30f4..5aa7040 100644 --- a/trixy-parser/src/parsing/checked/test.rs +++ b/trixy-parser/src/parsing/checked/test.rs @@ -22,6 +22,7 @@ use crate::command_spec::checked::{ Attribute, CommandSpec, DocIdentifier, DocNamedType, Enumeration, Function, Identifier, NamedType, Namespace, Structure, Type, }; +use crate::command_spec::Variant; use crate::lexing::TokenStream; use pretty_assertions::assert_eq; @@ -59,19 +60,23 @@ mod trinitrix { namespaces: vec![Namespace { name: Identifier { name: "trinitrix".to_owned(), + variant: Variant::Namespace, }, functions: vec![Function { identifier: Identifier { name: "execute_callback".to_owned(), + variant: Variant::Function, }, inputs: vec![ NamedType { name: Identifier { name: "callback".to_owned(), + variant: Variant::NamedType, }, r#type: Type { identifier: Identifier { name: "Callback".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }, @@ -79,10 +84,12 @@ mod trinitrix { NamedType { name: Identifier { name: "priority".to_owned(), + variant: Variant::NamedType, }, r#type: Type { identifier: Identifier { name: "CallbackPriority".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }, @@ -94,15 +101,18 @@ mod trinitrix { structures: vec![Structure { identifier: Identifier { name: "Callback".to_owned(), + variant: Variant::Structure, }, contents: vec![ DocNamedType { name: Identifier { name: "func".to_owned(), + variant: Variant::DocNamedType, }, r#type: Type { identifier: Identifier { name: "()".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }, @@ -111,10 +121,12 @@ mod trinitrix { DocNamedType { name: Identifier { name: "timeout".to_owned(), + variant: Variant::DocNamedType, }, r#type: Type { identifier: Identifier { name: "u8".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }, @@ -126,19 +138,23 @@ mod trinitrix { enumerations: vec![Enumeration { identifier: Identifier { name: "CallbackPriority".to_owned(), + variant: Variant::Enumeration, }, states: vec![ DocIdentifier { name: "High".to_owned(), attributes: vec![], + variant: Variant::DocNamedType, }, DocIdentifier { name: "Medium".to_owned(), attributes: vec![], + variant: Variant::DocNamedType, }, DocIdentifier { name: "Low".to_owned(), attributes: vec![], + variant: Variant::DocNamedType, }, ], attributes: vec![], @@ -171,7 +187,8 @@ fn execute_callback(callback: Name); assert_eq!( r#type, Identifier { - name: "Name".to_owned() + name: "Name".to_owned(), + variant: Variant::Type, } ) } @@ -202,14 +219,17 @@ mod trinitrix { functions: vec![Function { identifier: Identifier { name: "print".to_owned(), + variant: Variant::Function, }, inputs: vec![NamedType { name: Identifier { name: "message".to_owned(), + variant: Variant::NamedType, }, r#type: Type { identifier: Identifier { name: "String".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }, @@ -220,18 +240,22 @@ mod trinitrix { namespaces: vec![Namespace { name: Identifier { name: "trinitrix".to_owned(), + variant: Variant::Namespace, }, functions: vec![Function { identifier: Identifier { name: "hi".to_owned(), + variant: Variant::Function, }, inputs: vec![NamedType { name: Identifier { name: "name".to_owned(), + variant: Variant::NamedType, }, r#type: Type { identifier: Identifier { name: "String".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }, @@ -239,6 +263,7 @@ mod trinitrix { output: Some(Type { identifier: Identifier { name: "String".to_owned(), + variant: Variant::Type, }, generic_args: vec![], }), diff --git a/trixy-types/Cargo.toml b/trixy-types/Cargo.toml index 9d2e2f5..b869fbe 100644 --- a/trixy-types/Cargo.toml +++ b/trixy-types/Cargo.toml @@ -30,3 +30,4 @@ proc-macro2 = "1.0.70" quote = "1.0.33" syn = { version = "2.0.41", features = ["extra-traits", "full", "parsing"] } thiserror = "1.0.51" +trixy-types-derive = {path = "./trixy-types-derive"} diff --git a/trixy-types/src/error/mod.rs b/trixy-types/src/error/mod.rs index c6ae1a2..1d3122c 100644 --- a/trixy-types/src/error/mod.rs +++ b/trixy-types/src/error/mod.rs @@ -27,6 +27,9 @@ pub enum TypeConversionError { #[error("Can't convert this ('{got:#?}') string into a valid rust string; Remember that these must be valid utf-8")] String { got: *const c_char }, + #[error("Failed to parse this cstring into a rust string (which are valid utf-8)")] + CString(#[from] std::ffi::IntoStringError), + #[error("You passed a null pointer to the conversion function!")] NullPointer, @@ -38,4 +41,7 @@ pub enum TypeConversionError { #[error("The returned Result was an error: {original_message}")] ResultWasErr { original_message: String }, + + #[error("This funciton is not admissable")] + NotAdmissable, } diff --git a/trixy-types/src/traits/convert_trait.rs b/trixy-types/src/traits/convert_trait.rs index 13b761e..ea3ea62 100644 --- a/trixy-types/src/traits/convert_trait.rs +++ b/trixy-types/src/traits/convert_trait.rs @@ -1,4 +1,5 @@ use crate::error::{self, TypeConversionError}; +use log::warn; use std::{ error::Error, ffi::{c_char, CString}, @@ -8,14 +9,24 @@ use std::{ use super::errno; +pub use trixy_types_derive::Convertible; + /// Convert a value into a data structure that we can send directly to c. -pub trait Convertible { +pub trait Convertible +where + Self: Sized, +{ type Ptr; /// Turn the value into a c void pointer or a data structure that c can understand. fn into_ptr(self) -> Self::Ptr; + fn from_ptr(ptr: Self::Ptr) -> Result; + /// If the data returned by [`into_ptr`] needs to be dropped this function should do it. - fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError>; + fn drop_ptr(ptr: Self::Ptr) { + let val = Self::from_ptr(ptr); + drop(val); + } } macro_rules! primitive_convert { @@ -27,8 +38,9 @@ macro_rules! primitive_convert { self } - fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> { - unreachable!("Theres is no need to drop a {}", stringify!($name)) + fn from_ptr(_: Self::Ptr) -> Result<$name, error::TypeConversionError> { + warn!("Dropping a {}; Theres is no need to drop a {}", stringify!($name), stringify!($name)); + Ok(<$name>::default()) } } } @@ -51,35 +63,52 @@ primitive_convert!(f32); primitive_convert!(f64); impl Convertible for CString { - type Ptr = *const *const c_char; + type Ptr = *const c_char; fn into_ptr(self) -> Self::Ptr { - self.into_raw() as *const *const c_char + self.into_raw() as *const c_char } - fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError> { - unsafe { - if ptr.is_null() { - return Err(TypeConversionError::NullPointer); - } - // TODO(@soispha): Add this, when this feature is stable <2024-02-17> - // if !ptr.is_aligned() { - // return Err(TypeConversionError::NotAligned); - // } - - // SAFETY: - // Checked that the ptr is not null and that it is aligned (to c_char). - // This should hopefully be enough to avoid constructing a CString from a pointer not - // constructed by a us earlier in the [`into_ptr`] function. - // However: - // This still builds on the hope that c passes the pointer with the length unchanged. - let string = CString::from_raw(ptr as *mut i8); - drop(string); + fn from_ptr(ptr: Self::Ptr) -> Result { + if ptr.is_null() { + return Err(TypeConversionError::NullPointer); } - Ok(()) + // TODO(@soispha): Add this, when this feature is stable <2024-02-17> + // if !ptr.is_aligned() { + // return Err(TypeConversionError::NotAligned); + // } + + // SAFETY: + // Checked that the ptr is not null and that it is aligned (to c_char). + // This should hopefully be enough to avoid constructing a CString from a pointer not + // constructed by a us earlier in the [`into_ptr`] function. + // However: + // This still builds on the hope that c passes the pointer with the length unchanged. + let string = unsafe { CString::from_raw(ptr as *mut i8) }; + Ok(string) } } +impl Convertible for std::string::String { + type Ptr = ::Ptr; + + fn into_ptr(self) -> Self::Ptr { + let c_string = + CString::new(self).expect("This will always work; the input value is a valid string"); + let ptr: Self::Ptr = c_string.into_ptr(); + ptr + } + + fn from_ptr(_: Self::Ptr) -> Result { + unreachable!("This should not be called, call CString directly"); + } +} + +#[no_mangle] +pub extern "C" fn string_free(ptr: *const c_char) { + CString::drop_ptr(ptr); +} + impl Convertible for Option { type Ptr = *const T; @@ -92,14 +121,15 @@ impl Convertible for Option { } } - fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> { - unreachable!( + fn from_ptr(_: Self::Ptr) -> Result { + warn!( " This should never be called, as this option type resolves to a null pointer or to a pointer to the real value (e. g. a `crate::Vec`) " ); + Ok(Option::default()) } } @@ -121,14 +151,16 @@ impl Convertible for Result { } } - fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> { - unreachable!( + fn from_ptr(_: Self::Ptr) -> Result { + warn!( " This should never be called, as this result type resolves to a null pointer (and a set error) or to a pointer to the real value (e. g. a `crate::Vec`) " ); + todo!() + // Ok(Result::Ok(T::default())) } } @@ -144,23 +176,26 @@ impl Convertible for Vec { } } - fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError> { + fn from_ptr(ptr: Self::Ptr) -> Result { + if ptr.data.is_null() { + return Err(TypeConversionError::NullPointer); + } + // TODO(@soispha): Add this, when this feature is stable <2024-02-17> + // if !ptr.data.is_aligned() { + // return Err(TypeConversionError::NotAligned); + // } let vec = unsafe { - if ptr.data.is_null() { - return Err(TypeConversionError::NullPointer); - } - - // TODO(@soispha): Add this, when this feature is stable <2024-02-17> - // if !ptr.data.is_aligned() { - // return Err(TypeConversionError::NotAligned); - // } - // SAFETY: // We simply hope that c treated our vector as read-only and didn't modify it. // See the SAFETY section of the String implementation for more detail. Vec::from_raw_parts(ptr.data as *mut T, ptr.length, ptr.capacity) }; - drop(vec); - Ok(()) + Ok(vec) } } + +// FIXME(@soispha): Add the required generic support here <2024-02-18> +#[no_mangle] +pub extern "C" fn vec_free(ptr: crate::Vec) { + Vec::drop_ptr(ptr); +} diff --git a/trixy-types/src/traits/errno.rs b/trixy-types/src/traits/errno.rs index 67d1e24..814596a 100644 --- a/trixy-types/src/traits/errno.rs +++ b/trixy-types/src/traits/errno.rs @@ -30,19 +30,6 @@ 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::types::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); diff --git a/trixy-types/src/traits/mod.rs b/trixy-types/src/traits/mod.rs index 62ad4b8..66ef3f8 100644 --- a/trixy-types/src/traits/mod.rs +++ b/trixy-types/src/traits/mod.rs @@ -20,3 +20,5 @@ pub mod convert_trait; pub mod errno; +mod try_from_impl; +pub use try_from_impl::*; diff --git a/trixy-types/src/traits/try_from_impl.rs b/trixy-types/src/traits/try_from_impl.rs new file mode 100644 index 0000000..4e4040b --- /dev/null +++ b/trixy-types/src/traits/try_from_impl.rs @@ -0,0 +1,23 @@ +use crate::String; +use std::ffi::CString; + +use super::convert_trait::Convertible; + +impl From for String { + fn from(value: std::string::String) -> Self { + let cstring = + CString::new(value).expect("The input is a valid String, this always succeeds"); + let c_char = cstring.into_ptr(); + String(c_char) + } +} + +impl TryFrom for std::string::String { + type Error = crate::error::TypeConversionError; + + fn try_from(value: String) -> Result { + let cstring = CString::from_ptr(value.0)?; + let string = cstring.into_string()?; + Ok(string) + } +} diff --git a/trixy-types/src/types_list.rs b/trixy-types/src/types_list.rs index f20fda9..0e77f56 100644 --- a/trixy-types/src/types_list.rs +++ b/trixy-types/src/types_list.rs @@ -1,6 +1,12 @@ // 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> +use std::ffi::c_char; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct String(pub(crate) *const c_char); + #[derive(Debug)] #[repr(C)] pub struct Vec { diff --git a/trixy-macros/example/main/.gitignore b/trixy-types/trixy-types-derive/.gitignore similarity index 71% rename from trixy-macros/example/main/.gitignore rename to trixy-types/trixy-types-derive/.gitignore index 38848a7..20c0ba9 100644 --- a/trixy-macros/example/main/.gitignore +++ b/trixy-types/trixy-types-derive/.gitignore @@ -1,11 +1,6 @@ # build /target /result -/dist - -# C stuff -*.d -*.o # This crate is a library Cargo.lock diff --git a/trixy-types/trixy-types-derive/Cargo.toml b/trixy-types/trixy-types-derive/Cargo.toml new file mode 100644 index 0000000..97536ea --- /dev/null +++ b/trixy-types/trixy-types-derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "trixy-types-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +syn = "1.0" +quote = "1.0" +proc-macro2 = "1.0.78" + +[lib] +proc-macro = true diff --git a/trixy-types/trixy-types-derive/src/lib.rs b/trixy-types/trixy-types-derive/src/lib.rs new file mode 100644 index 0000000..221a7c6 --- /dev/null +++ b/trixy-types/trixy-types-derive/src/lib.rs @@ -0,0 +1,67 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Fields, FieldsNamed}; + +#[proc_macro_derive(Convertible)] +pub fn convertible_derive(input: TokenStream) -> TokenStream { + let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let name = &ast.ident; + + let drop_impl = match ast.data { + syn::Data::Struct(stru) => { + let field_drop = if let Fields::Named(FieldsNamed { + brace_token: _, + named, + }) = stru.fields + { + named + .iter() + .map(|field| field.ident.as_ref().expect("All are under NamedFields")) + .map(|name| { + quote! { + self.#name.drop_ptr()?; + } + }) + .collect::() + } else { + unimplemented!() + }; + + quote! { + impl trixy::types::traits::convert_trait::Convertible for #name { + type Ptr = #name; + + fn into_ptr(self) -> Self::Ptr { + self + } + + fn from_ptr(ptr: Self::Ptr) -> Result { + // #field_drop + // Ok(()) + Err(trixy::types::error::TypeConversionError::NotAdmissable) + } + } + } + } + syn::Data::Enum(_) => { + quote! { + impl trixy::types::traits::convert_trait::Convertible for #name { + type Ptr = #name ; + + fn into_ptr(self) -> Self::Ptr { + self + } + + fn from_ptr(ptr: Self::Ptr) -> Result { + // log::warn!("Drop does nothing on enums! (Called for {})", stringify!(#name)); + Err(trixy::types::error::TypeConversionError::NotAdmissable) + } + } + } + } + syn::Data::Union(_) => panic!("This derive macro does nothing for unions"), + }; + + drop_impl.into() +}