godot/modules/mono/utils/naming_utils.cpp

/**************************************************************************/
/*  naming_utils.cpp                                                      */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

#include "naming_utils.h"

#include "core/string/ucaps.h"
#include "core/templates/hash_map.h"

HashMap<String, String> _create_hashmap_from_vector(Vector<Pair<String, String>> vector) {
	HashMap<String, String> hashmap = HashMap<String, String>(vector.size());
	for (const Pair<String, String> &pair : vector) {
		hashmap.insert(pair.first, pair.second);
	}
	return hashmap;
}

// Hardcoded collection of PascalCase name conversions.
const HashMap<String, String> pascal_case_name_overrides = _create_hashmap_from_vector({
		{ "BitMap", "Bitmap" },
		{ "JSONRPC", "JsonRpc" },
		{ "Object", "GodotObject" },
		{ "OpenXRIPBinding", "OpenXRIPBinding" },
		{ "SkeletonModification2DCCDIK", "SkeletonModification2DCcdik" },
		{ "SkeletonModification2DFABRIK", "SkeletonModification2DFabrik" },
		{ "SkeletonModification3DCCDIK", "SkeletonModification3DCcdik" },
		{ "SkeletonModification3DFABRIK", "SkeletonModification3DFabrik" },
		{ "System", "System_" },
		{ "Thread", "GodotThread" },
});

// Hardcoded collection of PascalCase part conversions.
const HashMap<String, String> pascal_case_part_overrides = _create_hashmap_from_vector({
		{ "AA", "AA" }, // Anti Aliasing
		{ "AO", "AO" }, // Ambient Occlusion
		{ "FILENAME", "FileName" },
		{ "FADEIN", "FadeIn" },
		{ "FADEOUT", "FadeOut" },
		{ "FX", "FX" },
		{ "GI", "GI" }, // Global Illumination
		{ "GZIP", "GZip" },
		{ "HBOX", "HBox" }, // Horizontal Box
		{ "ID", "Id" },
		{ "IO", "IO" }, // Input/Output
		{ "IP", "IP" }, // Internet Protocol
		{ "IV", "IV" }, // Initialization Vector
		{ "MACOS", "MacOS" },
		{ "NODEPATH", "NodePath" },
		{ "SPIRV", "SpirV" },
		{ "STDIN", "StdIn" },
		{ "STDOUT", "StdOut" },
		{ "USERNAME", "UserName" },
		{ "UV", "UV" },
		{ "UV2", "UV2" },
		{ "VBOX", "VBox" }, // Vertical Box
		{ "WHITESPACE", "WhiteSpace" },
		{ "WM", "WM" },
		{ "XR", "XR" },
		{ "XRAPI", "XRApi" },
});

String _get_pascal_case_part_override(String p_part, bool p_input_is_upper = true) {
	if (!p_input_is_upper) {
		for (int i = 0; i < p_part.length(); i++) {
			p_part[i] = _find_upper(p_part[i]);
		}
	}

	if (pascal_case_part_overrides.has(p_part)) {
		return pascal_case_part_overrides.get(p_part);
	}

	return String();
}

Vector<String> _split_pascal_case(const String &p_identifier) {
	Vector<String> parts;
	int current_part_start = 0;
	bool prev_was_upper = is_ascii_upper_case(p_identifier[0]);
	for (int i = 1; i < p_identifier.length(); i++) {
		if (prev_was_upper) {
			if (is_digit(p_identifier[i]) || is_ascii_lower_case(p_identifier[i])) {
				if (!is_digit(p_identifier[i])) {
					// These conditions only apply when the separator is not a digit.
					if (i - current_part_start == 1) {
						// Upper character was only the beginning of a word.
						prev_was_upper = false;
						continue;
					}
					if (i != p_identifier.length()) {
						// If this is not the last character, the last uppercase
						// character is the start of the next word.
						i--;
					}
				}
				if (i - current_part_start > 0) {
					parts.append(p_identifier.substr(current_part_start, i - current_part_start));
					current_part_start = i;
					prev_was_upper = false;
				}
			}
		} else {
			if (is_digit(p_identifier[i]) || is_ascii_upper_case(p_identifier[i])) {
				parts.append(p_identifier.substr(current_part_start, i - current_part_start));
				current_part_start = i;
				prev_was_upper = true;
			}
		}
	}

	// Add the rest of the identifier as the last part.
	if (current_part_start != p_identifier.length()) {
		parts.append(p_identifier.substr(current_part_start));
	}

	return parts;
}

