///|
/// Typed values retain full uint64 and int64 precision on every backend.
/// Integers are strings in JSON; binary values use lowercase hexadecimal.
pub(all) enum Value {
Signed(Int64)
Unsigned(UInt64)
Floating(Double)
Text(String)
Binary(Bytes)
Record(Map[String, Value])
Sequence(Array[Value])
Absent
} derive(Debug)
///|
pub fn Value::field(self : Value, name : String) -> Value raise SbeError {
match self {
Record(fields) =>
match fields.get(name) {
Some(value) => value
None => raise InvalidValue(name, "required field is missing")
}
_ => raise InvalidValue(name, "expected a record")
}
}
///|
pub fn Value::as_record(self : Value) -> Map[String, Value] raise SbeError {
match self {
Record(fields) => fields
_ => raise InvalidValue("value", "expected a record")
}
}
///|
pub fn Value::as_sequence(self : Value) -> Array[Value] raise SbeError {
match self {
Sequence(values) => values
_ => raise InvalidValue("value", "expected an array")
}
}
///|
pub fn Value::as_unsigned(self : Value) -> UInt64 raise SbeError {
match self {
Unsigned(value) => value
Signed(value) if value >= 0L => value.reinterpret_as_uint64()
_ => raise InvalidValue("value", "expected an unsigned integer")
}
}
///|
pub fn Value::as_signed(self : Value) -> Int64 raise SbeError {
match self {
Signed(value) => value
Unsigned(value) if value <= 0x7fffffffffffffffUL =>
value.reinterpret_as_int64()
_ => raise InvalidValue("value", "expected a signed integer")
}
}
///|
pub fn Value::as_float(self : Value) -> Double raise SbeError {
match self {
Floating(value) => value
_ => raise InvalidValue("value", "expected a floating point number")
}
}
///|
pub fn Value::as_text(self : Value) -> String raise SbeError {
match self {
Text(value) => value
_ => raise InvalidValue("value", "expected text")
}
}
///|
pub fn Value::as_binary(self : Value) -> Bytes raise SbeError {
match self {
Binary(value) => value
_ => raise InvalidValue("value", "expected binary data")
}
}
///|
pub fn to_hex(bytes : Bytes) -> String {
let result = StringBuilder()
let digits = "0123456789abcdef".iter().to_array()
for byte in bytes {
result.write_char(digits[byte.to_int() >> 4])
result.write_char(digits[byte.to_int() & 15])
}
result.to_string()
}
///|
pub fn from_hex(text : String) -> Bytes raise SbeError {
if text.length() > 33554432 || text.length() % 2 != 0 {
raise InvalidValue(
"hex", "expected an even number of hexadecimal digits within 16 MiB",
)
}
let bytes : Array[Byte] = []
let mut first = -1
for char in text {
let value = match char {
'0'..<='9' => char.to_int() - '0'.to_int()
'a'..<='f' => char.to_int() - 'a'.to_int() + 10
'A'..<='F' => char.to_int() - 'A'.to_int() + 10
_ => raise InvalidValue("hex", "invalid hexadecimal digit")
}
if first < 0 {
first = value
} else {
bytes.push((first * 16 + value).to_byte())
first = -1
}
}
Bytes::from_array(bytes)
}
///|
pub fn Value::to_json(self : Value) -> Json {
match self {
Signed(value) => Json::string(value.to_string())
Unsigned(value) => Json::string(value.to_string())
Floating(value) =>
if value.is_nan() {
Json::string("NaN")
} else if value.is_inf() {
Json::string(if value > 0.0 { "Infinity" } else { "-Infinity" })
} else {
Json::number(value)
}
Text(value) => Json::string(value)
Binary(value) => Json::string(to_hex(value))
Record(fields) => {
let result : Map[String, Json] = Map([])
for name, value in fields {
result[name] = value.to_json()
}
Json::object(result)
}
Sequence(values) => Json::array(values.map(Value::to_json))
Absent => Json::null()
}
}
///|
fn scalar_from_literal(
primitive : Primitive,
text : String,
) -> Value raise SbeError {
if primitive == Char {
if @utf8.encode(text).length() != 1 {
raise InvalidValue("char", "expected one ASCII byte")
}
return Text(text)
}
if primitive.is_signed() {
let value = @string.parse_int64(text) catch {
_ => raise InvalidValue("integer", "invalid signed literal: \{text}")
}
Signed(value)
} else if primitive.is_float() {
if text == "NaN" {
return Floating(0.0 / 0.0)
}
let value = @string.parse_double(text) catch {
_ => raise InvalidValue("float", "invalid floating literal: \{text}")
}
Floating(value)
} else {
let value = @string.parse_uint64(text) catch {
_ => raise InvalidValue("integer", "invalid unsigned literal: \{text}")
}
Unsigned(value)
}
}
///|
fn default_null(primitive : Primitive) -> Value {
match primitive {
Char => Text("\u{0000}")
Int8 => Signed(-128L)
UInt8 => Unsigned(255UL)
Int16 => Signed(-32768L)
UInt16 => Unsigned(65535UL)
Int32 => Signed(-2147483648L)
UInt32 => Unsigned(0xffffffffUL)
Int64 => Signed(-9223372036854775808L)
UInt64 => Unsigned(0xffffffffffffffffUL)
Float32 | Float64 => Floating(0.0 / 0.0)
}
}
///|
fn same_scalar(left : Value, right : Value) -> Bool {
match (left, right) {
(Signed(a), Signed(b)) => a == b
(Unsigned(a), Unsigned(b)) => a == b
(Floating(a), Floating(b)) => a == b || (a.is_nan() && b.is_nan())
(Text(a), Text(b)) => a == b
(Absent, Absent) => true
_ => false
}
}