//! FFI interface for `rustc_format_parser` use std::alloc::Layout; // what's the plan? Have a function return something that can be constructed into a vector? // or an iterator? trait IntoFFI { fn into_ffi(self) -> T; } // FIXME: Make an ffi module in a separate file // FIXME: Remember to leak the boxed type somehow // FIXME: How to encode the Option type? As a pointer? Option -> Option<&T> -> *const T could work maybe? pub mod ffi { use super::IntoFFI; use std::marker::PhantomData; use std::mem::MaybeUninit; #[repr(C)] pub struct FFIVec { data: *mut T, len: usize, cap: usize } impl IntoFFI> for Vec { fn into_ffi(mut self) -> FFIVec { let ret = FFIVec { data: self.as_mut_ptr(), len: self.len(), cap: self.capacity() }; self.leak(); ret } } impl Drop for FFIVec { fn drop(&mut self) { unsafe { Vec::from_raw_parts(self.data, self.len, self.cap); } } } impl FFIVec { fn with_vec_ref FnOnce(&'a Vec) -> R>( &self, f: F ) -> R { let v = unsafe { Vec::from_raw_parts(self.data, self.len, self.cap) }; let ret = f(&v); v.leak(); ret } // currently unused // may be nice to have later, though #[allow(unused)] fn with_vec_mut_ref FnOnce(&'a mut Vec) -> R>( &mut self, f: F ) -> R { let mut v = unsafe { Vec::from_raw_parts(self.data, self.len, self.cap) }; let ret = f(&mut v); self.data = v.as_mut_ptr(); self.len = v.len(); self.cap = v.capacity(); v.leak(); ret } } impl Clone for FFIVec where T: Clone { fn clone(&self) -> FFIVec { self.with_vec_ref(|v| v.clone().into_ffi()) } } // https://github.com/rust-lang/rfcs/blob/master/text/2195-really-tagged-unions.md #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum FFIOpt { Some(T), None } impl IntoFFI> for Option { fn into_ffi(self) -> FFIOpt { match self { Some(v) => FFIOpt::Some(v), None => FFIOpt::None } } } // FIXME: We need to ensure we deal with memory properly - whether it's owned by the C++ side or the Rust side #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[repr(C)] pub struct RustHamster<'a> { ptr: *const u8, len: usize, phantom: PhantomData<&'a u8> } impl<'a> IntoFFI> for &'a str { fn into_ffi(self) -> RustHamster<'a> { RustHamster { ptr: self.as_ptr(), len: self.len(), phantom: PhantomData, } } } impl<'a> RustHamster<'a> { pub fn as_str(&self) -> &'a str { unsafe { let slice: &'a [u8] = std::slice::from_raw_parts(self.ptr, self.len); std::str::from_utf8_unchecked(slice) } } } // Note: copied from rustc_span /// Range inside of a `Span` used for diagnostics when we only have access to relative positions. #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[repr(C)] pub struct InnerSpan { pub start: usize, pub end: usize, } /// The location and before/after width of a character whose width has changed from its source code /// representation #[derive(Copy, Clone, PartialEq, Eq)] #[repr(C)] pub struct InnerWidthMapping { /// Index of the character in the source pub position: usize, /// The inner width in characters pub before: usize, /// The transformed width in characters pub after: usize, } // TODO: Not needed for now? // /// Whether the input string is a literal. If yes, it contains the inner width mappings. // #[derive(Clone, PartialEq, Eq)] // #[repr(C)] // enum InputStringKind { // NotALiteral, // Literal { // width_mappings: Vec, // }, // } // TODO: Not needed for now? // /// The type of format string that we are parsing. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(C)] pub enum ParseMode { /// A normal format string as per `format_args!`. Format = 0, /// An inline assembly template string for `asm!`. InlineAsm, } /// A piece is a portion of the format string which represents the next part /// to emit. These are emitted as a stream by the `Parser` class. #[derive(Clone)] #[repr(C)] pub enum Piece<'a> { /// A literal string which should directly be emitted String(RustHamster<'a>), /// This describes that formatting should process the next argument (as /// specified inside) for emission. // do we need a pointer here? we're doing big cloning anyway NextArgument(Argument<'a>), } /// Representation of an argument specification. #[derive(Clone)] #[repr(C)] pub struct Argument<'a> { /// Where to find this argument pub position: Position<'a>, /// The span of the position indicator. Includes any whitespace in implicit /// positions (`{ }`). pub position_span: InnerSpan, /// How to format the argument pub format: FormatSpec<'a>, } /// Specification for the formatting of an argument in the format string. #[derive(Clone)] #[repr(C)] pub struct FormatSpec<'a> { /// Optionally specified character to fill alignment with. pub fill: FFIOpt, /// Span of the optionally specified fill character. pub fill_span: FFIOpt, /// Optionally specified alignment. pub align: Alignment, /// The `+` or `-` flag. pub sign: FFIOpt, /// The `#` flag. pub alternate: bool, /// The `0` flag. pub zero_pad: bool, /// The `x` or `X` flag. (Only for `Debug`.) pub debug_hex: FFIOpt, /// The integer precision to use. pub precision: Count<'a>, /// The span of the precision formatting flag (for diagnostics). pub precision_span: FFIOpt, /// The string width requested for the resulting format. pub width: Count<'a>, /// The span of the width formatting flag (for diagnostics). pub width_span: FFIOpt, /// The descriptor string representing the name of the format desired for /// this argument, this can be empty or any number of characters, although /// it is required to be one word. pub ty: RustHamster<'a>, /// The span of the descriptor string (for diagnostics). pub ty_span: FFIOpt, } /// Enum describing where an argument for a format can be located. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] pub enum Position<'a> { /// The argument is implied to be located at an index ArgumentImplicitlyIs(usize), /// The argument is located at a specific index given in the format, ArgumentIs(usize), /// The argument has a name. ArgumentNamed(RustHamster<'a>), } /// Enum of alignments which are supported. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] pub enum Alignment { /// The value will be aligned to the left. AlignLeft, /// The value will be aligned to the right. AlignRight, /// The value will be aligned in the center. AlignCenter, /// The value will take on a default alignment. AlignUnknown, } /// Enum for the sign flags. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] pub enum Sign { /// The `+` flag. Plus, /// The `-` flag. Minus, } /// Enum for the debug hex flags. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] pub enum DebugHex { /// The `x` flag in `{:x?}`. Lower, /// The `X` flag in `{:X?}`. Upper, } /// A count is used for the precision and width parameters of an integer, and /// can reference either an argument or a literal integer. #[derive(Copy, Clone, Debug, PartialEq)] #[repr(C)] pub enum Count<'a> { /// The count is specified explicitly. CountIs(usize), /// The count is specified by the argument with the given name. CountIsName(RustHamster<'a>, InnerSpan), /// The count is specified by the argument at the given index. CountIsParam(usize), /// The count is specified by a star (like in `{:.*}`) that refers to the argument at the given index. CountIsStar(usize), /// The count is implied and cannot be explicitly specified. CountImplied, } impl<'a> From> for Piece<'a> { fn from(old: generic_format_parser::Piece<'a>) -> Self { match old { generic_format_parser::Piece::String(x) => Piece::String(x.into_ffi()), generic_format_parser::Piece::NextArgument(x) => { // FIXME: This is problematic - if we do this, then we probably run into the issue that the Box // is freed at the end of the call to collect_pieces. if we just .leak() it, then we have // a memory leak... should we resend the info back to the Rust lib afterwards to free it? // this is definitely the best way - store that pointer in the FFI piece and rebuild the box // in a Rust destructor let ptr = Box::leak(x); let dst = Into::::into(*ptr); Piece::NextArgument(dst) } } } } impl<'a> From> for Argument<'a> { fn from(old: generic_format_parser::Argument<'a>) -> Self { Argument { position: old.position.into(), position_span: old.position_span.into(), format: old.format.into(), } } } impl<'a> From> for Position<'a> { fn from(old: generic_format_parser::Position<'a>) -> Self { match old { generic_format_parser::Position::ArgumentImplicitlyIs(x) => { Position::ArgumentImplicitlyIs(x.into()) } generic_format_parser::Position::ArgumentIs(x) => Position::ArgumentIs(x.into()), generic_format_parser::Position::ArgumentNamed(x) => { Position::ArgumentNamed(x.into_ffi()) } } } } impl From for InnerSpan { fn from(old: generic_format_parser::InnerSpan) -> Self { InnerSpan { start: old.start, end: old.end, } } } impl<'a> From> for FormatSpec<'a> { fn from(old: generic_format_parser::FormatSpec<'a>) -> Self { FormatSpec { fill: old.fill.into_ffi(), fill_span: old.fill_span.map(Into::into).into_ffi(), align: old.align.into(), sign: old.sign.map(Into::into).into_ffi(), alternate: old.alternate, zero_pad: old.zero_pad, debug_hex: old.debug_hex.map(Into::into).into_ffi(), precision: old.precision.into(), precision_span: old.precision_span.map(Into::into).into_ffi(), width: old.width.into(), width_span: old.width_span.map(Into::into).into_ffi(), ty: old.ty.into_ffi(), ty_span: old.ty_span.map(Into::into).into_ffi(), } } } impl From for DebugHex { fn from(old: generic_format_parser::DebugHex) -> Self { match old { generic_format_parser::DebugHex::Lower => DebugHex::Lower, generic_format_parser::DebugHex::Upper => DebugHex::Upper, } } } impl<'a> From> for Count<'a> { fn from(old: generic_format_parser::Count<'a>) -> Self { match old { generic_format_parser::Count::CountIs(x) => Count::CountIs(x), generic_format_parser::Count::CountIsName(x, y) => Count::CountIsName(x.into_ffi(), y.into()), generic_format_parser::Count::CountIsParam(x) => Count::CountIsParam(x), generic_format_parser::Count::CountIsStar(x) => Count::CountIsStar(x), generic_format_parser::Count::CountImplied => Count::CountImplied, } } } impl From for Sign { fn from(old: generic_format_parser::Sign) -> Self { match old { generic_format_parser::Sign::Plus => Sign::Plus, generic_format_parser::Sign::Minus => Sign::Minus, } } } impl From for Alignment { fn from(old: generic_format_parser::Alignment) -> Self { match old { generic_format_parser::Alignment::AlignLeft => Alignment::AlignLeft, generic_format_parser::Alignment::AlignRight => Alignment::AlignRight, generic_format_parser::Alignment::AlignCenter => Alignment::AlignCenter, generic_format_parser::Alignment::AlignUnknown => Alignment::AlignUnknown, } } } } // FIXME: Rename? pub mod rust { use crate::ffi::ParseMode; use generic_format_parser::{Parser, Piece}; pub fn collect_pieces( input: &str, style: Option, snippet: Option, append_newline: bool, parse_mode: ParseMode, ) -> Vec> { let converted_parse_mode = match parse_mode { ParseMode::Format => generic_format_parser::ParseMode::Format, ParseMode::InlineAsm => generic_format_parser::ParseMode::InlineAsm, }; let parser = Parser::new(input, style, snippet, append_newline, converted_parse_mode); parser.into_iter().collect() } } // TODO: Should we instead make an FFIVector struct? type PieceVec<'a> = ffi::FFIVec>; #[no_mangle] pub extern "C" fn collect_pieces<'a>( input: ffi::RustHamster<'a>, append_newline: bool, parse_mode: crate::ffi::ParseMode, ) -> PieceVec<'a> { // FIXME: No unwrap let pieces: Vec> = rust::collect_pieces(input.as_str(), None, None, append_newline, parse_mode) .into_iter() .map(Into::into) .collect(); pieces.into_ffi() } #[no_mangle] pub extern "C" fn clone_pieces<'a, 'b>( piece_vec: &'a PieceVec<'b> ) -> PieceVec<'b> { piece_vec.clone() } // we need Layout::repeat // function signature is a bit different, so call it repeat_x trait LayoutExt { fn repeat_x(&self, n: usize) -> Layout; } impl LayoutExt for Layout { fn repeat_x(&self, n: usize) -> Layout { let elem = self.pad_to_align(); let total_size = elem.size().checked_mul(n).unwrap(); Layout::from_size_align(total_size, elem.align()).unwrap() } } #[no_mangle] pub unsafe extern "C" fn rust_ffi_alloc( count: usize, elem_size: usize, align: usize ) -> *mut u8 { unsafe { std::alloc::alloc( Layout::from_size_align_unchecked(elem_size, align) .repeat_x(count) ) } } #[no_mangle] pub unsafe extern "C" fn rust_ffi_dealloc( data: *mut u8, count: usize, elem_size: usize, align: usize ) { unsafe { std::alloc::dealloc( data, Layout::from_size_align_unchecked(elem_size, align) .repeat_x(count) ) } }