Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python language binding using libnanobind #767

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/src/ast/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,11 @@ impl TypeName {
if let syn::PathArguments::AngleBracketed(type_args) =
&p.path.segments.last().unwrap().arguments
{
assert!(
type_args.args.len() > 1,
"Not enough arguments given to Result<T,E>. Are you using a non-std Result type?"
);

if let (syn::GenericArgument::Type(ok), syn::GenericArgument::Type(err)) =
(&type_args.args[0], &type_args.args[1])
{
Expand Down
3 changes: 3 additions & 0 deletions tool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod dart;
mod demo_gen;
mod js;
mod kotlin;
mod python;

use colored::*;
use core::mem;
Expand Down Expand Up @@ -57,6 +58,7 @@ pub fn gen(
demo_gen::attr_support()
}
"kotlin" => kotlin::attr_support(),
"python" => python::attr_support(),
o => panic!("Unknown target: {}", o),
};

Expand All @@ -73,6 +75,7 @@ pub fn gen(
"cpp" => cpp::run(&tcx),
"dart" => dart::run(&tcx, docs_url_gen),
"js" => js::run(&tcx, docs_url_gen),
"python" => python::run(&tcx),
"demo_gen" => {
let conf = library_config.map(|c| {
let str = std::fs::read_to_string(c)
Expand Down
56 changes: 56 additions & 0 deletions tool/src/python/binding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use askama::Template;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::fmt::{self};
use std::string::String;

/// This abstraction allows us to build up the binding piece by piece without needing
/// to precalculate things like the list of dependent headers or classes
#[derive(Default, Template)]
#[template(path = "python/binding.cpp.jinja", escape = "none")]
pub(super) struct Binding<'a> {
/// The module name for this binding
pub module_name: Cow<'a, str>,
/// A list of includes
///
/// Example:
/// ```c
/// #include "Foo.h"
/// #include "Bar.h"
/// #include "diplomat_runtime.h"
/// ```
pub includes: BTreeSet<Cow<'a, str>>,
/// The actual meat of the impl: usually will contain a type definition and methods
///
/// Example:
/// ```c
/// typedef struct Foo {
/// uint8_t field1;
/// bool field2;
/// } Foo;
///
/// Foo make_foo(uint8_t field1, bool field2);
/// ```
pub body: String,
}

impl Binding<'_> {
pub fn new() -> Self {
Binding {
includes: BTreeSet::new(),
..Default::default()
}
}
}

impl fmt::Write for Binding<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.body.write_str(s)
}
fn write_char(&mut self, c: char) -> fmt::Result {
self.body.write_char(c)
}
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
self.body.write_fmt(args)
}
}
168 changes: 168 additions & 0 deletions tool/src/python/formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//! This module contains functions for formatting types

use crate::c::{CFormatter, CAPI_NAMESPACE};
use diplomat_core::hir::{self, StringEncoding, TypeContext, TypeId};
use std::borrow::Cow;

/// This type mediates all formatting
///
/// All identifiers from the HIR should go through here before being formatted
/// into the output: This makes it easy to handle reserved words or add rename support
///
/// If you find yourself needing an identifier formatted in a context not yet available here, please add a new method
///
/// This type may be used by other backends attempting to figure out the names
/// of C types and methods.
pub(crate) struct PyFormatter<'tcx> {
pub c: CFormatter<'tcx>,
}

impl<'tcx> PyFormatter<'tcx> {
pub fn new(tcx: &'tcx TypeContext) -> Self {
Self { c: CFormatter::new(tcx, true) }
}

/// Resolve and format the nested module names for this type
/// Returns an iterator to the namespaces. Will always have at least one entry
pub fn fmt_namespaces(&self, id: TypeId) -> impl Iterator<Item = Cow<'tcx, str>> {
let resolved = self.c.tcx().resolve_type(id);
resolved.attrs().namespace.as_deref().unwrap_or("m").split("::").map(Cow::Borrowed)
}

/// Resolve the name of the module to use
pub fn fmt_module(&self, id: TypeId) -> Cow<'tcx, str> {
self.fmt_namespaces(id).last().unwrap()
}

/// Resolve and format a named type for use in code (without the namespace)
pub fn fmt_type_name_unnamespaced(&self, id: TypeId) -> Cow<'tcx, str> {
let resolved = self.c.tcx().resolve_type(id);

resolved.attrs().rename.apply(resolved.name().as_str().into())
}

