Packages
caffeine_lang
4.6.4
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_lang/compiler.gleam
import caffeine_lang/analysis/dependency_validator
import caffeine_lang/analysis/semantic_analyzer
import caffeine_lang/analysis/vendor
import caffeine_lang/codegen/datadog
import caffeine_lang/codegen/dependency_graph
import caffeine_lang/codegen/dynatrace
import caffeine_lang/codegen/generator_utils
import caffeine_lang/codegen/honeycomb
import caffeine_lang/codegen/newrelic
import caffeine_lang/errors
import caffeine_lang/frontend/pipeline
import caffeine_lang/linker/artifacts
import caffeine_lang/linker/blueprints
import caffeine_lang/linker/expectations
import caffeine_lang/linker/ir.{
type IntermediateRepresentation, type Linked, type Resolved,
}
import caffeine_lang/linker/ir_builder
import caffeine_lang/linker/linker
import caffeine_lang/source_file.{
type BlueprintSource, type ExpectationSource, type SourceFile, SourceFile,
}
import caffeine_lang/standard_library/artifacts as stdlib_artifacts
import gleam/dict
import gleam/list
import gleam/option.{type Option}
import gleam/result
import gleam/string
import terra_madre/common
import terra_madre/render
import terra_madre/terraform
/// Output of the compilation process containing Terraform, optional dependency graph, and warnings.
pub type CompilationOutput {
CompilationOutput(
terraform: String,
dependency_graph: Option(String),
warnings: List(String),
)
}
/// Compiles a blueprint and expectation sources into Terraform configuration.
/// Pure function — all file reading happens before this function is called.
pub fn compile(
blueprint: SourceFile(BlueprintSource),
expectations: List(SourceFile(ExpectationSource)),
) -> Result(CompilationOutput, errors.CompilationError) {
use irs <- result.try(run_parse_and_link(blueprint, expectations))
use resolved_irs <- result.try(run_semantic_analysis(irs))
run_code_generation(resolved_irs)
}
/// Compiles from source strings directly (no file I/O).
/// Used for browser-based compilation.
pub fn compile_from_strings(
blueprints_source: String,
expectations_source: String,
expectations_path: String,
) -> Result(CompilationOutput, errors.CompilationError) {
use irs <- result.try(parse_from_strings(
blueprints_source,
expectations_source,
expectations_path,
))
use resolved_irs <- result.try(run_semantic_analysis(irs))
run_code_generation(resolved_irs)
}
// ==== Pipeline stages ====
fn run_parse_and_link(
blueprint: SourceFile(BlueprintSource),
expectations: List(SourceFile(ExpectationSource)),
) -> Result(List(IntermediateRepresentation(Linked)), errors.CompilationError) {
linker.link(blueprint, expectations)
}
fn run_semantic_analysis(
irs: List(IntermediateRepresentation(Linked)),
) -> Result(List(IntermediateRepresentation(Resolved)), errors.CompilationError) {
use validated_irs <- result.try(
dependency_validator.validate_dependency_relations(irs),
)
semantic_analyzer.resolve_intermediate_representations(validated_irs)
}
/// Vendor-specific platform configuration bundling code generation
/// and Terraform boilerplate.
type VendorPlatform {
VendorPlatform(
vendor: vendor.Vendor,
generate_resources: fn(List(IntermediateRepresentation(Resolved))) ->
Result(#(List(terraform.Resource), List(String)), errors.CompilationError),
terraform_settings: terraform.TerraformSettings,
provider: terraform.Provider,
variables: List(terraform.Variable),
)
}
/// Returns the platform configuration for a given vendor.
fn platform_for(v: vendor.Vendor) -> VendorPlatform {
case v {
vendor.Datadog ->
VendorPlatform(
vendor: vendor.Datadog,
generate_resources: datadog.generate_resources,
terraform_settings: datadog.terraform_settings(),
provider: datadog.provider(),
variables: datadog.variables(),
)
vendor.Honeycomb ->
VendorPlatform(
vendor: vendor.Honeycomb,
generate_resources: honeycomb.generate_resources,
terraform_settings: honeycomb.terraform_settings(),
provider: honeycomb.provider(),
variables: honeycomb.variables(),
)
vendor.Dynatrace ->
VendorPlatform(
vendor: vendor.Dynatrace,
generate_resources: dynatrace.generate_resources,
terraform_settings: dynatrace.terraform_settings(),
provider: dynatrace.provider(),
variables: dynatrace.variables(),
)
vendor.NewRelic ->
VendorPlatform(
vendor: vendor.NewRelic,
generate_resources: newrelic.generate_resources,
terraform_settings: newrelic.terraform_settings(),
provider: newrelic.provider(),
variables: newrelic.variables(),
)
}
}
fn run_code_generation(
resolved_irs: List(IntermediateRepresentation(Resolved)),
) -> Result(CompilationOutput, errors.CompilationError) {
let grouped = group_by_vendor(resolved_irs)
// Build active platform groups, defaulting to Datadog boilerplate if no IRs.
let active_groups =
grouped
|> dict.to_list
|> list.map(fn(pair) { #(platform_for(pair.0), pair.1) })
let active_groups = case list.is_empty(active_groups) {
True -> [#(platform_for(vendor.Datadog), [])]
False -> active_groups
}
// Generate resources and accumulate config from all active vendors.
// Note: active_groups has at most 4 elements (one per vendor), so the
// list.append calls here are bounded and not a performance concern.
use #(all_resources, all_warnings, required_providers, providers, variables) <- result.try(
list.try_fold(active_groups, #([], [], [], [], []), fn(acc, group) {
let #(platform, irs) = group
let #(resources, warnings, req_provs, provs, vars) = acc
use #(vendor_resources, vendor_warnings) <- result.try(
platform.generate_resources(irs),
)
Ok(#(
list.append(resources, vendor_resources),
list.append(warnings, vendor_warnings),
list.append(
req_provs,
dict.to_list(platform.terraform_settings.required_providers),
),
list.append(provs, [platform.provider]),
list.append(vars, platform.variables),
))
}),
)
let terraform_settings =
terraform.TerraformSettings(
required_version: option.None,
required_providers: dict.from_list(required_providers),
backend: option.None,
cloud: option.None,
)
// Render boilerplate (terraform/provider/variable blocks) without resources.
let boilerplate_config =
terraform.Config(
terraform: option.Some(terraform_settings),
providers: providers,
resources: [],
data_sources: [],
variables: variables,
outputs: [],
locals: [],
modules: [],
)
let boilerplate = render.render_config(boilerplate_config)
// Build resource name → metadata lookup for source comments.
let metadata_by_name =
resolved_irs
|> list.flat_map(fn(ir) {
let base = common.sanitize_terraform_identifier(ir.unique_identifier)
[#(base, ir.metadata), #(base <> "_sli", ir.metadata)]
})
|> dict.from_list
// Render each resource with a source traceability comment.
let resource_sections =
all_resources
|> list.map(fn(resource) {
let rendered = generator_utils.render_resource_to_string(resource)
case dict.get(metadata_by_name, resource.name) {
Ok(metadata) ->
generator_utils.build_source_comment(metadata) <> "\n" <> rendered
Error(_) -> rendered
}
})
// Assemble final output: boilerplate + commented resources.
let terraform_output = case resource_sections {
[] -> boilerplate
sections -> {
let trimmed_boilerplate = string.drop_end(boilerplate, 1)
trimmed_boilerplate <> "\n\n" <> string.join(sections, "\n\n") <> "\n"
}
}
// Dependency graph is only useful when relations exist.
let has_deps =
resolved_irs
|> list.any(fn(ir) {
list.contains(ir.artifact_refs, artifacts.DependencyRelations)
})
let graph = case has_deps {
True -> option.Some(dependency_graph.generate(resolved_irs))
False -> option.None
}
Ok(CompilationOutput(
terraform: terraform_output,
dependency_graph: graph,
warnings: all_warnings,
))
}
/// Groups IRs by their resolved vendor, sorted by unique_identifier within each group.
fn group_by_vendor(
irs: List(IntermediateRepresentation(Resolved)),
) -> dict.Dict(vendor.Vendor, List(IntermediateRepresentation(Resolved))) {
list.group(irs, fn(ir) {
case ir.vendor {
option.Some(v) -> v
// Non-SLO IRs default to Datadog for boilerplate.
option.None -> vendor.Datadog
}
})
|> dict.map_values(fn(_, group) {
list.sort(group, fn(a, b) {
string.compare(a.unique_identifier, b.unique_identifier)
})
})
}
fn parse_from_strings(
blueprints_source: String,
expectations_source: String,
expectations_path: String,
) -> Result(List(IntermediateRepresentation(Linked)), errors.CompilationError) {
let artifacts = stdlib_artifacts.standard_library()
let reserved_labels = ir_builder.reserved_labels_from_artifacts(artifacts)
use raw_blueprints <- result.try(
pipeline.compile_blueprints(SourceFile(
path: "browser/blueprints.caffeine",
content: blueprints_source,
)),
)
use raw_expectations <- result.try(
pipeline.compile_expects(SourceFile(
path: "browser/expectations.caffeine",
content: expectations_source,
)),
)
use validated_blueprints <- result.try(blueprints.validate_blueprints(
raw_blueprints,
artifacts,
))
use expectations_blueprint_collection <- result.try(
expectations.validate_expectations(
raw_expectations,
validated_blueprints,
from: expectations_path,
),
)
ir_builder.build_all(
[#(expectations_blueprint_collection, expectations_path)],
reserved_labels:,
)
}