| //! FFI interface for `rustc_format_parser` |
| |
| // what's the plan? Have a function return something that can be constructed into a vector? |
| // or an iterator? |
| |
| use std::ffi::CStr; |
| |
| trait IntoFFI<T> { |
| fn into_ffi(self) -> T; |
| } |
| |
| impl<T> IntoFFI<*const T> for Option<T> |
| where |
| T: Sized, |
| { |
| fn into_ffi(self) -> *const T { |
| match self.as_ref() { |
| None => std::ptr::null(), |
| Some(r) => r as *const T, |
| } |
| } |
| } |
| |
| // Extension trait to provide `String::leak` which did not exist in Rust 1.49 |
| pub trait StringLeakExt { |
| fn leak<'a>(self) -> &'a mut str; |
| } |
| |
| impl StringLeakExt for String { |
| fn leak<'a>(self) -> &'a mut str { |
| Box::leak(self.into_boxed_str()) |
| } |
| } |
| |
| // 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<T> -> Option<&T> -> *const T could work maybe? |
| pub mod ffi { |
| use super::IntoFFI; |
| |
| // 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 { |
| ptr: *const u8, |
| len: usize, |
| } |
| |
| impl<'a> From<&'a str> for RustHamster { |
| fn from(s: &'a str) -> RustHamster { |
| RustHamster { |
| ptr: s.as_ptr(), |
| len: s.len(), |
| } |
| } |
| } |
| |
| // 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<InnerWidthMapping>, |
| // }, |
| // } |
| |
| // 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(Debug, Clone, PartialEq)] |
| #[repr(C)] |
| pub enum Piece<'a> { |
| /// A literal string which should directly be emitted |
| String(RustHamster), |
| /// 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(Copy, Clone, Debug, PartialEq)] |
| #[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(Copy, Clone, Debug, PartialEq)] |
| #[repr(C)] |
| pub struct FormatSpec<'a> { |
| /// Optionally specified character to fill alignment with. |
| pub fill: Option<char>, |
| /// Span of the optionally specified fill character. |
| pub fill_span: *const InnerSpan, |
| /// Optionally specified alignment. |
| pub align: Alignment, |
| /// The `+` or `-` flag. |
| pub sign: *const Sign, |
| /// The `#` flag. |
| pub alternate: bool, |
| /// The `0` flag. |
| pub zero_pad: bool, |
| /// The `x` or `X` flag. (Only for `Debug`.) |
| pub debug_hex: *const DebugHex, |
| /// The integer precision to use. |
| pub precision: Count<'a>, |
| /// The span of the precision formatting flag (for diagnostics). |
| pub precision_span: *const InnerSpan, |
| /// The string width requested for the resulting format. |
| pub width: Count<'a>, |
| /// The span of the width formatting flag (for diagnostics). |
| pub width_span: *const InnerSpan, |
| /// 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: &'a str, |
| /// The span of the descriptor string (for diagnostics). |
| pub ty_span: *const InnerSpan, |
| } |
| |
| /// 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(&'a str), |
| } |
| |
| /// 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(&'a str, 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<generic_format_parser::Piece<'a>> for Piece<'a> { |
| fn from(old: generic_format_parser::Piece<'a>) -> Self { |
| match old { |
| generic_format_parser::Piece::String(x) => Piece::String(x.into()), |
| 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::<Argument>::into(*ptr); |
| |
| Piece::NextArgument(dst) |
| } |
| } |
| } |
| } |
| |
| impl<'a> From<generic_format_parser::Argument<'a>> 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<generic_format_parser::Position<'a>> 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()) |
| } |
| } |
| } |
| } |
| |
| impl From<generic_format_parser::InnerSpan> for InnerSpan { |
| fn from(old: generic_format_parser::InnerSpan) -> Self { |
| InnerSpan { |
| start: old.start, |
| end: old.end, |
| } |
| } |
| } |
| |
| impl<'a> From<generic_format_parser::FormatSpec<'a>> for FormatSpec<'a> { |
| fn from(old: generic_format_parser::FormatSpec<'a>) -> Self { |
| FormatSpec { |
| fill: old.fill, |
| 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, |
| ty_span: old.ty_span.map(Into::into).into_ffi(), |
| } |
| } |
| } |
| |
| impl From<generic_format_parser::DebugHex> 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<generic_format_parser::Count<'a>> 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, 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<generic_format_parser::Sign> 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<generic_format_parser::Alignment> 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<usize>, |
| snippet: Option<String>, |
| append_newline: bool, |
| parse_mode: ParseMode, |
| ) -> Vec<Piece<'_>> { |
| 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? |
| #[repr(C)] |
| pub struct PieceSlice { |
| base_ptr: *mut ffi::Piece<'static /* FIXME: That's wrong */>, |
| len: usize, |
| cap: usize, |
| } |
| |
| #[repr(C)] |
| // FIXME: we should probably use FFIString here |
| pub struct RustString { |
| ptr: *const u8, |
| len: usize, |
| cap: usize, |
| } |
| |
| #[repr(C)] |
| pub struct FormatArgsHandle(PieceSlice, RustString); |
| |
| #[no_mangle] |
| pub extern "C" fn collect_pieces( |
| input: *const libc::c_char, |
| append_newline: bool, |
| parse_mode: crate::ffi::ParseMode, |
| ) -> FormatArgsHandle { |
| // FIXME: Add comment |
| let str = unsafe { CStr::from_ptr(input) }; |
| let str = str.to_str().unwrap().to_owned(); |
| |
| // we are never going to free this string here (we leak it later on), so we can extend its lifetime |
| // to send it across an FFI boundary. |
| // FIXME: Is that correct? |
| let s = &str; |
| let s = unsafe { std::mem::transmute::<&'_ str, &'static str>(s) }; |
| |
| // FIXME: No unwrap |
| let pieces: Vec<ffi::Piece<'_>> = |
| rust::collect_pieces(s, None, None, append_newline, parse_mode) |
| .into_iter() |
| .map(Into::into) |
| .collect(); |
| |
| let piece_slice = PieceSlice { |
| len: pieces.len(), |
| cap: pieces.capacity(), |
| base_ptr: pieces.leak().as_mut_ptr(), |
| }; |
| let rust_string = RustString { |
| len: str.len(), |
| cap: str.capacity(), |
| ptr: str.leak().as_ptr(), |
| }; |
| |
| FormatArgsHandle(piece_slice, rust_string) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn destroy_pieces(FormatArgsHandle(piece_slice, s): FormatArgsHandle) { |
| let PieceSlice { base_ptr, len, cap } = piece_slice; |
| drop(Vec::from_raw_parts(base_ptr, len, cap)); |
| |
| let RustString { ptr, len, cap } = s; |
| drop(String::from_raw_parts(ptr as *mut u8, len, cap)); |
| } |
| |
| #[no_mangle] |
| pub extern "C" fn clone_pieces( |
| FormatArgsHandle(piece_slice, s): &FormatArgsHandle, |
| ) -> FormatArgsHandle { |
| let PieceSlice { base_ptr, len, cap } = *piece_slice; |
| |
| let v = unsafe { Vec::from_raw_parts(base_ptr, len, cap) }; |
| let cloned_v = v.clone(); |
| |
| // FIXME: Add documentation |
| v.leak(); |
| |
| let piece_slice = PieceSlice { |
| len: cloned_v.len(), |
| cap: cloned_v.capacity(), |
| base_ptr: cloned_v.leak().as_mut_ptr(), |
| }; |
| |
| let RustString { ptr, len, cap } = *s; |
| let s = unsafe { String::from_raw_parts(ptr as *mut u8, len, cap) }; |
| let cloned_s = s.clone(); |
| |
| // FIXME: Documentation |
| s.leak(); |
| |
| let rust_string = RustString { |
| len: cloned_s.len(), |
| cap: cloned_s.capacity(), |
| ptr: cloned_s.leak().as_ptr(), |
| }; |
| |
| FormatArgsHandle(piece_slice, rust_string) |
| } |