String pascal_to_pascal_case(const String &p_identifier) {
	if (p_identifier.length() == 0) {
		return p_identifier;
	}

	if (p_identifier.length() <= 2) {
		return p_identifier.to_upper();
	}

	if (pascal_case_name_overrides.has(p_identifier)) {
		// Use hardcoded value for the identifier.
		return pascal_case_name_overrides.get(p_identifier);
	}

	Vector<String> parts = _split_pascal_case(p_identifier);

	String ret;

	for (String &part : parts) {
		String part_override = _get_pascal_case_part_override(part);
		if (!part_override.is_empty()) {
			// Use hardcoded value for part.
			ret += part_override;
			continue;
		}

		if (part.length() <= 2 && part.to_upper() == part) {
			// Acronym of length 1 or 2.
			for (int j = 0; j < part.length(); j++) {
				part[j] = _find_upper(part[j]);
			}
			ret += part;
			continue;
		}

		part[0] = _find_upper(part[0]);
		for (int i = 1; i < part.length(); i++) {
			if (is_digit(part[i - 1])) {
				// Use uppercase after digits.
				part[i] = _find_upper(part[i]);
				continue;
			}

			part[i] = _find_lower(part[i]);
		}
		ret += part;
	}

	return ret;
}

String snake_to_pascal_case(const String &p_identifier, bool p_input_is_upper) {
	String ret;
	Vector<String> parts = p_identifier.split("_", true);

	for (int i = 0; i < parts.size(); i++) {
		String part = parts[i];

		String part_override = _get_pascal_case_part_override(part, p_input_is_upper);
		if (!part_override.is_empty()) {
			// Use hardcoded value for part.
			ret += part_override;
			continue;
		}

		if (!part.is_empty()) {
			part[0] = _find_upper(part[0]);
			for (int j = 1; j < part.length(); j++) {
				if (is_digit(part[j - 1])) {
					// Use uppercase after digits.
					part[j] = _find_upper(part[j]);
					continue;
				}

				if (p_input_is_upper) {
					part[j] = _find_lower(part[j]);
				}
			}
			ret += part;
		} else {
			if (i == 0 || i == (parts.size() - 1)) {
				// Preserve underscores at the beginning and end
				ret += "_";
			} else {
				// Preserve contiguous underscores
				if (parts[i - 1].length()) {
					ret += "__";
				} else {
					ret += "_";
				}
			}
		}
	}

	return ret;
}

String snake_to_camel_case(const String &p_identifier, bool p_input_is_upper) {
	String ret;
	Vector<String> parts = p_identifier.split("_", true);

	for (int i = 0; i < parts.size(); i++) {
		String part = parts[i];

		String part_override = _get_pascal_case_part_override(part, p_input_is_upper);
		if (!part_override.is_empty()) {
			// Use hardcoded value for part.
			if (i == 0) {
				part_override[0] = _find_lower(part_override[0]);
			}
			ret += part_override;
			continue;
		}

		if (!part.is_empty()) {
			if (i == 0) {
				part[0] = _find_lower(part[0]);
			} else {
				part[0] = _find_upper(part[0]);
			}
			for (int j = 1; j < part.length(); j++) {
				if (is_digit(part[j - 1])) {
					// Use uppercase after digits.
					part[j] = _find_upper(part[j]);
					continue;
				}

				if (p_input_is_upper) {
					part[j] = _find_lower(part[j]);
				}
			}
			ret += part;
		} else {
			if (i == 0 || i == (parts.size() - 1)) {
				// Preserve underscores at the beginning and end
				ret += "_";
			} else {
				// Preserve contiguous underscores
				if (parts[i - 1].length()) {
					ret += "__";
				} else {
					ret += "_";
				}
			}
		}
	}

	return ret;
}