-module(handles@parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([run/2]). -export_type([parse_error/0, ast/0]). -type parse_error() :: {unbalanced_block, integer(), integer(), binary()} | {unknown_block_kind, integer(), integer(), binary()}. -type ast() :: {constant, binary()} | {property, list(binary())} | {block, binary(), list(binary()), list(ast())}. -spec validation_pass(list(handles@lexer:token()), list(binary())) -> list({ok, handles@lexer:token()} | {error, parse_error()}). validation_pass(Tokens, Valid_blocks) -> _pipe = Tokens, gleam@list:map(_pipe, fun(It) -> case It of {block_start, Start, End, Kind, _} -> case gleam@list:contains(Valid_blocks, Kind) of true -> {ok, It}; false -> {error, {unknown_block_kind, Start, End, Kind}} end; _ -> {ok, It} end end). -spec balance_pass(list({ok, handles@lexer:token()} | {error, parse_error()})) -> list({ok, handles@lexer:token()} | {error, parse_error()}). balance_pass(Tokens) -> {Out@1, Stack@1} = begin _pipe = Tokens, gleam@list:fold( _pipe, {[], []}, fun(Acc, It) -> {Out, Stack} = Acc, case It of {ok, {block_start, _, _, Kind, _}} -> {[It | Out], [Kind | Stack]}; {ok, {block_end, Start, End, Kind@1}} -> case gleam@list:first(Stack) of {ok, Top} when Top =:= Kind@1 -> case gleam@list:rest(Stack) of {ok, Rest} -> {[It | Out], Rest}; _ -> {[{error, {unbalanced_block, Start, End, Kind@1}} | Out], []} end; _ -> case gleam@list:rest(Stack) of {ok, Rest@1} -> {[It | Out], Rest@1}; _ -> {[{error, {unbalanced_block, Start, End, Kind@1}} | Out], []} end end; _ -> {[It | Out], Stack} end end ) end, gleam@list:concat( [Out@1, gleam@list:map( Stack@1, fun(It@1) -> {error, {unbalanced_block, -1, -1, It@1}} end )] ). -spec ast_transform_pass(list(handles@lexer:token()), list(ast())) -> list(ast()). ast_transform_pass(Tokens, Ast) -> case Tokens of [] -> lists:reverse(Ast); [Head | Tail] -> case Head of {constant, _, _, Value} -> ast_transform_pass(Tail, [{constant, Value} | Ast]); {property, _, _, Path} -> ast_transform_pass(Tail, [{property, Path} | Ast]); {block_start, _, _, Kind, Path@1} -> Children = ast_transform_pass(Tail, []), ast_transform_pass( gleam@list:drop(Tail, erlang:length(Children)), [{block, Kind, Path@1, Children} | Ast] ); {block_end, _, _, _} -> lists:reverse(Ast) end end. -spec run(list(handles@lexer:token()), list(binary())) -> {ok, list(ast())} | {error, list(parse_error())}. run(Tokens, Valid_blocks) -> case begin _pipe = Tokens, _pipe@1 = validation_pass(_pipe, Valid_blocks), _pipe@2 = balance_pass(_pipe@1), gleam@result:partition(_pipe@2) end of {[{constant, _, _, <<""/utf8>>} | Tokens@1], []} -> {ok, begin _pipe@3 = Tokens@1, ast_transform_pass(_pipe@3, []) end}; {Tokens@2, []} -> {ok, begin _pipe@4 = Tokens@2, ast_transform_pass(_pipe@4, []) end}; {_, Err} -> {error, Err} end.