Packages
caffeine_lang
5.4.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/generator.gleam
import caffeine_query_language/ast.{
type Exp, type Substituted, OperatorExpr, Primary, PrimaryExp, PrimaryWord,
TimeSliceExp, Word,
}
import caffeine_query_language/parser
import caffeine_query_language/printer
import caffeine_query_language/resolver
import gleam/dict
import gleam/list
import gleam/result
import gleam/set.{type Set}
import gleam/string
/// Transform an expression tree by substituting word values using a dictionary.
/// Words found in the dictionary are replaced with their corresponding values.
/// Words not found in the dictionary are left unchanged.
@internal
pub fn substitute_words(
exp: Exp(a),
substitutions: dict.Dict(String, String),
) -> Exp(Substituted) {
case exp {
Primary(PrimaryWord(Word(name))) -> {
let value = dict.get(substitutions, name) |> result.unwrap(name)
Primary(PrimaryWord(Word(value)))
}
Primary(PrimaryExp(inner)) ->
Primary(PrimaryExp(substitute_words(inner, substitutions)))
ast.TimeSliceExpr(spec) -> {
let query =
dict.get(substitutions, spec.query) |> result.unwrap(spec.query)
ast.TimeSliceExpr(TimeSliceExp(..spec, query: query))
}
OperatorExpr(left, right, op) ->
OperatorExpr(
substitute_words(left, substitutions),
substitute_words(right, substitutions),
op,
)
}
}
/// Extracts all word names from an expression AST.
/// Returns a list of unique word strings found in the expression.
@internal
pub fn extract_words(exp: Exp(a)) -> List(String) {
extract_words_loop(exp, set.new())
|> set.to_list
|> list.sort(string.compare)
}
/// Accumulates unique word names into a Set.
fn extract_words_loop(exp: Exp(a), acc: Set(String)) -> Set(String) {
case exp {
Primary(PrimaryWord(Word(name))) -> set.insert(acc, name)
Primary(PrimaryExp(inner)) -> extract_words_loop(inner, acc)
ast.TimeSliceExpr(_) -> acc
OperatorExpr(left, right, _) ->
extract_words_loop(right, extract_words_loop(left, acc))
}
}
/// Parse a value expression, resolve it, substitute indicator names,
/// and return the resulting expression as a plain string.
/// Handles identity expressions (single words, compositions) and good-over-total divisions.
/// Rejects time_slice expressions (not valid for expression-based resolution).
@internal
pub fn resolve_slo_to_expression(
value_expr: String,
substitutions: dict.Dict(String, String),
) -> Result(String, String) {
use parsed <- result.try(
parser.parse_expr(value_expr)
|> result.map_error(fn(err) { "Parse error: " <> err }),
)
let exp = case resolver.resolve_primitives(parsed) {
Ok(resolver.GoodOverTotal(num, den)) -> Ok(OperatorExpr(num, den, ast.Div))
Ok(resolver.TimeSlice(..)) ->
Error(
"time_slice expressions are not supported for expression resolution",
)
// Not a division or time_slice — treat as direct expression (identity/composition).
Error(_) -> Ok(parsed)
}
use exp <- result.try(exp)
use <- validate_words_exist(exp, substitutions)
Ok(substitute_words(exp, substitutions) |> printer.exp_to_string)
}
/// Validate that all words in an expression exist in the substitutions dict.
/// Returns an error listing any missing indicator names.
fn validate_words_exist(
exp: Exp(a),
substitutions: dict.Dict(String, String),
next: fn() -> Result(String, String),
) -> Result(String, String) {
let missing =
extract_words(exp)
|> list.filter(fn(word) {
case dict.get(substitutions, word) {
Ok(_) -> False
Error(_) -> True
}
})
case missing {
[] -> next()
_ ->
Error(
"evaluation references undefined indicators: "
<> string.join(missing, ", "),
)
}
}