Packages
caffeine_lang
4.4.0
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/position_utils.gleam
/// Utilities for finding source positions of names in content.
/// Positions are 1-indexed to match parser/tokenizer conventions.
import gleam/string
/// Finds the 1-indexed line and column of the first whole-word occurrence
/// of a name in source. Returns #(1, 1) if not found.
pub fn find_name_position(content: String, name: String) -> #(Int, Int) {
let lines = string.split(content, "\n")
find_in_lines(lines, name, 1)
}
fn find_in_lines(
lines: List(String),
name: String,
line_num: Int,
) -> #(Int, Int) {
case lines {
[] -> #(1, 1)
[first, ..rest] -> {
case find_whole_word(first, name, 0) {
Ok(col) -> #(line_num, col + 1)
Error(_) -> find_in_lines(rest, name, line_num + 1)
}
}
}
}
/// Searches for `name` as a whole word within `line`, starting from `offset`.
/// Returns Ok(column) as 0-indexed offset on match, Error(Nil) if not found.
fn find_whole_word(line: String, name: String, offset: Int) -> Result(Int, Nil) {
case string.split_once(line, name) {
Error(_) -> Error(Nil)
Ok(#(before, after)) -> {
let col = offset + string.length(before)
let before_ok = case string.last(before) {
Ok(c) -> !is_word_char(c)
Error(_) -> True
}
let after_ok = case string.first(after) {
Ok(c) -> !is_word_char(c)
Error(_) -> True
}
case before_ok && after_ok {
True -> Ok(col)
False -> {
let skip = string.length(before) + string.length(name)
find_whole_word(string.drop_start(line, skip), name, offset + skip)
}
}
}
}
}
fn is_word_char(g: String) -> Bool {
case g {
"_" -> True
_ ->
case string.to_utf_codepoints(g) {
[cp] -> {
let code = string.utf_codepoint_to_int(cp)
{ code >= 65 && code <= 90 }
|| { code >= 97 && code <= 122 }
|| { code >= 48 && code <= 57 }
}
_ -> False
}
}
}