Current section

Files

Jump to
caffeine_lang src caffeine_lang parsing_utils.gleam
Raw

src/caffeine_lang/parsing_utils.gleam

import gleam/list
import gleam/result
import gleam/string
/// Splits a string at commas that are not inside parentheses or curly braces.
/// Used by collection and modifier type parsers for nested type arguments.
@internal
pub fn split_at_top_level_comma(s: String) -> List(String) {
let chars = string.to_graphemes(s)
do_split_at_top_level_comma(chars, 0, 0, 0, [], [])
}
/// Extracts the content inside the outermost pair of parentheses,
/// properly handling nested parentheses.
/// Returns Error if there's content after the closing paren (e.g., refinement constraints).
@internal
pub fn extract_paren_content(raw: String) -> Result(String, Nil) {
case string.split_once(raw, "(") {
Error(_) -> Error(Nil)
Ok(#(_, after_open)) -> {
// Find the matching close paren by tracking nesting depth
use #(content, rest) <- result.try(
find_matching_close_paren(after_open, 1, []),
)
// If there's non-whitespace content after the closing paren,
// this is likely a refinement type and we should fail
case string.trim(rest) {
"" -> Ok(string.trim(content))
_ -> Error(Nil)
}
}
}
}
/// Finds the matching closing parenthesis, tracking nesting depth.
/// Returns the content before the matching close and the rest after it.
fn find_matching_close_paren(
s: String,
depth: Int,
acc: List(String),
) -> Result(#(String, String), Nil) {
case string.pop_grapheme(s) {
Error(_) -> Error(Nil)
Ok(#("(", rest)) -> find_matching_close_paren(rest, depth + 1, ["(", ..acc])
Ok(#(")", rest)) -> {
case depth {
1 -> Ok(#(string.concat(list.reverse(acc)), rest))
_ -> find_matching_close_paren(rest, depth - 1, [")", ..acc])
}
}
Ok(#(char, rest)) -> find_matching_close_paren(rest, depth, [char, ..acc])
}
}
/// Extracts the trimmed content inside the outermost parentheses.
/// Falls back to trimming the raw string if no valid parenthesized content is found.
@internal
pub fn paren_innerds_trimmed(raw: String) -> String {
case extract_paren_content(raw) {
Ok(content) -> content
Error(_) -> string.trim(raw)
}
}
/// Splits a parenthesized type string at the top-level comma only.
/// Handles nested parentheses correctly.
/// Example: "(String, Dict(String, Integer))" -> ["String", "Dict(String, Integer)"]
@internal
pub fn paren_innerds_split_and_trimmed(raw: String) -> List(String) {
case extract_paren_content(raw) {
Ok(content) -> split_at_top_level_comma(content)
Error(_) -> []
}
}
fn do_split_at_top_level_comma(
chars: List(String),
paren_depth: Int,
brace_depth: Int,
bracket_depth: Int,
current: List(String),
acc: List(String),
) -> List(String) {
case chars {
[] -> {
let trimmed = string.trim(string.concat(list.reverse(current)))
case trimmed {
"" -> list.reverse(acc)
_ -> list.reverse([trimmed, ..acc])
}
}
["(", ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth + 1,
brace_depth,
bracket_depth,
["(", ..current],
acc,
)
[")", ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth - 1,
brace_depth,
bracket_depth,
[")", ..current],
acc,
)
["{", ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth,
brace_depth + 1,
bracket_depth,
["{", ..current],
acc,
)
["}", ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth,
brace_depth - 1,
bracket_depth,
["}", ..current],
acc,
)
["[", ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth,
brace_depth,
bracket_depth + 1,
["[", ..current],
acc,
)
["]", ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth,
brace_depth,
bracket_depth - 1,
["]", ..current],
acc,
)
[",", ..rest]
if paren_depth == 0 && brace_depth == 0 && bracket_depth == 0
-> {
let trimmed = string.trim(string.concat(list.reverse(current)))
do_split_at_top_level_comma(
rest,
paren_depth,
brace_depth,
bracket_depth,
[],
[trimmed, ..acc],
)
}
[char, ..rest] ->
do_split_at_top_level_comma(
rest,
paren_depth,
brace_depth,
bracket_depth,
[char, ..current],
acc,
)
}
}