plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/Uuid.plum
| 141de54 | 1 | module std |
| ca5fd6f | 2 | import std/Number |
| b7071c9 | 3 | import std/Result |
| b7071c9 | 4 | import std/Str |
| 73b5e55 | 5 | import std/Bool |
| 141de54 | 6 | |
| a271f34 | 7 | # A UUID, stored as its canonical 36-character |
| a271f34 | 8 | # `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` text form. |
| 5f2f962 | 9 | enum Uuid(ToStr) = |
| 5f2f962 | 10 | | Uuid(value: Str) |
| a271f34 | 11 | |
| a271f34 | 12 | fun toStr(self) -> Str = |
| a271f34 | 13 | return self.value |
| a271f34 | 14 | |
| a271f34 | 15 | fun equals(self, other: Uuid) -> Bool = |
| a271f34 | 16 | return self.value == other.value |
| a271f34 | 17 | |
| 2ec05c8 | 18 | fun isNil(self) -> Bool = |
| 2ec05c8 | 19 | return self.value == NIL |
| 2ec05c8 | 20 | |
| 2ec05c8 | 21 | # The version nibble (1-5 for a well-formed RFC 4122 UUID), the first hex |
| 2ec05c8 | 22 | # digit of the third group (`xxxxxxxx-xxxx-Vxxx-...`), at string index 14. |
| 2ec05c8 | 23 | fun version(self) -> Int = |
| 2ec05c8 | 24 | return hexValue(self.value.byteAt(14)) |
| 2ec05c8 | 25 | |
| 2ec05c8 | 26 | # The variant bits, decoded from the high bits of the first hex digit of |
| 2ec05c8 | 27 | # the fourth group (`...-Nxxx-...`), at string index 19. Returns 0 for the |
| 2ec05c8 | 28 | # NCS-backward-compatible variant (`0xxx`), 2 for the RFC 4122 variant |
| 2ec05c8 | 29 | # (`10xx`, what `v4` produces), 6 for Microsoft's (`110x`), 7 for the |
| 2ec05c8 | 30 | # reserved future variant (`111x`). |
| 2ec05c8 | 31 | fun variant(self) -> Int = |
| 2ec05c8 | 32 | n := hexValue(self.value.byteAt(19)) |
| 2ec05c8 | 33 | if n & 0x8 == 0 |
| 2ec05c8 | 34 | return 0 |
| 2ec05c8 | 35 | if n & 0x4 == 0 |
| 2ec05c8 | 36 | return 2 |
| 2ec05c8 | 37 | if n & 0x2 == 0 |
| 2ec05c8 | 38 | return 6 |
| 2ec05c8 | 39 | return 7 |
| 2ec05c8 | 40 | |
| a271f34 | 41 | NIL = "00000000-0000-0000-0000-000000000000" |
| a271f34 | 42 | |
| a271f34 | 43 | fun nil() -> Uuid = |
| a271f34 | 44 | return Uuid(value: NIL) |
| a271f34 | 45 | |
| 2ec05c8 | 46 | # Parses the canonical `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` text form |
| 2ec05c8 | 47 | # (case-insensitive), returning `Err` if `s` isn't exactly that shape. |
| 2ec05c8 | 48 | fun fromStr(s: Str) -> Result[Uuid, Str] = |
| 2ec05c8 | 49 | if s.length() != 36 |
| 2ec05c8 | 50 | return Err("invalid UUID length: '{s}'") |
| 2ec05c8 | 51 | i := 0 |
| 2ec05c8 | 52 | while i < 36 |
| 2ec05c8 | 53 | if i == 8 || i == 13 || i == 18 || i == 23 |
| 2ec05c8 | 54 | # '-' |
| 2ec05c8 | 55 | if s.byteAt(i) != 45 |
| 2ec05c8 | 56 | return Err("invalid UUID: '{s}'") |
| 2ec05c8 | 57 | else |
| 2ec05c8 | 58 | if hexValue(s.byteAt(i)) < 0 |
| 2ec05c8 | 59 | return Err("invalid UUID: '{s}'") |
| 2ec05c8 | 60 | i = i + 1 |
| 2ec05c8 | 61 | return Ok(Uuid(value: s.toLower())) |
| 2ec05c8 | 62 | |
| 2ec05c8 | 63 | fun isValid(s: Str) -> Bool = |
| 2ec05c8 | 64 | match fromStr(s) |
| 2ec05c8 | 65 | Ok(_) => True |
| 2ec05c8 | 66 | Err(_) => False |
| 2ec05c8 | 67 | |
| 2ec05c8 | 68 | # The numeric value (0-15) of an ASCII hex digit byte, or -1 if it isn't one. |
| 2ec05c8 | 69 | fun hexValue(byte: Int) -> Int = |
| 2ec05c8 | 70 | # '0'-'9' |
| 2ec05c8 | 71 | if byte >= 48 && byte <= 57 |
| 2ec05c8 | 72 | return byte - 48 |
| 2ec05c8 | 73 | # 'a'-'f' |
| 2ec05c8 | 74 | if byte >= 97 && byte <= 102 |
| 2ec05c8 | 75 | return byte - 97 + 10 |
| 2ec05c8 | 76 | # 'A'-'F' |
| 2ec05c8 | 77 | if byte >= 65 && byte <= 70 |
| 2ec05c8 | 78 | return byte - 65 + 10 |
| 2ec05c8 | 79 | return -1 |
| 2ec05c8 | 80 | |
| a271f34 | 81 | # A random (version 4, RFC 4122) UUID. |
| a271f34 | 82 | fun v4() -> Uuid = |
| a271f34 | 83 | a := rawRandomInt() |
| a271f34 | 84 | b := rawRandomInt() |
| a271f34 | 85 | time_low := { a >> 32 } & 0xFFFFFFFF |
| a271f34 | 86 | time_mid := { a >> 16 } & 0xFFFF |
| a271f34 | 87 | time_hi_and_version := { a & 0x0FFF } | 0x4000 |
| a271f34 | 88 | clock_seq := { { b >> 48 } & 0x3FFF } | 0x8000 |
| a271f34 | 89 | node := b & 0xFFFFFFFFFFFF |
| a271f34 | 90 | s := hexDigits(time_low, 8) + "-" + hexDigits(time_mid, 4) + "-" |
| a271f34 | 91 | + hexDigits(time_hi_and_version, 4) + "-" + hexDigits(clock_seq, 4) + "-" |
| a271f34 | 92 | + hexDigits(node, 12) |
| a271f34 | 93 | return Uuid(value: s) |
| a271f34 | 94 | |
| a271f34 | 95 | fun hexDigit(n: Int) -> Str = |
| a271f34 | 96 | d := n & 0xF |
| a271f34 | 97 | if d < 10 |
| a271f34 | 98 | return digitChar(d) |
| a271f34 | 99 | match d |
| a271f34 | 100 | 10 => "a" |
| a271f34 | 101 | 11 => "b" |
| a271f34 | 102 | 12 => "c" |
| a271f34 | 103 | 13 => "d" |
| a271f34 | 104 | 14 => "e" |
| a271f34 | 105 | _ => "f" |
| a271f34 | 106 | |
| a271f34 | 107 | # The low `count` hex digits of `n`, most-significant first. |
| a271f34 | 108 | fun hexDigits(n: Int, count: Int) -> Str = |
| a271f34 | 109 | if count <= 0 |
| a271f34 | 110 | return "" |
| a271f34 | 111 | return hexDigits(n >> 4, count - 1) + hexDigit(n) |
| 2ec05c8 | 112 | |
| 2ec05c8 | 113 | test "nil is the all-zero UUID and is recognized as such" |
| a9a0147 | 114 | assert nil().toStr() == "00000000-0000-0000-0000-000000000000" |
| a9a0147 | 115 | assert nil().isNil() == True |
| 2ec05c8 | 116 | |
| 2ec05c8 | 117 | test "v4 produces a valid version-4, variant-2 UUID" |
| 2ec05c8 | 118 | u := v4() |
| a9a0147 | 119 | assert isValid(u.toStr()) == True |
| a9a0147 | 120 | assert u.version() == 4 |
| a9a0147 | 121 | assert u.variant() == 2 |
| a9a0147 | 122 | assert u.isNil() == False |
| 2ec05c8 | 123 | |
| 2ec05c8 | 124 | test "fromStr parses a valid UUID and normalizes case" |
| 2ec05c8 | 125 | match fromStr("550E8400-E29B-41D4-A716-446655440000") |
| 2ec05c8 | 126 | Ok(u) => |
| a9a0147 | 127 | assert u.toStr() == "550e8400-e29b-41d4-a716-446655440000" |
| 2ec05c8 | 128 | Err(_) => |
| a9a0147 | 129 | assert True == False |
| 2ec05c8 | 130 | |
| 2ec05c8 | 131 | test "fromStr rejects malformed input" |
| a9a0147 | 132 | assert isValid("not-a-uuid") == False |
| a9a0147 | 133 | assert isValid("550e8400-e29b-41d4-a716-44665544000") == False |
| a9a0147 | 134 | assert isValid("550e8400xe29b-41d4-a716-446655440000") == False |
| 2ec05c8 | 135 | |
| 2ec05c8 | 136 | test "equals compares by value" |
| 2ec05c8 | 137 | a := Uuid(value: "550e8400-e29b-41d4-a716-446655440000") |
| 2ec05c8 | 138 | b := Uuid(value: "550e8400-e29b-41d4-a716-446655440000") |
| a9a0147 | 139 | assert a.equals(b) == True |
| a9a0147 | 140 | assert a.equals(nil()) == False |