util/common-util.spice
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Copyright (c) 2021-2026 ChilliBits. All rights reserved. | ||
| 2 | |||
| 3 | // Std imports | ||
| 4 | import "std/text/stringstream"; | ||
| 5 | import "std/bindings/llvm/llvm" as llvm; | ||
| 6 | import "std/text/json/json-value"; | ||
| 7 | import "std/time/time"; | ||
| 8 | import "std/type/type-conversion"; | ||
| 9 | |||
| 10 | // External functions | ||
| 11 | ext f<int> memcmp(string, string, unsigned long); | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Split the given haystack by the needle and return the last fragment | ||
| 15 | * | ||
| 16 | * @param haystack Input string | ||
| 17 | * @param needle String to search | ||
| 18 | * @return Last fragment | ||
| 19 | */ | ||
| 20 | 109793 | public f<String> getLastFragment(const String &haystack, const string needle) { | |
| 21 | 109793 | const unsigned long index = haystack.rfind(needle); | |
| 22 | 109793 | return index != -1l ? haystack.getSubstring(index + getRawLength(needle)) : haystack; | |
| 23 | } | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Generate the version info string for the Spice driver | ||
| 27 | * | ||
| 28 | * @return Version info string | ||
| 29 | */ | ||
| 30 | 1099 | public f<String> buildVersionInfo() { | |
| 31 | 1099 | const string version = __get_build_var<string>("SPICE_VERSION", "dev"); | |
| 32 | 1099 | const string targetOs = __get_build_var<string>("SPICE_TARGET_OS", "dev"); | |
| 33 | 1099 | const string targetArch = __get_build_var<string>("SPICE_TARGET_ARCH", "dev"); | |
| 34 | 1099 | const string gitHash = __get_build_var<string>("SPICE_GIT_HASH", "dev"); | |
| 35 | 1099 | const string builtBy = __get_build_var<string>("SPICE_BUILT_BY", "dev"); | |
| 36 | |||
| 37 | 1099 | StringStream versionString; | |
| 38 | 1099 | versionString << "Spice version: " << version << " " << targetOs << "/" << targetArch << endl(); | |
| 39 | 1099 | versionString << "Git hash: " << gitHash << endl(); | |
| 40 | 1099 | versionString << "LLVM version: " << llvm::getVersionString() << endl(); | |
| 41 | 1099 | versionString << "built by: " << builtBy << endl() << endl(); | |
| 42 | 1099 | versionString << "(c) Marc Auberer 2021-" << toString(getCurrentYear()); | |
| 43 | 2198 | return versionString.str(); | |
| 44 | 1099 | } | |
| 45 | |||
| 46 | /** | ||
| 47 | * Checks if the first string sorts lexicographically before the second one. This compares byte-wise. | ||
| 48 | * | ||
| 49 | * @param lhs First string | ||
| 50 | * @param rhs Second string | ||
| 51 | * @return Sorts before or not | ||
| 52 | */ | ||
| 53 | 6917043 | public f<bool> isStringLess(const String& lhs, const String& rhs) { | |
| 54 | 6917043 | const unsigned long lhsLength = lhs.getLength(); | |
| 55 | 6917043 | const unsigned long rhsLength = rhs.getLength(); | |
| 56 | 6917043 | const unsigned long commonLength = lhsLength < rhsLength ? lhsLength : rhsLength; | |
| 57 | 6917043 | if commonLength > 0l { | |
| 58 | 6917042 | const int comparison = memcmp(lhs.getRaw(), rhs.getRaw(), commonLength); | |
| 59 | 6917042 | if comparison != 0 { | |
| 60 | 6917040 | return comparison < 0; | |
| 61 | } | ||
| 62 | 2 | } | |
| 63 | 3 | return lhsLength < rhsLength; | |
| 64 | } | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Add a field to a JSON object, keeping the fields sorted by key. | ||
| 68 | * The object takes ownership of the heap-allocated field value. | ||
| 69 | * | ||
| 70 | * @param object JSON object | ||
| 71 | * @param key Field key | ||
| 72 | * @param value Field value | ||
| 73 | */ | ||
| 74 | 3738083 | public p setJsonField(JsonValue* object, const String& key, JsonValue* value) { | |
| 75 | 3738083 | const unsigned long size = object.getObjectSize(); | |
| 76 | 3738083 | unsigned long idx = 0l; | |
| 77 | 7946469 | while idx < size && isStringLess(object.getObjectKey(idx), key) { | |
| 78 | 4208386 | idx++; | |
| 79 | 4208386 | } | |
| 80 | 3738083 | assert idx == size || object.getObjectKey(idx) != key; // Overwriting fields is not supported | |
| 81 | 3738083 | object.insertObjectField(idx, key, value); | |
| 82 | 3738083 | } | |
| 83 | |||
| 84 | // ─── tests ──────────────────────────────────────────────────────────────────── | ||
| 85 | |||
| 86 | 1 | #[test] | |
| 87 | f<bool> testGetLastFragment() { | ||
| 88 | 1 | assert getLastFragment(String("This is a test"), " ") == "test"; | |
| 89 | 1 | assert getLastFragment(String("This_is_a_test"), "_") == "test"; | |
| 90 | 1 | assert getLastFragment(String("This.is.a."), ".") == ""; | |
| 91 | 1 | assert getLastFragment(String("This is a test"), "This is a test ") == "This is a test"; | |
| 92 | 1 | return true; | |
| 93 | 1 | } | |
| 94 | |||
| 95 | 1 | #[test] | |
| 96 | f<bool> testIsStringLess() { | ||
| 97 | 1 | assert isStringLess(String("a"), String("b")); | |
| 98 | 1 | assert !isStringLess(String("b"), String("a")); | |
| 99 | 1 | assert !isStringLess(String("a"), String("a")); | |
| 100 | 1 | assert isStringLess(String("ab"), String("abc")); // A prefix sorts first | |
| 101 | 1 | assert isStringLess(String(""), String("a")); | |
| 102 | 1 | assert isStringLess(String("Z"), String("a")); // Byte-wise, like std::string | |
| 103 | 1 | return true; | |
| 104 | 1 | } | |
| 105 |