///|
pub(all) enum ValueType {
Boolean
Int32
Int64
Float32
Float64
StringValue
} derive(Eq, @debug.Debug)
///|
pub extend ValueType with Eq::{equal, not_equal}
///|
pub extend ValueType with @debug.Debug::{to_repr}
///|
pub(all) enum Scalar {
BooleanValue(Bool)
IntegerValue(Int32)
LongValue(Int64)
FloatValue(Float)
DoubleValue(Double)
TextValue(String)
} derive(@debug.Debug)
///|
pub extend Scalar with @debug.Debug::{to_repr}
///|
pub fn ValueType::from_name(name : String) -> ValueType raise GraphError {
match name {
"boolean" => Boolean
"int" => Int32
"long" => Int64
"float" => Float32
"double" => Float64
"string" => StringValue
_ => raise InvalidGraph(path="key.attr.type", reason="unknown type \{name}")
}
}
///|
pub fn ValueType::name(self : ValueType) -> String {
match self {
Boolean => "boolean"
Int32 => "int"
Int64 => "long"
Float32 => "float"
Float64 => "double"
StringValue => "string"
}
}
///|
fn decimal_integer(text : String, path : String) -> Unit raise GraphError {
require(text.length() > 0, path, "empty integer")
let first = text.code_unit_at(0)
let start = if first == 43 || first == 45 { 1 } else { 0 }
require(start < text.length(), path, "integer requires digits")
for i = start; i < text.length(); i = i + 1 {
let c = text.code_unit_at(i)
require(c >= 48 && c <= 57, path, "invalid decimal integer")
}
}
///|
fn floating_lexical(text : String, path : String) -> Unit raise GraphError {
if text == "INF" || text == "-INF" || text == "NaN" {
return
}
require(text.length() > 0, path, "empty floating-point value")
let mut position = 0
if text.code_unit_at(0) == 43 || text.code_unit_at(0) == 45 {
position = 1
}
let mut digits = 0
while position < text.length() &&
text.code_unit_at(position) >= 48 &&
text.code_unit_at(position) <= 57 {
digits = digits + 1
position = position + 1
}
if position < text.length() && text.code_unit_at(position) == 46 {
position = position + 1
while position < text.length() &&
text.code_unit_at(position) >= 48 &&
text.code_unit_at(position) <= 57 {
digits = digits + 1
position = position + 1
}
}
require(digits > 0, path, "floating-point value requires digits")
if position < text.length() &&
(text.code_unit_at(position) == 69 || text.code_unit_at(position) == 101) {
position = position + 1
if position < text.length() &&
(text.code_unit_at(position) == 43 || text.code_unit_at(position) == 45) {
position = position + 1
}
let exponent_start = position
while position < text.length() &&
text.code_unit_at(position) >= 48 &&
text.code_unit_at(position) <= 57 {
position = position + 1
}
require(position > exponent_start, path, "exponent requires digits")
}
require(
position == text.length(),
path,
"invalid floating-point lexical value",
)
}
///|
fn read_double(text : String, path : String) -> Double raise GraphError {
floating_lexical(text, path)
match text {
"INF" => 1.0 / 0.0
"-INF" => -1.0 / 0.0
"NaN" => 0.0 / 0.0
_ => {
let value = @string.parse_double(text) catch {
_ => raise InvalidGraph(path~, reason="floating-point overflow")
}
require(!value.is_inf(), path, "finite floating-point value overflow")
value
}
}
}
///|
pub fn Scalar::parse(
kind : ValueType,
authored : String,
) -> Scalar raise GraphError {
// Public scalar parsing can be called without the XML reader's Unicode gate.
// Validate before trimming or iterating an unchecked UTF-16 string.
let _ = utf8_size(authored)
if kind == StringValue {
return TextValue(authored)
}
let text = authored.trim(chars=" \t\r\n").to_owned()
let path = "data.\{kind.name()}"
match kind {
Boolean =>
match text {
"true" | "1" => BooleanValue(true)
"false" | "0" => BooleanValue(false)
_ => raise InvalidGraph(path~, reason="expected true, false, 1 or 0")
}
Int32 => {
decimal_integer(text, path)
let value = @string.parse_int(text) catch {
_ => raise InvalidGraph(path~, reason="32-bit integer overflow")
}
IntegerValue(value)
}
Int64 => {
decimal_integer(text, path)
let value = @string.parse_int64(text) catch {
_ => raise InvalidGraph(path~, reason="64-bit integer overflow")
}
LongValue(value)
}
Float32 => {
let value = read_double(text, path)
let single = Float::from_double(value)
require(
value.is_inf() || !single.to_double().is_inf(),
path,
"32-bit floating-point overflow",
)
FloatValue(single)
}
Float64 => DoubleValue(read_double(text, path))
StringValue => TextValue(authored)
}
}
///|
pub fn Scalar::value_type(self : Scalar) -> ValueType {
match self {
BooleanValue(_) => Boolean
IntegerValue(_) => Int32
LongValue(_) => Int64
FloatValue(_) => Float32
DoubleValue(_) => Float64
TextValue(_) => StringValue
}
}
///|
fn double_text(value : Double) -> String {
if value.is_nan() {
"NaN"
} else if value.is_inf() {
if value < 0.0 {
"-INF"
} else {
"INF"
}
} else {
value.to_string()
}
}
///|
pub fn Scalar::lexical(self : Scalar) -> String {
match self {
BooleanValue(value) => if value { "true" } else { "false" }
IntegerValue(value) => value.to_string()
LongValue(value) => value.to_string()
FloatValue(value) => double_text(value.to_double())
DoubleValue(value) => double_text(value)
TextValue(value) => value
}
}
///|
pub fn Scalar::equivalent(self : Scalar, other : Scalar) -> Bool {
if self.value_type() != other.value_type() {
return false
}
match (self, other) {
(BooleanValue(a), BooleanValue(b)) => a == b
(IntegerValue(a), IntegerValue(b)) => a == b
(LongValue(a), LongValue(b)) => a == b
(FloatValue(a), FloatValue(b)) =>
a == b || (a.to_double().is_nan() && b.to_double().is_nan())
(DoubleValue(a), DoubleValue(b)) => a == b || (a.is_nan() && b.is_nan())
(TextValue(a), TextValue(b)) => a == b
_ => false
}
}