///|
fn duration_bound(value : Double) -> Unit raise ParseError {
if value.is_inf() ||
value > 9223372036.854775807 ||
value < -9223372036.854775808 {
raise Invalid("duration out of signed-64 nanosecond range")
}
}
///|
fn require_positive_duration(expr : DurationExpr) -> Unit raise ParseError {
if expr is DurationValue(s) {
let value = number_value(s)
duration_bound(value)
if value <= 0.0 {
raise Invalid("duration must be greater than zero")
}
}
}
///|
fn duration_unary(op : String, value : DurationExpr) -> DurationExpr {
if op == "+" {
return value
}
match value {
DurationValue(s) =>
if op == "+" {
value
} else {
DurationValue(
if s.has_prefix("-") {
s[1:].to_owned()
} else if s.has_prefix("+") {
"-" + s[1:].to_owned()
} else {
"-" + s
},
)
}
_ => DurationUnary(op, value)
}
}
///|
fn duration_expression(
c : Cursor,
min : Int,
depth : Int,
) -> DurationExpr raise ParseError {
if depth > 64 {
raise Invalid("duration expression nesting limit")
}
let token = c.take()
let mut lhs = if token.text == "+" || token.text == "-" {
duration_unary(token.text, duration_expression(c, 6, depth + 1))
} else if token.text == "(" {
if !c.options.duration_expressions {
raise Invalid("duration expressions are not enabled")
}
let e = duration_expression(c, 1, depth + 1)
c.need(")")
e
} else if token.kind == "number" || token.kind == "duration" {
if token.text.to_lower() == "inf" || token.text.to_lower() == "nan" {
raise Invalid("non-finite duration")
}
duration_bound(number_value(token.text))
DurationValue(token.text)
} else {
let name = token.text.to_lower()
if !["step", "range", "min_of", "max_of"].contains(name) || token.quoted {
raise Invalid("duration value or function required")
}
if !c.options.duration_expressions {
raise Invalid("duration expressions are not enabled")
}
c.need("(")
let args = if name == "step" || name == "range" {
[]
} else {
let left = duration_expression(c, 1, depth + 1)
c.need(",")
[left, duration_expression(c, 1, depth + 1)]
}
c.need(")")
DurationCall(name, args)
}
while ["+", "-", "*", "/", "%", "^"].contains(c.peek()) &&
precedence(c.peek()) >= min {
if !c.options.duration_expressions {
raise Invalid("duration expressions are not enabled")
}
let op = c.take().text
let p = precedence(op)
let rhs = duration_expression(
c,
if op == "^" {
p
} else {
p + 1
},
depth + 1,
)
if (op == "/" || op == "%") &&
rhs is DurationValue(value) &&
number_value(value) == 0.0 {
raise Invalid("duration division by zero")
}
lhs = DurationBinary(op, lhs, rhs)
}
lhs
}
///|
/// Unparenthesized offset consumes only one signed literal/function.
fn offset_duration(c : Cursor, depth : Int) -> DurationExpr raise ParseError {
let sign = if c.eat("-") { "-" } else if c.eat("+") { "+" } else { "" }
let result = if c.peek().to_lower() == "nan" {
DurationValue(c.take().text)
} else {
duration_expression(c, 7, depth)
}
let result = if sign.is_empty() {
result
} else {
duration_unary(sign, result)
}
if result is DurationValue(s) {
duration_bound(number_value(s))
}
result
}
///|
/// Literal duration converted through signed-64 nanoseconds, as in Prometheus.
pub fn duration_value(text : String) -> Double raise ParseError {
let value = number_value(text)
duration_bound(value)
let plain = if text.has_prefix("-") || text.has_prefix("+") {
text[1:].to_owned()
} else {
text
}
if !plain.to_lower().has_prefix("0x") &&
plain.iter().any(c => ['m', 's', 'h', 'd', 'w', 'y'].contains(c)) {
return value
}
let ns = if value.is_nan() || value * 1000000000.0 >= 9223372036854775807.0 {
-9223372036854775808L
} else {
(value * 1000000000.0).to_int64()
}
(ns / 1000000000L).to_double() + (ns % 1000000000L).to_double() / 1000000000.0
}
///|
/// Timestamp in milliseconds, with round-half-away-from-zero conversion.
/// Accepted seconds follow upstream bounds; overflowing milliseconds use Go/amd64's minimum-int64 sentinel.
pub fn timestamp_value(text : String) -> Int64 raise ParseError {
let value = number_value(text)
if value.is_nan() ||
value.is_inf() ||
value >= 9223372036854775807.0 ||
value <= -9223372036854775808.0 {
raise Invalid("timestamp out of bounds")
}
let millis = value * 1000.0
if millis >= 9223372036854775807.0 || millis <= -9223372036854775808.0 {
return -9223372036854775808L
}
(if millis < 0.0 {
-@double.floor(-millis + 0.5)
} else {
@double.floor(millis + 0.5)
}).to_int64()
}