Packages
caffeine_lang
4.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_lang/frontend/pipeline.gleam
/// Frontend pipeline for compiling .caffeine source files.
/// Orchestrates the tokenizer, parser, validator, and generator
/// to transform .caffeine source into Blueprint and Expectation types.
import caffeine_lang/errors.{type CompilationError, SourceLocation}
import caffeine_lang/frontend/lowering
import caffeine_lang/frontend/parser
import caffeine_lang/frontend/parser_error.{type ParserError}
import caffeine_lang/frontend/tokenizer_error
import caffeine_lang/frontend/validator
import caffeine_lang/linker/blueprints.{type Blueprint}
import caffeine_lang/linker/expectations.{type Expectation}
import caffeine_lang/position_utils
import caffeine_lang/rich_error.{type RichError, ErrorCode, RichError}
import caffeine_lang/source_file.{type SourceFile}
import caffeine_lang/string_distance
import gleam/list
import gleam/option
import gleam/result
import gleam/string
/// Compiles a blueprints .caffeine source to a list of blueprints.
@internal
pub fn compile_blueprints(
source: SourceFile,
) -> Result(List(Blueprint), CompilationError) {
use ast <- result.try(
parser.parse_blueprints_file(source.content)
|> result.map_error(fn(err) {
parser_error_to_compilation_error(err, source.path)
}),
)
use validated <- result.try(
validator.validate_blueprints_file(ast)
|> result.map_error(fn(errs) {
validator_errors_to_compilation_error(errs, source.path)
}),
)
Ok(lowering.lower_blueprints(validated))
}
/// Compiles an expects .caffeine source to a list of expectations.
@internal
pub fn compile_expects(
source: SourceFile,
) -> Result(List(Expectation), CompilationError) {
use ast <- result.try(
parser.parse_expects_file(source.content)
|> result.map_error(fn(err) {
parser_error_to_compilation_error(err, source.path)
}),
)
use validated <- result.try(
validator.validate_expects_file(ast)
|> result.map_error(fn(errs) {
validator_errors_to_compilation_error(errs, source.path)
}),
)
Ok(lowering.lower_expectations(validated))
}
fn parser_error_to_compilation_error(
err: parser_error.ParserError,
file_path: String,
) -> CompilationError {
errors.frontend_parse_error(
msg: file_path <> ": " <> parser_error.to_string(err),
)
}
fn validator_error_to_compilation_error(
err: validator.ValidatorError,
file_path: String,
) -> CompilationError {
errors.frontend_validation_error(
msg: file_path <> ": " <> validator_error_to_string(err),
)
}
/// Converts a list of ValidatorErrors to a single CompilationError.
fn validator_errors_to_compilation_error(
errs: List(validator.ValidatorError),
file_path: String,
) -> CompilationError {
case errs {
[single] -> validator_error_to_compilation_error(single, file_path)
multiple -> {
let compilation_errors =
multiple
|> list.map(validator_error_to_compilation_error(_, file_path))
errors.CompilationErrors(errors: compilation_errors)
}
}
}
fn validator_error_to_string(err: validator.ValidatorError) -> String {
case err {
validator.DuplicateExtendable(name) -> "Duplicate extendable: " <> name
validator.UndefinedExtendable(name, referenced_by, _candidates) ->
"Undefined extendable '"
<> name
<> "' referenced by '"
<> referenced_by
<> "'"
validator.DuplicateExtendsReference(name, referenced_by) ->
"Duplicate extends reference '"
<> name
<> "' in '"
<> referenced_by
<> "'"
validator.InvalidExtendableKind(name, expected, got) ->
"Invalid extendable kind for '"
<> name
<> "': expected "
<> expected
<> ", got "
<> got
validator.UndefinedTypeAlias(name, referenced_by, _candidates) ->
"Undefined type alias '"
<> name
<> "' referenced by '"
<> referenced_by
<> "'"
validator.DuplicateTypeAlias(name) -> "Duplicate type alias: " <> name
validator.CircularTypeAlias(name, _cycle) ->
"Circular type alias reference detected in '" <> name <> "'"
validator.InvalidDictKeyTypeAlias(alias_name, resolved_to, referenced_by) ->
"Type alias '"
<> alias_name
<> "' used as Dict key resolves to '"
<> resolved_to
<> "' which is not String-based, in '"
<> referenced_by
<> "'"
validator.ExtendableOvershadowing(field_name, item_name, extendable_name) ->
"Field '"
<> field_name
<> "' in '"
<> item_name
<> "' overshadows field from extendable '"
<> extendable_name
<> "'"
validator.ExtendableTypeAliasNameCollision(name) ->
"Name '" <> name <> "' is used as both an extendable and a type alias"
validator.InvalidRefinementValue(value, expected_type, referenced_by) ->
"Refinement value '"
<> value
<> "' is not a valid "
<> expected_type
<> " literal, in '"
<> referenced_by
<> "'"
validator.InvalidPercentageBounds(value, referenced_by) ->
"Percentage value '"
<> value
<> "' must be between 0.0 and 100.0, in '"
<> referenced_by
<> "'"
}
}
// =============================================================================
// RICH ERROR PIPELINE
// =============================================================================
/// Compiles blueprints with rich error information including source locations.
@internal
pub fn compile_blueprints_rich(
source: SourceFile,
) -> Result(List(Blueprint), RichError) {
use ast <- result.try(
parser.parse_blueprints_file(source.content)
|> result.map_error(fn(err) { parser_error_to_rich_error(err, source) }),
)
use validated <- result.try(
validator.validate_blueprints_file(ast)
|> result.map_error(fn(errs) {
validator_errors_to_rich_error(errs, source)
}),
)
Ok(lowering.lower_blueprints(validated))
}
/// Compiles expects with rich error information including source locations.
@internal
pub fn compile_expects_rich(
source: SourceFile,
) -> Result(List(Expectation), RichError) {
use ast <- result.try(
parser.parse_expects_file(source.content)
|> result.map_error(fn(err) { parser_error_to_rich_error(err, source) }),
)
use validated <- result.try(
validator.validate_expects_file(ast)
|> result.map_error(fn(errs) {
validator_errors_to_rich_error(errs, source)
}),
)
Ok(lowering.lower_expectations(validated))
}
/// Converts a ParserError to a RichError with location info.
fn parser_error_to_rich_error(err: ParserError, source: SourceFile) -> RichError {
let compilation_error = parser_error_to_compilation_error(err, source.path)
let #(location, suggestion) = case err {
parser_error.TokenizerError(tok_err) -> {
let #(line, column) = case tok_err {
tokenizer_error.UnterminatedString(line, column) -> #(line, column)
tokenizer_error.InvalidCharacter(line, column, _) -> #(line, column)
}
#(
option.Some(SourceLocation(line:, column:, end_column: option.None)),
option.None,
)
}
parser_error.UnexpectedToken(_, got, line, column) -> #(
option.Some(SourceLocation(
line:,
column:,
end_column: option.Some(column + string.length(got)),
)),
option.None,
)
parser_error.UnexpectedEOF(_, line, column) -> #(
option.Some(SourceLocation(line:, column:, end_column: option.None)),
option.None,
)
parser_error.UnknownType(name, line, column) -> {
let known_types = [
"String", "Integer", "Float", "Boolean", "URL", "List", "Dict",
"Optional", "Defaulted",
]
#(
option.Some(SourceLocation(
line:,
column:,
end_column: option.Some(column + string.length(name)),
)),
string_distance.closest_match(name, known_types),
)
}
parser_error.InvalidRefinement(_, line, column) -> #(
option.Some(SourceLocation(line:, column:, end_column: option.None)),
option.None,
)
parser_error.QuotedFieldName(_, line, column) -> #(
option.Some(SourceLocation(line:, column:, end_column: option.None)),
option.None,
)
parser_error.InvalidTypeAliasName(name, _, line, column) -> #(
option.Some(SourceLocation(
line:,
column:,
end_column: option.Some(column + string.length(name)),
)),
option.None,
)
}
RichError(
error: compilation_error,
code: rich_error.error_code_for(compilation_error),
source_path: option.Some(source.path),
source_content: option.Some(source.content),
location:,
suggestion:,
)
}
/// Converts a list of ValidatorErrors to a RichError.
/// Uses the first error for the primary rich error since RichError is a single error.
fn validator_errors_to_rich_error(
errs: List(validator.ValidatorError),
source: SourceFile,
) -> RichError {
case errs {
// Single error gets full rich error treatment
[single] -> validator_error_to_rich_error(single, source)
// Multiple errors: use the first for location, combine messages
[first, ..] -> {
let compilation_error =
validator_errors_to_compilation_error(errs, source.path)
let first_rich = validator_error_to_rich_error(first, source)
RichError(
error: compilation_error,
code: first_rich.code,
source_path: first_rich.source_path,
source_content: first_rich.source_content,
location: first_rich.location,
suggestion: first_rich.suggestion,
)
}
// Empty list should not happen, but handle gracefully
[] ->
rich_error.from_compilation_error(errors.frontend_validation_error(
msg: source.path <> ": unknown validation error",
))
}
}
/// Converts a ValidatorError to a RichError with location info.
fn validator_error_to_rich_error(
err: validator.ValidatorError,
source: SourceFile,
) -> RichError {
let compilation_error = validator_error_to_compilation_error(err, source.path)
let #(name, suggestion) = case err {
validator.UndefinedExtendable(name, _, candidates) -> #(
name,
string_distance.closest_match(name, candidates),
)
validator.UndefinedTypeAlias(name, _, candidates) -> #(
name,
string_distance.closest_match(name, candidates),
)
validator.DuplicateExtendable(name) -> #(name, option.None)
validator.DuplicateExtendsReference(name, _) -> #(name, option.None)
validator.InvalidExtendableKind(name, _, _) -> #(name, option.None)
validator.DuplicateTypeAlias(name) -> #(name, option.None)
validator.CircularTypeAlias(name, _) -> #(name, option.None)
validator.InvalidDictKeyTypeAlias(alias_name, _, _) -> #(
alias_name,
option.None,
)
validator.ExtendableOvershadowing(field_name, _, _) -> #(
field_name,
option.None,
)
validator.ExtendableTypeAliasNameCollision(name) -> #(name, option.None)
validator.InvalidRefinementValue(value, _, _) -> #(value, option.None)
validator.InvalidPercentageBounds(value, _) -> #(value, option.None)
}
// Look up position of the name in source
let #(line, column) = position_utils.find_name_position(source.content, name)
let location =
SourceLocation(
line:,
column:,
end_column: option.Some(column + string.length(name)),
)
RichError(
error: compilation_error,
code: ErrorCode("validation", 200),
source_path: option.Some(source.path),
source_content: option.Some(source.content),
location: option.Some(location),
suggestion:,
)
}