/// Resolve and format a named type for use in code
pub fn fmt_type_name(&self, id: TypeId) -> Cow<'tcx, str> {
let resolved = self.c.tcx().resolve_type(id);
let name = resolved.attrs().rename.apply(resolved.name().as_str().into());
if let Some(ref ns) = resolved.attrs().namespace {
format!("{ns}::{name}").into()
} else {
name
}
}

/// Resolve and format the name of a type for use in header names
pub fn fmt_decl_header_path(&self, id: TypeId) -> String {
let resolved = self.c.tcx().resolve_type(id);
let type_name = resolved.attrs().rename.apply(resolved.name().as_str().into());
if let Some(ref ns) = resolved.attrs().namespace {
let ns = ns.replace("::", "/");
format!("../cpp/{ns}/{type_name}.d.hpp")
} else {
format!("../cpp/{type_name}.d.hpp")
}
}

/// Resolve and format the name of a type for use in header names
pub fn fmt_impl_file_path(&self, id: TypeId) -> String {
let resolved = self.c.tcx().resolve_type(id);
let type_name = resolved.attrs().rename.apply(resolved.name().as_str().into());
if let Some(ref ns) = resolved.attrs().namespace {
let ns = ns.replace("::", "/");
format!("../cpp/{ns}/{type_name}.hpp")
} else {
format!("../cpp/{type_name}.hpp")
}
}

/// Format a field name or parameter name
// might need splitting in the future if we decide to support renames here
pub fn fmt_param_name<'a>(&self, ident: &'a str) -> Cow<'a, str> {
ident.into()
}

pub fn fmt_c_type_name(&self, id: TypeId) -> Cow<'tcx, str> {
self.c.fmt_type_name_maybe_namespaced(id.into())
}

pub fn fmt_c_ptr<'a>(&self, ident: &'a str, mutability: hir::Mutability) -> Cow<'a, str> {
self.c.fmt_ptr(ident, mutability)
}

pub fn fmt_optional(&self, ident: &str) -> String {
format!("std::optional<{ident}>")
}

pub fn fmt_borrowed<'a>(&self, ident: &'a str, mutability: hir::Mutability) -> Cow<'a, str> {
// TODO: Where is the right place to put `const` here?
if mutability.is_mutable() {
format!("{ident}&").into()
} else {
format!("const {ident}&").into()
}
}

pub fn fmt_move_ref<'a>(&self, ident: &'a str) -> Cow<'a, str> {
format!("{ident}&&").into()
}

pub fn fmt_optional_borrowed<'a>(&self, ident: &'a str, mutability: hir::Mutability) -> Cow<'a, str> {
self.c.fmt_ptr(ident, mutability)
}

pub fn fmt_owned<'a>(&self, ident: &'a str) -> Cow<'a, str> {
format!("std::unique_ptr<{ident}>").into()
}

pub fn fmt_borrowed_slice<'a>(&self, ident: &'a str, mutability: hir::Mutability) -> Cow<'a, str> {
// TODO: This needs to change if an abstraction other than std::span is used
// TODO: Where is the right place to put `const` here?
if mutability.is_mutable() {
format!("diplomat::span<{ident}>").into()
} else {
format!("diplomat::span<const {ident}>").into()
}
}

pub fn fmt_borrowed_str(&self, encoding: StringEncoding) -> Cow<'static, str> {
// TODO: This needs to change if an abstraction other than std::u8string_view is used
match encoding {
StringEncoding::Utf8 | StringEncoding::UnvalidatedUtf8 => "std::string_view".into(),
StringEncoding::UnvalidatedUtf16 => "std::u16string_view".into(),
_ => unreachable!(),
}
}

pub fn fmt_owned_str(&self) -> Cow<'static, str> {
"std::string".into()
}

/// Format a method
pub fn fmt_method_name<'a>(&self, method: &'a hir::Method) -> Cow<'a, str> {
let name = method.attrs.rename.apply(method.name.as_str().into());

// TODO(#60): handle other keywords
if name == "new" {
"new_".into()
} else if name == "default" {
"default_".into()
} else {
name
}
}

pub fn namespace_c_method_name(&self, ty: TypeId, name: &str) -> String {
let resolved = self.c.tcx().resolve_type(ty);
if let Some(ref ns) = resolved.attrs().namespace {
format!("{ns}::{CAPI_NAMESPACE}::{name}")
} else {
format!("diplomat::{CAPI_NAMESPACE}::{name}")
}
}

/// Get the primitive type as a C type
pub fn fmt_primitive_as_c(&self, prim: hir::PrimitiveType) -> Cow<'static, str> {
self.c.fmt_primitive_as_c(prim)
}
}
Loading