Skip to content
209 changes: 204 additions & 5 deletions godot-codegen/src/special_cases/special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,22 +232,221 @@ pub fn is_method_private(class_or_builtin_ty: &TyName, godot_method_name: &str)
#[rustfmt::skip]
pub fn is_builtin_method_exposed(builtin_ty: &TyName, godot_method_name: &str) -> bool {
match (builtin_ty.godot_ty.as_str(), godot_method_name) {
// TODO maybe consider renaming "match_" -> "matches". The "*n" could technically be "*_n", but is probably OK.

// GString
| ("String", "casecmp_to")
| ("String", "nocasecmp_to")
| ("String", "naturalcasecmp_to")
| ("String", "naturalnocasecmp_to")
| ("String", "filecasecmp_to")
| ("String", "filenocasecmp_to")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these functions return a Ordering? They do return -1, 0, 1 corresponding to less/equal/greater. Same with StringName below

| ("String", "get_slice")
| ("String", "get_slicec")
| ("String", "get_slice_count")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a some functions like these that can fail that should maybe return Option<T> instead, though get_slice actually is a bit weird in that there are two failure cases that have different behavior.

| ("String", "find")
| ("String", "findn")
| ("String", "count")
| ("String", "countn")
| ("String", "rfind")
| ("String", "rfindn")
| ("String", "match")
| ("String", "matchn")
| ("String", "begins_with")
| ("String", "ends_with")
| ("String", "is_subsequence_of")
| ("String", "is_subsequence_ofn")
| ("String", "bigrams")
| ("String", "similarity")
| ("String", "replace")
| ("String", "replacen")
| ("String", "repeat")
| ("String", "reverse")
| ("String", "insert")
| ("String", "erase")
| ("String", "capitalize")
| ("String", "to_camel_case")
| ("String", "to_pascal_case")
| ("String", "to_snake_case")
| ("String", "split")
| ("String", "rsplit")
| ("String", "split_floats")
| ("String", "join")
| ("String", "to_upper")
| ("String", "to_lower")
| ("String", "left")
| ("String", "right")
| ("String", "strip_edges")
| ("String", "strip_escapes")
| ("String", "lstrip")
| ("String", "rstrip")
| ("String", "get_extension")
| ("String", "get_basename")
| ("String", "path_join")
| ("String", "unicode_at")
| ("String", "indent")
| ("String", "dedent")
| ("String", "md5_text")
| ("String", "sha1_text")
| ("String", "sha256_text")
| ("String", "md5_buffer")
| ("String", "sha1_buffer")
| ("String", "sha256_buffer")
| ("String", "is_empty")
| ("String", "contains")
| ("String", "containsn")
| ("String", "is_absolute_path")
| ("String", "is_relative_path")
| ("String", "simplify_path")
| ("String", "get_base_dir")
| ("String", "get_file")
| ("String", "xml_escape")
| ("String", "xml_unescape")
| ("String", "uri_encode")
| ("String", "uri_decode")
| ("String", "c_escape")
| ("String", "c_unescape")
| ("String", "json_escape")
| ("String", "validate_node_name")
| ("String", "validate_filename")
| ("String", "is_valid_identifier")
| ("String", "is_valid_int")
| ("String", "is_valid_float")
| ("String", "is_valid_hex_number")
| ("String", "is_valid_html_color")
| ("String", "is_valid_ip_address")
| ("String", "is_valid_filename")
| ("String", "to_int")
| ("String", "to_float")
| ("String", "hex_to_int")
| ("String", "bin_to_int")
| ("String", "lpad")
| ("String", "rpad")
| ("String", "pad_decimals")
| ("String", "pad_zeros")
| ("String", "trim_prefix")
| ("String", "trim_suffix")
| ("String", "to_ascii_buffer")
| ("String", "to_utf8_buffer")
| ("String", "to_utf16_buffer")
| ("String", "to_utf32_buffer")
| ("String", "hex_decode")
| ("String", "to_wchar_buffer")
| ("String", "num_scientific")
| ("String", "num")
| ("String", "num_int64")
| ("String", "num_uint64")
| ("String", "chr")
| ("String", "humanize_size")

// StringName
| ("StringName", "casecmp_to")
| ("StringName", "nocasecmp_to")
| ("StringName", "naturalcasecmp_to")
| ("StringName", "naturalnocasecmp_to")
| ("StringName", "filecasecmp_to")
| ("StringName", "filenocasecmp_to")
| ("StringName", "get_slice")
| ("StringName", "get_slicec")
| ("StringName", "get_slice_count")
| ("StringName", "find")
| ("StringName", "findn")
| ("StringName", "count")
| ("StringName", "countn")
| ("StringName", "rfind")
| ("StringName", "rfindn")
| ("StringName", "match")
| ("StringName", "matchn")
| ("StringName", "begins_with")
| ("StringName", "ends_with")
| ("StringName", "is_subsequence_of")
| ("StringName", "is_subsequence_ofn")
| ("StringName", "bigrams")
| ("StringName", "similarity")
| ("StringName", "replace")
| ("StringName", "replacen")
| ("StringName", "repeat")
| ("StringName", "reverse")
| ("StringName", "insert")
| ("StringName", "erase")
| ("StringName", "capitalize")
| ("StringName", "to_camel_case")
| ("StringName", "to_pascal_case")
| ("StringName", "to_snake_case")
| ("StringName", "split")
| ("StringName", "rsplit")
| ("StringName", "split_floats")
| ("StringName", "join")
| ("StringName", "to_upper")
| ("StringName", "to_lower")
| ("StringName", "left")
| ("StringName", "right")
| ("StringName", "strip_edges")
| ("StringName", "strip_escapes")
| ("StringName", "lstrip")
| ("StringName", "rstrip")
| ("StringName", "get_extension")
| ("StringName", "get_basename")
| ("StringName", "path_join")
| ("StringName", "unicode_at")
| ("StringName", "indent")
| ("StringName", "dedent")
| ("StringName", "md5_text")
| ("StringName", "sha1_text")
| ("StringName", "sha256_text")
| ("StringName", "md5_buffer")
| ("StringName", "sha1_buffer")
| ("StringName", "sha256_buffer")
| ("StringName", "is_empty")
| ("StringName", "contains")
| ("StringName", "containsn")
| ("StringName", "is_absolute_path")
| ("StringName", "is_relative_path")
| ("StringName", "simplify_path")
| ("StringName", "get_base_dir")
| ("StringName", "get_file")
| ("StringName", "xml_escape")
| ("StringName", "xml_unescape")
| ("StringName", "uri_encode")
| ("StringName", "uri_decode")
| ("StringName", "c_escape")
| ("StringName", "c_unescape")
| ("StringName", "json_escape")
| ("StringName", "validate_node_name")
| ("StringName", "validate_filename")
| ("StringName", "is_valid_identifier")
| ("StringName", "is_valid_int")
| ("StringName", "is_valid_float")
| ("StringName", "is_valid_hex_number")
| ("StringName", "is_valid_html_color")
| ("StringName", "is_valid_ip_address")
| ("StringName", "is_valid_filename")
| ("StringName", "to_int")
| ("StringName", "to_float")
| ("StringName", "hex_to_int")
| ("StringName", "bin_to_int")
| ("StringName", "lpad")
| ("StringName", "rpad")
| ("StringName", "pad_decimals")
| ("StringName", "pad_zeros")
| ("StringName", "trim_prefix")
| ("StringName", "trim_suffix")
| ("StringName", "to_ascii_buffer")
| ("StringName", "to_utf8_buffer")
| ("StringName", "to_utf16_buffer")
| ("StringName", "to_utf32_buffer")
| ("StringName", "hex_decode")
| ("StringName", "to_wchar_buffer")

// NodePath

// (add more builtin types below)

// Vector2i
| ("Vector2i", "clampi")
| ("Vector2i", "distance_squared_to")
| ("Vector2i", "distance_to")
| ("Vector2i", "maxi")
// Vector2i
| ("Vector2i", "clampi")
| ("Vector2i", "distance_squared_to")
| ("Vector2i", "distance_to")
| ("Vector2i", "maxi")
| ("Vector2i", "mini")
| ("Vector2i", "snappedi")

Expand Down
54 changes: 43 additions & 11 deletions godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use std::convert::Infallible;
use std::ffi::c_char;
use std::fmt::Write;
use std::{convert::Infallible, ffi::c_char, fmt, str::FromStr};
use std::str::FromStr;
use std::{fmt, ops};

use godot_ffi as sys;
use sys::types::OpaqueString;
use sys::{ffi_methods, interface_fn, GodotFfi};

use crate::builtin::{inner, NodePath, StringName};
use crate::builtin::{inner, NodePath, StringName, Variant};
use crate::meta;

/// Godot's reference counted string type.
///
Expand Down Expand Up @@ -56,6 +60,10 @@ use crate::builtin::{inner, NodePath, StringName};
/// | General purpose | **`GString`** |
/// | Interned names | [`StringName`][crate::builtin::StringName] |
/// | Scene-node paths | [`NodePath`][crate::builtin::NodePath] |
///
/// # Godot docs
///
/// [`String` (stable)](https://docs.godotengine.org/en/stable/classes/class_string.html)
#[doc(alias = "String")]
// #[repr] is needed on GString itself rather than the opaque field, because PackedStringArray::as_slice() relies on a packed representation.
#[repr(transparent)]
Expand All @@ -64,19 +72,18 @@ pub struct GString {
}

impl GString {
/// Construct a new empty GString.
/// Construct a new empty `GString`.
pub fn new() -> Self {
Self::default()
}

/// Number of characters in the string.
///
/// _Godot equivalent: `length`_
pub fn len(&self) -> usize {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// _Godot equivalent: `length`_
pub fn len(&self) -> usize {
/// _Godot equivalent: `length`_
#[doc(alias = "length")]
pub fn len(&self) -> usize {

self.as_inner().length().try_into().unwrap()
}

pub fn is_empty(&self) -> bool {
self.as_inner().is_empty()
}

/// Returns a 32-bit integer hash value representing the string.
pub fn hash(&self) -> u32 {
self.as_inner()
Expand All @@ -85,7 +92,7 @@ impl GString {
.expect("Godot hashes are uint32_t")
}

/// Gets the UTF-32 character slice from a [`GString`].
/// Gets the UTF-32 character slice from a `GString`.
pub fn chars(&self) -> &[char] {
// SAFETY: Godot 4.1 ensures valid UTF-32, making interpreting as char slice safe.
// See https://github.com/godotengine/godot/pull/74760.
Expand All @@ -94,7 +101,7 @@ impl GString {
let len = interface_fn!(string_to_utf32_chars)(s, std::ptr::null_mut(), 0);
let ptr = interface_fn!(string_operator_index_const)(s, 0);

// Even when len == 0, from_raw_parts requires ptr != 0
// Even when len == 0, from_raw_parts requires ptr != null.
if ptr.is_null() {
return &[];
}
Expand All @@ -103,6 +110,31 @@ impl GString {
}
}

/// Returns a substring of this, as another `GString`.
pub fn substr(&self, range: impl ops::RangeBounds<usize>) -> Self {
let (from, len) = super::to_fromlen_pair(range);

self.as_inner().substr(from, len)
}

/// Format a string using substitutions from an array or dictionary.
///
/// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
pub fn format(&self, array_or_dict: &Variant) -> Self {
self.as_inner().format(array_or_dict, "{_}")
}

/// Format a string using substitutions from an array or dictionary + custom placeholder.
///
/// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
pub fn format_with_placeholder(
&self,
array_or_dict: &Variant,
placeholder: impl meta::AsArg<GString>,
) -> Self {
self.as_inner().format(array_or_dict, placeholder)
}

ffi_methods! {
type sys::GDExtensionStringPtr = *mut Self;

Expand Down Expand Up @@ -151,7 +183,7 @@ impl GString {
self.move_return_ptr(dst, sys::PtrcallType::Standard);
}

crate::meta::declare_arg_method! {
meta::declare_arg_method! {
/// Use as argument for an [`impl AsArg<StringName|NodePath>`][crate::meta::AsArg] parameter.
///
/// This is a convenient way to convert arguments of similar string types.
Expand Down Expand Up @@ -192,7 +224,7 @@ unsafe impl GodotFfi for GString {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}

crate::meta::impl_godot_as_self!(GString);
meta::impl_godot_as_self!(GString);

impl_builtin_traits! {
for GString {
Expand Down
23 changes: 23 additions & 0 deletions godot-core/src/builtin/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod string_name;

use crate::meta::error::ConvertError;
use crate::meta::{FromGodot, GodotConvert, ToGodot};
use std::ops;

pub use gstring::*;
pub use node_path::NodePath;
Expand Down Expand Up @@ -51,3 +52,25 @@ impl FromGodot for String {
Ok(via.to_string())
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

/// Returns a tuple of `(from, len)` from a Rust range.
fn to_fromlen_pair<R>(range: R) -> (i64, i64)
where
R: ops::RangeBounds<usize>,
{
let from = match range.start_bound() {
ops::Bound::Included(&n) => n as i64,
ops::Bound::Excluded(&n) => (n as i64) + 1,
ops::Bound::Unbounded => 0,
};

let len = match range.end_bound() {
ops::Bound::Included(&n) => ((n + 1) as i64) - from,
ops::Bound::Excluded(&n) => (n as i64) - from,
ops::Bound::Unbounded => -1,
};

(from, len)
}
4 changes: 4 additions & 0 deletions godot-core/src/builtin/string/node_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ use super::{GString, StringName};
/// | General purpose | [`GString`][crate::builtin::GString] |
/// | Interned names | [`StringName`][crate::builtin::StringName] |
/// | Scene-node paths | **`NodePath`** |
///
/// # Godot docs
///
/// [`NodePath` (stable)](https://docs.godotengine.org/en/stable/classes/class_nodepath.html)
pub struct NodePath {
opaque: sys::types::OpaqueNodePath,
}
Expand Down
Loading
Loading