Skip to content

Commit

Permalink
Add Python language binding using libnanobind
Browse files Browse the repository at this point in the history
  • Loading branch information
Walter-Reactor committed Jan 17, 2025
1 parent 22aa043 commit a61f1a8
Show file tree
Hide file tree
Showing 13 changed files with 1,082 additions and 0 deletions.
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

0 comments on commit a61f1a8

Please sign in to comment.