feat(treewide): Finalize basic c API

See the example under `./example/main/` for more details.
This commit is contained in:
Benedikt Peetz 2024-02-19 15:50:30 +01:00
parent e205ea4625
commit e52f74b0c1
Signed by: bpeetz
GPG Key ID: A5E94010C3A642AD
34 changed files with 512 additions and 347 deletions

View File

@ -0,0 +1,2 @@
[build]
rustflags = ["-C", "link-args=-rdynamic"]

3
example/main/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
/result
/dist

12
example/main/Cargo.toml Normal file
View File

@ -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 = "../../." }

35
example/main/Makefile Normal file
View File

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

2
example/main/README.md Normal file
View File

@ -0,0 +1,2 @@
# Reason
This is not a cargo example, as its needs a full build.rs step.

11
example/main/build.rs Normal file
View File

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

48
example/main/c/main.c Normal file
View File

@ -0,0 +1,48 @@
#include "../dist/interface.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,4 @@
fn main() {
let input = include_str!(concat!(env!("OUT_DIR"), "/api.rs"));
println!("{}", input);
}

View File

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

View File

@ -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 -- <LIB>");
type AddFunc = unsafe fn() -> c_int;
println!("Loading p_main() from {}", library_path);
unsafe {
let lib = Library::new(library_path).unwrap();
let func: Symbol<AddFunc> = lib.get(b"p_main").unwrap();
println!("starting plugin");
let out = func();
println!("plugin finished with: {}", out);
}
}

View File

@ -1,36 +0,0 @@
# Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
#
# This file is part of the Trixy crate for Trinitrix.
#
# Trixy is free software: you can redistribute it and/or modify
# it under the terms of the Lesser GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# and the Lesser GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
[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"}

View File

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

View File

@ -1,31 +0,0 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -1,52 +0,0 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu,
* antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "../dist/interface.h"
#include <stdio.h>
#include <stdlib.h>
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;
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
// nasp trinitrix {
// struct Callback {
// func: String,
// timeout: String,
// };
//
// enum CallbackPriority {
// High,
// Medium,
// Low,
// };
//
// fn execute_callback(callback: Callback, priority: CallbackPriority);
// }
/// Some doc comment
fn fn_alone(input: String);
/// Some doc comment
nasp one {
/// Some doc comment
fn fn_one();
nasp two {
fn fn_two();
nasp three {
fn fn_three();
}
}
}
// That's a flat out lie, but it results in a rather nice syntax highlight compared to nothing:
// vim: syntax=rust

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
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<unsafe extern "C" fn() -> c_int> = lib.get(b"main").unwrap();
func()
};
assert_eq!(out, 0);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TokenKind> 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<unchecked::Attribute> 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<Attribute>,
}
@ -178,6 +207,7 @@ impl From<&DocIdentifier> for Identifier {
fn from(value: &DocIdentifier) -> Self {
Self {
name: value.name.to_owned(),
variant: value.variant,
}
}
}

View File

@ -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<Namespace, ParsingError> {
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<Function, ParsingError> {
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<Structure, ParsingError> {
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<NamedType, ParsingError> {
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<DocNamedType, ParsingError> {
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<Type, ParsingError> {
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::<Identifier>::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::<Identifier>::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()

View File

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

View File

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

View File

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

View File

@ -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<Self, error::TypeConversionError>;
/// 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<Self, error::TypeConversionError> {
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 = <CString as Convertible>::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<Self, error::TypeConversionError> {
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<T: Convertible> Convertible for Option<T> {
type Ptr = *const T;
@ -92,14 +121,15 @@ impl<T: Convertible> Convertible for Option<T> {
}
}
fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> {
unreachable!(
fn from_ptr(_: Self::Ptr) -> Result<Self, error::TypeConversionError> {
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<T>`)
"
);
Ok(Option::default())
}
}
@ -121,14 +151,16 @@ impl<T: Convertible, E: Error> Convertible for Result<T, E> {
}
}
fn drop_ptr(_: Self::Ptr) -> Result<(), error::TypeConversionError> {
unreachable!(
fn from_ptr(_: Self::Ptr) -> Result<Self, error::TypeConversionError> {
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<T>`)
"
);
todo!()
// Ok(Result::Ok(T::default()))
}
}
@ -144,23 +176,26 @@ impl<T> Convertible for Vec<T> {
}
}
fn drop_ptr(ptr: Self::Ptr) -> Result<(), error::TypeConversionError> {
fn from_ptr(ptr: Self::Ptr) -> Result<Self, error::TypeConversionError> {
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<u8>) {
Vec::drop_ptr(ptr);
}

View File

@ -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<Option<Box<TypeConversionError>>> = RefCell::new(None);

View File

@ -20,3 +20,5 @@
pub mod convert_trait;
pub mod errno;
mod try_from_impl;
pub use try_from_impl::*;

View File

@ -0,0 +1,23 @@
use crate::String;
use std::ffi::CString;
use super::convert_trait::Convertible;
impl From<std::string::String> 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<String> for std::string::String {
type Error = crate::error::TypeConversionError;
fn try_from(value: String) -> Result<Self, Self::Error> {
let cstring = CString::from_ptr(value.0)?;
let string = cstring.into_string()?;
Ok(string)
}
}

View File

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

View File

@ -1,11 +1,6 @@
# build
/target
/result
/dist
# C stuff
*.d
*.o
# This crate is a library
Cargo.lock

View File

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

View File

@ -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::<TokenStream2>()
} 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<Self, trixy::types::error::TypeConversionError> {
// #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<Self, trixy::types::error::TypeConversionError> {
// 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()
}