Packages
caffeine_lang
4.8.3
6.3.1
6.3.0
6.2.2
6.2.1
6.2.0
6.1.2
6.1.1
6.1.0
6.0.0
5.6.0
5.5.0
5.4.4
5.4.3
5.4.2
5.4.1
5.4.0
5.3.0
5.2.0
5.1.1
5.1.0
5.0.12
5.0.11
5.0.10
5.0.8
5.0.7
5.0.6
5.0.5
5.0.4
5.0.1
5.0.0
4.10.0
4.9.0
4.8.3
4.8.2
4.8.1
4.8.0
4.7.9
4.7.8
4.7.7
4.7.6
4.7.5
4.6.7
4.6.6
4.6.5
4.6.4
4.6.3
4.6.2
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.7
4.3.6
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.0.2
1.0.1
0.1.0
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.2
0.0.1
A compiler for generating reliability artifacts from service expectation definitions.
Current section
Files
Jump to
Current section
Files
src/caffeine_query_language/printer.gleam
import caffeine_query_language/ast.{
type Comparator, type Exp, type Operator, type Primary,
}
import gleam/float
import gleam/int
import gleam/option.{type Option}
import gleam/string
/// Converts an expression AST node to its string representation.
@internal
pub fn exp_to_string(exp: Exp(a)) -> String {
case exp {
ast.Primary(primary:) -> primary_to_string(primary, option.None)
ast.TimeSliceExpr(spec) ->
"time_slice("
<> spec.query
<> " "
<> comparator_to_string(spec.comparator)
<> " "
<> float_to_string(spec.threshold)
<> " per "
<> float_to_string(spec.interval_seconds)
<> "s)"
ast.OperatorExpr(numerator:, denominator:, operator:) -> {
case operator, is_path_expression(exp) {
ast.Div, True -> {
exp_to_string_no_spaces(exp)
}
_, _ -> {
let left =
exp_to_string_with_context(numerator, option.Some(operator), True)
let right =
exp_to_string_with_context(
denominator,
option.Some(operator),
False,
)
let op = operator_to_string(operator)
left <> " " <> op <> " " <> right
}
}
}
}
}
/// Converts a CQL comparator to its string representation.
fn comparator_to_string(comparator: Comparator) -> String {
case comparator {
ast.LessThan -> "<"
ast.LessThanOrEqualTo -> "<="
ast.GreaterThan -> ">"
ast.GreaterThanOrEqualTo -> ">="
}
}
/// Converts a CQL operator to its string representation.
@internal
pub fn operator_to_string(operator: Operator) -> String {
case operator {
ast.Add -> "+"
ast.Sub -> "-"
ast.Mul -> "*"
ast.Div -> "/"
}
}
fn float_to_string(f: Float) -> String {
let truncated = float.truncate(f)
let is_whole = int.to_float(truncated) == f
case is_whole {
True -> int.to_string(truncated)
False -> float.to_string(f)
}
}
fn is_path_expression(exp: Exp(a)) -> Bool {
case get_leftmost_word(exp) {
option.Some(w) -> {
case string.ends_with(w, ":") {
True -> all_divisions(exp)
False -> False
}
}
option.None -> False
}
}
fn get_leftmost_word(exp: Exp(a)) -> Option(String) {
case exp {
ast.Primary(ast.PrimaryWord(ast.Word(w))) -> option.Some(w)
ast.Primary(ast.PrimaryExp(inner_exp)) -> get_leftmost_word(inner_exp)
ast.TimeSliceExpr(_) -> option.None
ast.OperatorExpr(left, _, _) -> get_leftmost_word(left)
}
}
fn all_divisions(exp: Exp(a)) -> Bool {
case exp {
ast.Primary(_) -> True
ast.OperatorExpr(left, right, ast.Div) ->
all_divisions(left) && all_divisions(right)
_ -> False
}
}
fn exp_to_string_no_spaces(exp: Exp(a)) -> String {
case exp {
ast.Primary(ast.PrimaryWord(ast.Word(w))) -> w
ast.OperatorExpr(left, right, ast.Div) ->
exp_to_string_no_spaces(left) <> "/" <> exp_to_string_no_spaces(right)
_ -> exp_to_string(exp)
}
}
fn exp_to_string_with_context(
exp: Exp(a),
parent_op: Option(Operator),
_is_left: Bool,
) -> String {
case exp {
ast.Primary(primary:) -> primary_to_string(primary, parent_op)
ast.TimeSliceExpr(_) -> exp_to_string(exp)
ast.OperatorExpr(numerator:, denominator:, operator:) -> {
case operator, is_path_expression(exp) {
ast.Div, True -> exp_to_string_no_spaces(exp)
_, _ -> {
let left =
exp_to_string_with_context(numerator, option.Some(operator), True)
let right =
exp_to_string_with_context(
denominator,
option.Some(operator),
False,
)
let op = operator_to_string(operator)
left <> " " <> op <> " " <> right
}
}
}
}
}
fn primary_to_string(
primary: Primary(a),
_parent_op: Option(Operator),
) -> String {
case primary {
ast.PrimaryWord(word:) -> word.value
ast.PrimaryExp(exp:) -> {
"(" <> exp_to_string(exp) <> ")"
}
}
}
/// Strips outer parentheses from a string if they wrap the entire expression.
/// E.g., "(a + b)" -> "a + b", but "(a + b) * c" stays unchanged.
@internal
pub fn strip_outer_parens(s: String) -> String {
let trimmed = string.trim(s)
case string.starts_with(trimmed, "(") && string.ends_with(trimmed, ")") {
True -> {
let inner = string.slice(trimmed, 1, string.length(trimmed) - 2)
case is_balanced(inner, 0) {
True -> inner
False -> trimmed
}
}
False -> trimmed
}
}
fn is_balanced(s: String, depth: Int) -> Bool {
case string.pop_grapheme(s) {
Error(_) -> depth == 0
Ok(#("(", rest)) -> is_balanced(rest, depth + 1)
Ok(#(")", rest)) ->
case depth {
0 -> False
_ -> is_balanced(rest, depth - 1)
}
Ok(#(_, rest)) -> is_balanced(rest, depth)
}
}