-module(glsql@parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glsql/parser.gleam"). -export([parse/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. -file("src/glsql/parser.gleam", 98). ?DOC(" Consume tokens up to and including the next top-level `;`.\n"). -spec skip_statement(list(glsql@token:positioned())) -> list(glsql@token:positioned()). skip_statement(Tokens) -> case Tokens of [] -> []; [{positioned, semicolon, _, _} | Rest] -> Rest; [_ | Rest@1] -> skip_statement(Rest@1) end. -file("src/glsql/parser.gleam", 487). -spec expect(list(glsql@token:positioned()), glsql@token:token(), binary()) -> {ok, {integer(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. expect(Tokens, Want, Label) -> case Tokens of [{positioned, T, Pos, _} | Rest] when T =:= Want -> {ok, {Pos, Rest}}; [{positioned, T@1, Pos@1, _} | _] -> {error, {parse_error, <<<<<<<<"Expected `"/utf8, Label/binary>>/binary, "`, found `"/utf8>>/binary, (glsql@token:to_string(T@1))/binary>>/binary, "`"/utf8>>, Pos@1, none}}; [] -> {error, {parse_error, <<<<"Unexpected end of file, expected `"/utf8, Label/binary>>/binary, "`"/utf8>>, 0, none}} end. -file("src/glsql/parser.gleam", 446). -spec identifier(list(glsql@token:positioned())) -> {ok, {binary(), integer(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. identifier(Tokens) -> case Tokens of [{positioned, {word, W}, Pos, _} | Rest] -> {ok, {W, Pos, Rest}}; [{positioned, {quoted_ident, W@1}, Pos@1, _} | Rest@1] -> {ok, {W@1, Pos@1, Rest@1}}; [{positioned, T, Pos@2, _} | _] -> {error, {parse_error, <<<<"Expected a name, found `"/utf8, (glsql@token:to_string(T))/binary>>/binary, "`"/utf8>>, Pos@2, none}}; [] -> {error, {parse_error, <<"Unexpected end of file, expected a name"/utf8>>, 0, none}} end. -file("src/glsql/parser.gleam", 311). -spec collect_names(list(glsql@token:positioned()), list(binary())) -> {ok, {list(binary()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. collect_names(Tokens, Acc) -> gleam@result:'try'( identifier(Tokens), fun(_use0) -> {Name, _, Rest} = _use0, case Rest of [{positioned, comma, _, _} | Rest@1] -> collect_names(Rest@1, [Name | Acc]); [{positioned, r_paren, _, _} | Rest@2] -> {ok, {lists:reverse([Name | Acc]), Rest@2}}; [{positioned, T, Pos, _} | _] -> {error, {parse_error, <<<<"Expected `,` or `)`, found `"/utf8, (glsql@token:to_string(T))/binary>>/binary, "`"/utf8>>, Pos, none}}; [] -> {error, {parse_error, <<"Unexpected end of file in column list"/utf8>>, 0, none}} end end ). -file("src/glsql/parser.gleam", 302). -spec optional_column_list(list(glsql@token:positioned())) -> {ok, {list(binary()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. optional_column_list(Tokens) -> case Tokens of [{positioned, l_paren, _, _} | Rest] -> collect_names(Rest, []); _ -> {ok, {[], Tokens}} end. -file("src/glsql/parser.gleam", 433). -spec qualified_name(list(glsql@token:positioned())) -> {ok, {gleam@option:option(binary()), binary(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. qualified_name(Tokens) -> gleam@result:'try'( identifier(Tokens), fun(_use0) -> {First, _, Rest} = _use0, case Rest of [{positioned, dot, _, _} | Rest@1] -> gleam@result:'try'( identifier(Rest@1), fun(_use0@1) -> {Second, _, Rest@2} = _use0@1, {ok, {{some, First}, Second, Rest@2}} end ); _ -> {ok, {none, First, Rest}} end end ). -file("src/glsql/parser.gleam", 364). -spec join(binary(), binary()) -> binary(). join(Acc, Word) -> case (Acc =:= <<""/utf8>>) orelse gleam_stdlib:string_ends_with( Acc, <<"("/utf8>> ) of true -> <>; false -> <<<>/binary, Word/binary>> end. -file("src/glsql/parser.gleam", 332). ?DOC( " Consume an expression to a balanced-paren boundary, stopping at a top-level\n" " comma or closing paren. Retained as opaque source text — never interpreted.\n" ). -spec take_expression(list(glsql@token:positioned()), binary(), integer()) -> {binary(), list(glsql@token:positioned())}. take_expression(Tokens, Acc, Depth) -> case Tokens of [] -> {gleam@string:trim(Acc), []}; [{positioned, comma, _, _} | _] when Depth =:= 0 -> {gleam@string:trim(Acc), Tokens}; [{positioned, r_paren, _, _} | _] when Depth =:= 0 -> {gleam@string:trim(Acc), Tokens}; [{positioned, {word, W}, _, _} | Rest] when Depth =:= 0 -> case gleam@list:contains( [<<"not"/utf8>>, <<"null"/utf8>>, <<"primary"/utf8>>, <<"unique"/utf8>>, <<"default"/utf8>>, <<"references"/utf8>>, <<"check"/utf8>>, <<"constraint"/utf8>>, <<"foreign"/utf8>>, <<"generated"/utf8>>, <<"collate"/utf8>>], string:lowercase(W) ) of true -> {gleam@string:trim(Acc), Tokens}; false -> take_expression(Rest, join(Acc, W), Depth) end; [{positioned, l_paren, _, _} | Rest@1] -> take_expression(Rest@1, <>, Depth + 1); [{positioned, r_paren, _, _} | Rest@2] -> take_expression(Rest@2, <>, Depth - 1); [{positioned, {word, W@1}, _, _} | Rest@3] -> take_expression(Rest@3, join(Acc, W@1), Depth); [{positioned, T, _, _} | Rest@4] -> take_expression( Rest@4, <>, Depth ) end. -file("src/glsql/parser.gleam", 460). -spec keyword(list(glsql@token:positioned()), binary()) -> {ok, {integer(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. keyword(Tokens, Want) -> case Tokens of [{positioned, {word, W}, Pos, _} | Rest] -> case string:lowercase(W) =:= Want of true -> {ok, {Pos, Rest}}; false -> {error, {parse_error, <<<<<<<<"Expected `"/utf8, Want/binary>>/binary, "`, found `"/utf8>>/binary, W/binary>>/binary, "`"/utf8>>, Pos, none}} end; [{positioned, T, Pos@1, _} | _] -> {error, {parse_error, <<<<<<<<"Expected `"/utf8, Want/binary>>/binary, "`, found `"/utf8>>/binary, (glsql@token:to_string(T))/binary>>/binary, "`"/utf8>>, Pos@1, none}}; [] -> {error, {parse_error, <<<<"Unexpected end of file, expected `"/utf8, Want/binary>>/binary, "`"/utf8>>, 0, none}} end. -file("src/glsql/parser.gleam", 172). -spec parse_column_constraints( list(glsql@token:positioned()), list(glsql@ast:column_constraint()) ) -> {ok, {list(glsql@ast:column_constraint()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_column_constraints(Tokens, Acc) -> case Tokens of [{positioned, comma, _, _} | _] -> {ok, {Acc, Tokens}}; [{positioned, r_paren, _, _} | _] -> {ok, {Acc, Tokens}}; [] -> {ok, {Acc, Tokens}}; [{positioned, {word, W}, Pos, _} | Rest] -> case string:lowercase(W) of <<"not"/utf8>> -> gleam@result:'try'( keyword(Rest, <<"null"/utf8>>), fun(_use0) -> {_, Rest@1} = _use0, parse_column_constraints( Rest@1, [{not_null, Pos} | Acc] ) end ); <<"null"/utf8>> -> parse_column_constraints(Rest, [{nullable, Pos} | Acc]); <<"primary"/utf8>> -> gleam@result:'try'( keyword(Rest, <<"key"/utf8>>), fun(_use0@1) -> {_, Rest@2} = _use0@1, parse_column_constraints( Rest@2, [{primary_key, Pos} | Acc] ) end ); <<"unique"/utf8>> -> parse_column_constraints(Rest, [{unique, Pos} | Acc]); <<"default"/utf8>> -> {Expr, Rest@3} = take_expression(Rest, <<""/utf8>>, 0), parse_column_constraints( Rest@3, [{default, Expr, Pos} | Acc] ); <<"check"/utf8>> -> {Expr@1, Rest@4} = take_expression(Rest, <<""/utf8>>, 0), parse_column_constraints( Rest@4, [{check, Expr@1, Pos} | Acc] ); <<"references"/utf8>> -> gleam@result:'try'( qualified_name(Rest), fun(_use0@2) -> {_, Tbl, Rest@5} = _use0@2, gleam@result:'try'( optional_column_list(Rest@5), fun(_use0@3) -> {Cols, Rest@6} = _use0@3, parse_column_constraints( Rest@6, [{references, Tbl, Cols, Pos} | Acc] ) end ) end ); Other -> {error, {parse_error, <<<<"Unknown column constraint `"/utf8, W/binary>>/binary, "`"/utf8>>, Pos, glsql@suggest:closest( Other, [<<"not"/utf8>>, <<"null"/utf8>>, <<"primary"/utf8>>, <<"unique"/utf8>>, <<"default"/utf8>>, <<"references"/utf8>>, <<"check"/utf8>>, <<"constraint"/utf8>>, <<"foreign"/utf8>>, <<"generated"/utf8>>, <<"collate"/utf8>>] )}} end; [{positioned, T, Pos@1, _} | _] -> {error, {parse_error, <<<<"Unexpected `"/utf8, (glsql@token:to_string(T))/binary>>/binary, "` in column definition"/utf8>>, Pos@1, none}} end. -file("src/glsql/parser.gleam", 402). -spec parse_array_dims(list(glsql@token:positioned()), integer()) -> {integer(), list(glsql@token:positioned())}. parse_array_dims(Tokens, Count) -> case Tokens of [{positioned, l_bracket, _, _}, {positioned, r_bracket, _, _} | Rest] -> parse_array_dims(Rest, Count + 1); _ -> {Count, Tokens} end. -file("src/glsql/parser.gleam", 385). -spec collect_args(list(glsql@token:positioned()), list(binary())) -> {ok, {list(binary()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. collect_args(Tokens, Acc) -> case Tokens of [{positioned, {number, N}, _, _}, {positioned, comma, _, _} | Rest] -> collect_args(Rest, [N | Acc]); [{positioned, {number, N@1}, _, _}, {positioned, r_paren, _, _} | Rest@1] -> {ok, {lists:reverse([N@1 | Acc]), Rest@1}}; [{positioned, {word, W}, _, _}, {positioned, r_paren, _, _} | Rest@2] -> {ok, {lists:reverse([W | Acc]), Rest@2}}; [{positioned, _, Pos, _} | _] -> {error, {parse_error, <<"Expected a type argument"/utf8>>, Pos, none}}; [] -> {error, {parse_error, <<"Unexpected end of file in type arguments"/utf8>>, 0, none}} end. -file("src/glsql/parser.gleam", 378). -spec parse_type_args(list(glsql@token:positioned())) -> {ok, {list(binary()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_type_args(Tokens) -> case Tokens of [{positioned, l_paren, _, _} | Rest] -> collect_args(Rest, []); _ -> {ok, {[], Tokens}} end. -file("src/glsql/parser.gleam", 371). -spec parse_type(list(glsql@token:positioned())) -> {ok, {glsql@ast:sql_type(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_type(Tokens) -> gleam@result:'try'( identifier(Tokens), fun(_use0) -> {Name, Pos, Rest} = _use0, gleam@result:'try'( parse_type_args(Rest), fun(_use0@1) -> {Args, Rest@1} = _use0@1, {Dims, Rest@2} = parse_array_dims(Rest@1, 0), {ok, {{sql_type, Name, Args, Dims, Pos}, Rest@2}} end ) end ). -file("src/glsql/parser.gleam", 157). -spec parse_column(list(glsql@token:positioned())) -> {ok, {glsql@ast:column(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_column(Tokens) -> gleam@result:'try'( identifier(Tokens), fun(_use0) -> {Name, Pos, Rest} = _use0, gleam@result:'try'( parse_type(Rest), fun(_use0@1) -> {Sql_type, Rest@1} = _use0@1, gleam@result:'try'( parse_column_constraints(Rest@1, []), fun(_use0@2) -> {Constraints, Rest@2} = _use0@2, {ok, {{column, Name, Sql_type, lists:reverse(Constraints), Pos}, Rest@2}} end ) end ) end ). -file("src/glsql/parser.gleam", 286). -spec skip_constraint_name(list(glsql@token:positioned())) -> list(glsql@token:positioned()). skip_constraint_name(Tokens) -> case Tokens of [{positioned, {word, W}, _, _} | Rest] -> case string:lowercase(W) of <<"constraint"/utf8>> -> case Rest of [{positioned, {word, _}, _, _} | Rest@1] -> Rest@1; [{positioned, {quoted_ident, _}, _, _} | Rest@2] -> Rest@2; _ -> Rest end; _ -> Tokens end; _ -> Tokens end. -file("src/glsql/parser.gleam", 275). -spec continue_table_constraints( list(glsql@token:positioned()), list(glsql@ast:table_constraint()) ) -> {ok, {list(glsql@ast:table_constraint()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. continue_table_constraints(Tokens, Acc) -> case Tokens of [{positioned, comma, _, _} | Rest] -> parse_table_constraints(Rest, Acc); _ -> {ok, {lists:reverse(Acc), Tokens}} end. -file("src/glsql/parser.gleam", 226). -spec parse_table_constraints( list(glsql@token:positioned()), list(glsql@ast:table_constraint()) ) -> {ok, {list(glsql@ast:table_constraint()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_table_constraints(Tokens, Acc) -> Tokens@1 = skip_constraint_name(Tokens), case Tokens@1 of [{positioned, {word, W}, Pos, _} | Rest] -> case string:lowercase(W) of <<"primary"/utf8>> -> gleam@result:'try'( keyword(Rest, <<"key"/utf8>>), fun(_use0) -> {_, Rest@1} = _use0, gleam@result:'try'( optional_column_list(Rest@1), fun(_use0@1) -> {Cols, Rest@2} = _use0@1, continue_table_constraints( Rest@2, [{table_primary_key, Cols, Pos} | Acc] ) end ) end ); <<"unique"/utf8>> -> gleam@result:'try'( optional_column_list(Rest), fun(_use0@2) -> {Cols@1, Rest@3} = _use0@2, continue_table_constraints( Rest@3, [{table_unique, Cols@1, Pos} | Acc] ) end ); <<"foreign"/utf8>> -> gleam@result:'try'( keyword(Rest, <<"key"/utf8>>), fun(_use0@3) -> {_, Rest@4} = _use0@3, gleam@result:'try'( optional_column_list(Rest@4), fun(_use0@4) -> {Cols@2, Rest@5} = _use0@4, gleam@result:'try'( keyword(Rest@5, <<"references"/utf8>>), fun(_use0@5) -> {_, Rest@6} = _use0@5, gleam@result:'try'( qualified_name(Rest@6), fun(_use0@6) -> {_, Tbl, Rest@7} = _use0@6, gleam@result:'try'( optional_column_list( Rest@7 ), fun(_use0@7) -> {Ref, Rest@8} = _use0@7, continue_table_constraints( Rest@8, [{table_foreign_key, Cols@2, Tbl, Ref, Pos} | Acc] ) end ) end ) end ) end ) end ); <<"check"/utf8>> -> {Expr, Rest@9} = take_expression(Rest, <<""/utf8>>, 0), continue_table_constraints( Rest@9, [{table_check, Expr, Pos} | Acc] ); Other -> {error, {parse_error, <<<<"Unknown table constraint `"/utf8, W/binary>>/binary, "`"/utf8>>, Pos, glsql@suggest:closest( Other, [<<"not"/utf8>>, <<"null"/utf8>>, <<"primary"/utf8>>, <<"unique"/utf8>>, <<"default"/utf8>>, <<"references"/utf8>>, <<"check"/utf8>>, <<"constraint"/utf8>>, <<"foreign"/utf8>>, <<"generated"/utf8>>, <<"collate"/utf8>>] )}} end; [{positioned, T, Pos@1, _} | _] -> {error, {parse_error, <<<<"Expected a table constraint, found `"/utf8, (glsql@token:to_string(T))/binary>>/binary, "`"/utf8>>, Pos@1, none}}; [] -> {error, {parse_error, <<"Unexpected end of file in table constraints"/utf8>>, 0, none}} end. -file("src/glsql/parser.gleam", 146). -spec is_table_constraint(list(glsql@token:positioned())) -> boolean(). is_table_constraint(Tokens) -> case Tokens of [{positioned, {word, W}, _, _} | _] -> case string:lowercase(W) of <<"primary"/utf8>> -> true; <<"unique"/utf8>> -> true; <<"foreign"/utf8>> -> true; <<"check"/utf8>> -> true; <<"constraint"/utf8>> -> true; _ -> false end; _ -> false end. -file("src/glsql/parser.gleam", 126). -spec parse_column_list( list(glsql@token:positioned()), list(glsql@ast:column()) ) -> {ok, {list(glsql@ast:column()), list(glsql@ast:table_constraint()), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_column_list(Tokens, Acc) -> case is_table_constraint(Tokens) of true -> gleam@result:'try'( parse_table_constraints(Tokens, []), fun(_use0) -> {Constraints, Rest} = _use0, {ok, {Acc, Constraints, Rest}} end ); false -> gleam@result:'try'( parse_column(Tokens), fun(_use0@1) -> {Column, Rest@1} = _use0@1, case Rest@1 of [{positioned, comma, _, _} | Rest@2] -> parse_column_list(Rest@2, [Column | Acc]); _ -> {ok, {[Column | Acc], [], Rest@1}} end end ) end. -file("src/glsql/parser.gleam", 413). -spec skip_if_not_exists(list(glsql@token:positioned())) -> list(glsql@token:positioned()). skip_if_not_exists(Tokens) -> case Tokens of [{positioned, {word, A}, _, _}, {positioned, {word, B}, _, _}, {positioned, {word, C}, _, _} | Rest] -> case {string:lowercase(A), string:lowercase(B), string:lowercase(C)} of {<<"if"/utf8>>, <<"not"/utf8>>, <<"exists"/utf8>>} -> Rest; {_, _, _} -> Tokens end; _ -> Tokens end. -file("src/glsql/parser.gleam", 106). -spec parse_create_table(list(glsql@token:positioned())) -> {ok, {glsql@ast:table(), list(glsql@token:positioned())}} | {error, glsql@error:error()}. parse_create_table(Tokens) -> gleam@result:'try'( keyword(Tokens, <<"create"/utf8>>), fun(_use0) -> {Pos, Rest} = _use0, gleam@result:'try'( keyword(Rest, <<"table"/utf8>>), fun(_use0@1) -> {_, Rest@1} = _use0@1, Rest@2 = skip_if_not_exists(Rest@1), gleam@result:'try'( qualified_name(Rest@2), fun(_use0@2) -> {Schema, Name, Rest@3} = _use0@2, gleam@result:'try'( expect(Rest@3, l_paren, <<"("/utf8>>), fun(_use0@3) -> {_, Rest@4} = _use0@3, gleam@result:'try'( parse_column_list(Rest@4, []), fun(_use0@4) -> {Columns, Constraints, Rest@5} = _use0@4, gleam@result:'try'( expect( Rest@5, r_paren, <<")"/utf8>> ), fun(_use0@5) -> {_, Rest@6} = _use0@5, {ok, {{table, Name, Schema, lists:reverse( Columns ), Constraints, Pos}, Rest@6}} end ) end ) end ) end ) end ) end ). -file("src/glsql/parser.gleam", 39). -spec parse_statements(list(glsql@token:positioned()), list(glsql@ast:table())) -> {ok, list(glsql@ast:table())} | {error, glsql@error:error()}. parse_statements(Tokens, Acc) -> case Tokens of [] -> {ok, Acc}; [{positioned, semicolon, _, _} | Rest] -> parse_statements(Rest, Acc); [{positioned, {word, W}, Pos, _} | Rest@1] -> case string:lowercase(W) of <<"create"/utf8>> -> case Rest@1 of [{positioned, {word, Obj}, Obj_pos, _} | After] -> case string:lowercase(Obj) of <<"table"/utf8>> -> gleam@result:'try'( parse_create_table(Tokens), fun(_use0) -> {Table, Rest@2} = _use0, parse_statements( Rest@2, [Table | Acc] ) end ); Other -> case gleam@list:contains( [<<"index"/utf8>>, <<"unique"/utf8>>, <<"extension"/utf8>>, <<"sequence"/utf8>>, <<"view"/utf8>>, <<"schema"/utf8>>, <<"function"/utf8>>, <<"trigger"/utf8>>, <<"type"/utf8>>, <<"domain"/utf8>>, <<"materialized"/utf8>>, <<"publication"/utf8>>, <<"policy"/utf8>>, <<"aggregate"/utf8>>, <<"operator"/utf8>>, <<"role"/utf8>>, <<"database"/utf8>>], Other ) of true -> parse_statements( skip_statement(After), Acc ); false -> {error, {unknown_statement, Obj, Obj_pos, glsql@suggest:closest( Other, [<<"table"/utf8>> | [<<"index"/utf8>>, <<"unique"/utf8>>, <<"extension"/utf8>>, <<"sequence"/utf8>>, <<"view"/utf8>>, <<"schema"/utf8>>, <<"function"/utf8>>, <<"trigger"/utf8>>, <<"type"/utf8>>, <<"domain"/utf8>>, <<"materialized"/utf8>>, <<"publication"/utf8>>, <<"policy"/utf8>>, <<"aggregate"/utf8>>, <<"operator"/utf8>>, <<"role"/utf8>>, <<"database"/utf8>>]] )}} end end; _ -> {error, {parse_error, <<"Unexpected end of file after `create`"/utf8>>, Pos, none}} end; Other@1 -> case gleam@list:contains( [<<"alter"/utf8>>, <<"drop"/utf8>>, <<"set"/utf8>>, <<"comment"/utf8>>, <<"grant"/utf8>>, <<"revoke"/utf8>>, <<"begin"/utf8>>, <<"commit"/utf8>>, <<"insert"/utf8>>, <<"update"/utf8>>, <<"delete"/utf8>>, <<"select"/utf8>>, <<"truncate"/utf8>>, <<"analyze"/utf8>>, <<"vacuum"/utf8>>, <<"refresh"/utf8>>, <<"reset"/utf8>>, <<"with"/utf8>>], Other@1 ) of true -> parse_statements(skip_statement(Rest@1), Acc); false -> {error, {unknown_statement, W, Pos, glsql@suggest:closest( Other@1, [<<"create"/utf8>>, <<"alter"/utf8>>, <<"drop"/utf8>>, <<"set"/utf8>>, <<"comment"/utf8>>] )}} end end; [{positioned, T, Pos@1, _} | _] -> {error, {parse_error, <<<<"Expected a statement, found `"/utf8, (glsql@token:to_string(T))/binary>>/binary, "`"/utf8>>, Pos@1, none}} end. -file("src/glsql/parser.gleam", 34). -spec parse(list(glsql@token:positioned())) -> {ok, glsql@ast:schema_ast()} | {error, glsql@error:error()}. parse(Tokens) -> gleam@result:'try'( parse_statements(Tokens, []), fun(Tables) -> {ok, {schema_ast, lists:reverse(Tables)}} end ).