-module(taffy). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/taffy.gleam"). -export([error_location/2, parse/1, parse_all/1, validate_unique_keys/1, get/2, get_or/3, get_path/2, index/2, as_string/1, as_int/1, as_float/1, as_bool/1, as_list/1, as_dict/1, as_pairs/1, is_null/1, to_string/1, to_yaml/1, to_json/1, to_json_string/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " A pure-Gleam YAML 1.2 parser. Passes 351/351 of the official YAML test\n" " suite. Works on both Erlang and JavaScript targets; an optional native\n" " backend (`taffy/native`, Erlang only) wraps `fast_yaml` for ~3-7×\n" " speedup on large documents.\n" "\n" " ## Quick start\n" "\n" " ```gleam\n" " import taffy\n" "\n" " pub fn main() {\n" " let assert Ok(value) =\n" " taffy.parse(\"name: John\\nage: 30\\ntags:\\n - gleam\\n - erlang\")\n" "\n" " let assert Ok(name) = taffy.get(value, \"name\")\n" " let json = taffy.to_json_string(value)\n" " }\n" " ```\n" "\n" " ## Output\n" "\n" " - `to_json` / `to_json_string` — convert to `gleam_json` values\n" " - `to_yaml` — emit block-style YAML, round-trips through `parse`\n" " - `validate_unique_keys` — opt-in YAML 1.2 strict duplicate-key check\n" ). -file("src/taffy.gleam", 63). -spec error_location_loop(list(binary()), integer(), integer(), integer()) -> {integer(), integer()}. error_location_loop(Graphemes, Remaining, Line, Col) -> case {Graphemes, Remaining} of {_, N} when N =< 0 -> {Line, Col}; {[], _} -> {Line, Col}; {[<<"\n"/utf8>> | Rest], _} -> error_location_loop(Rest, Remaining - 1, Line + 1, 1); {[_ | Rest@1], _} -> error_location_loop(Rest@1, Remaining - 1, Line, Col + 1) end. -file("src/taffy.gleam", 59). ?DOC( " Compute `#(line, column)` (both 1-indexed) for a grapheme position\n" " inside `input`. Out-of-range positions clamp to the last line/column.\n" " Useful for turning `error.pos` into a user-facing location:\n" "\n" " ```gleam\n" " case taffy.parse(input) {\n" " Ok(_) -> ...\n" " Error(err) -> {\n" " let #(line, col) = taffy.error_location(input, err.pos)\n" " io.println(err.message <> \" at \" <> int.to_string(line) <>\n" " \":\" <> int.to_string(col))\n" " }\n" " }\n" " ```\n" ). -spec error_location(binary(), integer()) -> {integer(), integer()}. error_location(Input, Pos) -> error_location_loop(gleam@string:to_graphemes(Input), Pos, 1, 1). -file("src/taffy.gleam", 83). ?DOC( " Parse a single YAML document. For multi-document streams use `parse_all`.\n" " On error the position points at the grapheme where parsing gave up.\n" " `<<` merge keys are resolved automatically. Duplicate mapping keys are\n" " allowed (with first-wins access) — the YAML 1.2 spec mandates uniqueness\n" " but the official test suite expects parsers to accept them; use\n" " `validate_unique_keys` to enforce strict uniqueness yourself.\n" ). -spec parse(binary()) -> {ok, taffy@value:yaml_value()} | {error, taffy@parser@types:parse_error()}. parse(Input) -> case taffy@lexer:tokenize(Input) of {error, {Msg, Pos}} -> {error, {parse_error, Msg, Pos}}; {ok, Tokens} -> _pipe = taffy@parser:parse(Tokens), gleam@result:map(_pipe, fun taffy@value:resolve_merges/1) end. -file("src/taffy.gleam", 92). ?DOC( " Parse a YAML stream as a list of documents. Empty input yields `[]`.\n" " Same merge-key + duplicate semantics as `parse`.\n" ). -spec parse_all(binary()) -> {ok, list(taffy@value:yaml_value())} | {error, taffy@parser@types:parse_error()}. parse_all(Input) -> case taffy@lexer:tokenize(Input) of {error, {Msg, Pos}} -> {error, {parse_error, Msg, Pos}}; {ok, Tokens} -> _pipe = taffy@parser:parse_all(Tokens), gleam@result:map( _pipe, fun(_capture) -> gleam@list:map(_capture, fun taffy@value:resolve_merges/1) end ) end. -file("src/taffy.gleam", 103). ?DOC( " Reject any mapping (recursively) that contains duplicate keys. The error\n" " message names the first offending key. Run after `parse` if your\n" " application requires YAML 1.2's key-uniqueness invariant.\n" ). -spec validate_unique_keys(taffy@value:yaml_value()) -> {ok, taffy@value:yaml_value()} | {error, taffy@parser@types:parse_error()}. validate_unique_keys(Val) -> case taffy@value:check_no_duplicates(Val) of {ok, nil} -> {ok, Val}; {error, Key} -> {error, {parse_error, <<"Duplicate mapping key: "/utf8, Key/binary>>, 0}} end. -file("src/taffy.gleam", 112). ?DOC( " Look up a key in a mapping. Returns `Error(Nil)` when `val` is not a\n" " mapping or the key is absent.\n" ). -spec get(taffy@value:yaml_value(), binary()) -> {ok, taffy@value:yaml_value()} | {error, nil}. get(Val, Key) -> _pipe = taffy@value:get(Val, Key), gleam@option:to_result(_pipe, nil). -file("src/taffy.gleam", 118). ?DOC( " Like `get`, but returns `default` instead of an error when the key is\n" " missing or `val` is not a mapping.\n" ). -spec get_or(taffy@value:yaml_value(), binary(), taffy@value:yaml_value()) -> taffy@value:yaml_value(). get_or(Val, Key, Default) -> case taffy@value:get(Val, Key) of {some, V} -> V; none -> Default end. -file("src/taffy.gleam", 127). ?DOC( " Walk a chain of keys through nested mappings. An empty path returns\n" " `val` unchanged; any missing key short-circuits to `Error(Nil)`.\n" ). -spec get_path(taffy@value:yaml_value(), list(binary())) -> {ok, taffy@value:yaml_value()} | {error, nil}. get_path(Val, Path) -> case Path of [] -> {ok, Val}; [Key | Rest] -> case taffy@value:get(Val, Key) of {some, V} -> get_path(V, Rest); none -> {error, nil} end end. -file("src/taffy.gleam", 141). ?DOC( " Get the `idx`-th item of a sequence (0-indexed). Returns `Error(Nil)` if\n" " `val` is not a sequence or `idx` is out of range.\n" ). -spec index(taffy@value:yaml_value(), integer()) -> {ok, taffy@value:yaml_value()} | {error, nil}. index(Val, Idx) -> _pipe = taffy@value:index(Val, Idx), gleam@option:to_result(_pipe, nil). -file("src/taffy.gleam", 146). ?DOC(" Pull out a string scalar; `None` for any other variant.\n"). -spec as_string(taffy@value:yaml_value()) -> gleam@option:option(binary()). as_string(Val) -> taffy@value:as_string(Val). -file("src/taffy.gleam", 151). ?DOC(" Pull out an int scalar; `None` for any other variant (no float coercion).\n"). -spec as_int(taffy@value:yaml_value()) -> gleam@option:option(integer()). as_int(Val) -> taffy@value:as_int(Val). -file("src/taffy.gleam", 157). ?DOC( " Pull out a float scalar. Coerces `Int(n)` to `Float(n)` so numeric callers\n" " don't have to branch on the parse-time tag.\n" ). -spec as_float(taffy@value:yaml_value()) -> gleam@option:option(float()). as_float(Val) -> taffy@value:as_float(Val). -file("src/taffy.gleam", 162). ?DOC(" Pull out a bool scalar; `None` for any other variant.\n"). -spec as_bool(taffy@value:yaml_value()) -> gleam@option:option(boolean()). as_bool(Val) -> taffy@value:as_bool(Val). -file("src/taffy.gleam", 167). ?DOC(" Pull out a sequence as a list. Order is preserved.\n"). -spec as_list(taffy@value:yaml_value()) -> gleam@option:option(list(taffy@value:yaml_value())). as_list(Val) -> taffy@value:as_list(Val). -file("src/taffy.gleam", 173). ?DOC( " Pull out a mapping as a `Dict`. Note: this loses YAML's insertion order;\n" " use `as_pairs` if order matters.\n" ). -spec as_dict(taffy@value:yaml_value()) -> gleam@option:option(gleam@dict:dict(binary(), taffy@value:yaml_value())). as_dict(Val) -> taffy@value:as_dict(Val). -file("src/taffy.gleam", 179). ?DOC( " Pull out a mapping as ordered `#(key, value)` pairs, preserving YAML\n" " insertion order.\n" ). -spec as_pairs(taffy@value:yaml_value()) -> gleam@option:option(list({binary(), taffy@value:yaml_value()})). as_pairs(Val) -> taffy@value:as_pairs(Val). -file("src/taffy.gleam", 184). ?DOC(" True only for `Null`. Bool/Int/zero values all return False.\n"). -spec is_null(taffy@value:yaml_value()) -> boolean(). is_null(Val) -> taffy@value:is_null(Val). -file("src/taffy.gleam", 190). ?DOC( " Render a value as a JSON-shaped debug string. Useful for snapshot tests;\n" " for round-trippable YAML output use `to_yaml`.\n" ). -spec to_string(taffy@value:yaml_value()) -> binary(). to_string(Val) -> taffy@value:to_string(Val). -file("src/taffy.gleam", 197). ?DOC( " Emit a value as block-style YAML with a trailing newline. The output\n" " round-trips through `parse` for all values that are themselves\n" " round-trippable (taffy is lossy on tag, anchor, and comment metadata).\n" ). -spec to_yaml(taffy@value:yaml_value()) -> binary(). to_yaml(Val) -> taffy@value:to_yaml(Val). -file("src/taffy.gleam", 203). ?DOC( " Convert a parsed YAML value into a `gleam_json` `Json`. Whole-valued\n" " floats serialize as ints to round-trip nicely with the YAML test suite.\n" ). -spec to_json(taffy@value:yaml_value()) -> gleam@json:json(). to_json(Val) -> case Val of null -> gleam@json:null(); {bool, B} -> gleam@json:bool(B); {int, I} -> gleam@json:int(I); {float, F} -> Truncated = erlang:trunc(F), case F =:= erlang:float(Truncated) of true -> gleam@json:int(Truncated); false -> gleam@json:float(F) end; {string, S} -> gleam@json:string(S); {sequence, Items} -> gleam@json:array(Items, fun to_json/1); {mapping, Pairs} -> gleam@json:object( begin _pipe = Pairs, gleam@list:map( _pipe, fun(Pair) -> {erlang:element(1, Pair), to_json(erlang:element(2, Pair))} end ) end ) end. -file("src/taffy.gleam", 226). ?DOC(" Convenience wrapper around `to_json` + `json.to_string`.\n"). -spec to_json_string(taffy@value:yaml_value()) -> binary(). to_json_string(Val) -> _pipe = to_json(Val), gleam@json:to_string(_pipe).