///|
/// FEEL interval. Missing endpoints are unbounded.
pub(all) struct Range {
comparison : String?
start : Value?
start_included : Bool
end : Value?
end_included : Bool
}
///|
/// Construct a range with neither a lower nor an upper bound.
pub fn Range::unbounded() -> Range {
{
comparison: None,
start: None,
start_included: false,
end: None,
end_included: false,
}
}
///|
/// Test point membership with open and closed endpoint semantics.
pub fn Range::contains(range : Range, value : Value) -> Bool raise EngineError {
if value is Null {
return false
}
if range.comparison is Some(op) && range.start is Some(bound) {
return match op {
"=" => value.equals(bound)
"!=" => !value.equals(bound)
_ => raise fail("E_RANGE", "unsupported range comparison: " + op)
}
}
if range.start is Some(low) {
let c = compare_values(value, low)
if c < 0 || (c == 0 && !range.start_included) {
return false
}
}
if range.end is Some(high) {
let c = compare_values(value, high)
if c > 0 || (c == 0 && !range.end_included) {
return false
}
}
true
}
///|
/// Test whether two ranges share at least one point.
pub fn Range::overlaps(a : Range, b : Range) -> Bool raise EngineError {
let starts_before_end_of_b = match a.start {
None => true
Some(x) =>
match b.end {
None => true
Some(y) => {
let c = compare_values(x, y)
c < 0 || (c == 0 && a.start_included && b.end_included)
}
}
}
let starts_before_end_of_a = match b.start {
None => true
Some(x) =>
match a.end {
None => true
Some(y) => {
let c = compare_values(x, y)
c < 0 || (c == 0 && b.start_included && a.end_included)
}
}
}
starts_before_end_of_b && starts_before_end_of_a
}
///|
/// Format range endpoints and inclusion brackets.
pub fn Range::text(range : Range) -> String {
if range.comparison is Some(op) && range.start is Some(bound) {
return "(" + op + bound.text() + ")"
}
let low = match range.start {
Some(v) => v.text()
None => ""
}
let high = match range.end {
Some(v) => v.text()
None => ""
}
let left = if range.start_included { "[" } else { "(" }
let right = if range.end_included { "]" } else { ")" }
left + low + ".." + high + right
}
///|
/// Encode range bounds and inclusion flags as JSON.
pub fn Range::json(range : Range) -> Json raise EngineError {
let start = match range.start {
Some(v) => v.json()
None => Json::null()
}
let end = match range.end {
Some(v) => v.json()
None => Json::null()
}
let fields = Map([
("start", start),
("startIncluded", Json(range.start_included)),
("end", end),
("endIncluded", Json(range.end_included)),
])
if range.comparison is Some(op) {
fields["comparison"] = Json(op)
}
Json::object(fields)
}