// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
#valtype
struct Uuid {
hi : UInt64
lo : UInt64
} derive(Eq, Compare, Hash)
///|
pub impl ToJson for Uuid with fn to_json(self : Uuid) -> Json {
self.to_string().to_json()
}
///|
pub impl @json.FromJson for Uuid with fn from_json(json, json_path) -> Uuid raise {
guard json is String(string) else {
raise JsonDecodeError((json_path, "Uuid::from_json: expected string"))
}
parse(string) catch {
error =>
raise JsonDecodeError((json_path, "Uuid::from_json: \{to_repr(error)}"))
}
}
///|
struct Generator(&Source)
///|
pub(open) trait Source {
fn uint64(self : Self) -> UInt64
}
///|
pub fn Generator::v4(self : Generator) -> Uuid {
let hi = self.0.uint64()
let lo = self.0.uint64()
let hi = (hi & 0xFFFF_FFFF_FFFF_0FFF_UL) | 0x0000_0000_0000_4000_UL
let lo = (lo & 0x0FFF_FFFF_FFFF_FFFF_UL) | 0x8000_0000_0000_0000_UL
{ hi, lo }
}
///|
pub impl Source for @random.Rand with fn uint64(self : @random.Rand) -> UInt64 {
self.uint64()
}
///|
pub fn[Source : Source] generator(source : Source) -> Generator {
Generator(source)
}
///|
pub let nil : Uuid = { hi: 0UL, lo: 0UL }
///|
pub let max : Uuid = {
hi: 0xFFFF_FFFF_FFFF_FFFF_UL,
lo: 0xFFFF_FFFF_FFFF_FFFF_UL,
}
///|
#as_free_fn
pub fn Uuid::of_bytes(bytes : Bytes) -> Uuid {
let hi = (bytes[0].to_uint64() << 56) |
(bytes[1].to_uint64() << 48) |
(bytes[2].to_uint64() << 40) |
(bytes[3].to_uint64() << 32) |
(bytes[4].to_uint64() << 24) |
(bytes[5].to_uint64() << 16) |
(bytes[6].to_uint64() << 8) |
bytes[7].to_uint64()
let lo = (bytes[8].to_uint64() << 56) |
(bytes[9].to_uint64() << 48) |
(bytes[10].to_uint64() << 40) |
(bytes[11].to_uint64() << 32) |
(bytes[12].to_uint64() << 24) |
(bytes[13].to_uint64() << 16) |
(bytes[14].to_uint64() << 8) |
bytes[15].to_uint64()
{ hi, lo }
}
///|
pub fn Uuid::to_bytes(self : Uuid) -> Bytes {
let b : FixedArray[Byte] = FixedArray::make(16, 0)
b[0] = (self.hi >> 56).to_byte()
b[1] = (self.hi >> 48).to_byte()
b[2] = (self.hi >> 40).to_byte()
b[3] = (self.hi >> 32).to_byte()
b[4] = (self.hi >> 24).to_byte()
b[5] = (self.hi >> 16).to_byte()
b[6] = (self.hi >> 8).to_byte()
b[7] = self.hi.to_byte()
b[8] = (self.lo >> 56).to_byte()
b[9] = (self.lo >> 48).to_byte()
b[10] = (self.lo >> 40).to_byte()
b[11] = (self.lo >> 32).to_byte()
b[12] = (self.lo >> 24).to_byte()
b[13] = (self.lo >> 16).to_byte()
b[14] = (self.lo >> 8).to_byte()
b[15] = self.lo.to_byte()
b.unsafe_reinterpret_as_bytes()
}
///|
suberror ParseError {
UnexpectedEnd
InvalidOctet(StringView)
MissingDash(StringView)
ExtraContent(StringView)
} derive(Debug, ToJson)
///|
pub fn parse(hex : StringView) -> Uuid raise ParseError {
let mut view = hex.view()
fn parse_hex_octet(count : Int, value : UInt64) -> UInt64 raise ParseError {
let mut value = value
for _ in 0..<(count * 2) {
match view {
['0'..='9' as c, .. rest] => {
value = (value << 4) | (c.to_uint() - '0'.to_uint()).to_uint64()
view = rest
}
['a'..='f' as c, .. rest] => {
value = (value << 4) | (c.to_uint() - 'a'.to_uint() + 10).to_uint64()
view = rest
}
['A'..='F' as c, .. rest] => {
value = (value << 4) | (c.to_uint() - 'A'.to_uint() + 10).to_uint64()
view = rest
}
[_, ..] => raise InvalidOctet(view)
[] => raise UnexpectedEnd
}
}
value
}
fn parse_dash() -> Unit raise ParseError {
match view {
['-', .. rest] => view = rest
_ => raise MissingDash(view)
}
}
let hi = parse_hex_octet(4, 0UL)
parse_dash()
let hi = parse_hex_octet(2, hi)
parse_dash()
let hi = parse_hex_octet(2, hi)
parse_dash()
let lo = parse_hex_octet(2, 0UL)
parse_dash()
let lo = parse_hex_octet(6, lo)
guard view.is_empty() else { raise ExtraContent(view) }
{ hi, lo }
}
///|
// let hex_table : ReadOnlyArray[Char] = [
// '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
// ]
let hex_table : Bytes = b"0123456789abcdef"
///|
pub fn Uuid::to_string(self : Uuid) -> String {
let builder = StringBuilder::new(size_hint=36)
let mut hi = self.hi
for _ in 0..<8 {
builder.write_char(hex_table[(hi >> 60).to_int()].to_char())
hi = hi << 4
}
builder.write_char('-')
for _ in 0..<4 {
builder.write_char(hex_table[(hi >> 60).to_int()].to_char())
hi = hi << 4
}
builder.write_char('-')
for _ in 0..<4 {
builder.write_char(hex_table[(hi >> 60).to_int()].to_char())
hi = hi << 4
}
builder.write_char('-')
let mut lo = self.lo
for _ in 0..<4 {
builder.write_char(hex_table[(lo >> 60).to_int()].to_char())
lo = lo << 4
}
builder.write_char('-')
for _ in 0..<12 {
builder.write_char(hex_table[(lo >> 60).to_int()].to_char())
lo = lo << 4
}
builder.to_string()
}
///|
test {
inspect(b'a'.to_char(), content="a")
}
///|
pub impl Show for Uuid with fn output(self : Uuid, logger : &Logger) -> Unit {
logger.write_string(self.to_string())
}
///|
pub enum Variant {
ReservedNCS
RFC9562(Version)
ReservedMicrosoft
ReservedFuture
} derive(ToJson)
///|
pub(all) enum Version {
V0 = 0
V1 = 1
V2 = 2
V3 = 3
V4 = 4
V5 = 5
V6 = 6
V7 = 7
V8 = 8
V9 = 9
V10 = 10
V11 = 11
V12 = 12
V13 = 13
V14 = 14
V15 = 15
} derive(ToJson)
///|
fn Version::from_uint64_unchecked(value : UInt64) -> Version = "%identity"
///|
pub fn Uuid::variant(self : Uuid) -> Variant {
if (self.lo & (0x8000UL << 48)) == 0UL {
ReservedNCS
} else if (self.lo & (0x4000UL << 48)) == 0UL {
RFC9562(Version::from_uint64_unchecked((self.hi >> 12) & 0xFUL))
} else if (self.lo & (0x2000UL << 48)) == 0UL {
ReservedMicrosoft
} else {
ReservedFuture
}
}
///|
pub fn Uuid::version(self : Uuid) -> Version? {
match self.variant() {
RFC9562(ver) => Some(ver)
_ => None
}
}