%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Sophia abstract syntax types.
%%%
%%% @end
%%%-------------------------------------------------------------------

-module(so_syntax).
-vsn("9.0.0").

-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).

-export_type([ann_file/0, ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]).

-type ast() :: [decl()].

-type ann_line()   :: integer().
-type ann_col()    :: integer().
-type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann_file()   :: string() | no_file.

-type ann() :: [ {file, ann_file()} | {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
               | stateful | private | payable | main | interface | entrypoint].

-type name() :: string().
-type id()   :: {id,   ann(), name()}.
-type con()  :: {con,  ann(), name()}.
-type qid()  :: {qid,  ann(), [name()]}.
-type qcon() :: {qcon, ann(), [name()]}.
-type tvar() :: {tvar, ann(), name()}.

-type namespace_alias() :: none | con().
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.

-type decl() :: {contract_main, ann(), con(), [con()], [decl()]}
              | {contract_child, ann(), con(), [con()], [decl()]}
              | {contract_interface, ann(), con(), [con()], [decl()]}
              | {namespace, ann(), con(), [decl()]}
              | {include, ann(), {string, ann(), string()}}
              | {pragma, ann(), pragma()}
              | {type_decl, ann(), id(), [tvar()]} % Only for error msgs
              | {type_def, ann(), id(), [tvar()], typedef()}
              | {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
              | {block, ann(), [decl()]}
              | {using, ann(), con(), namespace_alias(), namespace_parts()}
              | fundecl()
              | letfun()
              | letval(). % Only for error msgs

-type compiler_version() :: [non_neg_integer()].

-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.

-type guard() :: expr().
-type guarded_expr() :: {guarded, ann(), [guard()], expr()}.

-type letval()  :: {letval, ann(), pat(), expr()}.
-type letfun()  :: {letfun, ann(), id(), [pat()], type(), [guarded_expr(),...]}.
-type letpat()  :: {letpat, ann(), id(), pat()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}.

-type letbind()
    :: letfun()
     | letval().

-type arg() :: {arg, ann(), id(), type()}.

-type typedef()
    :: {alias_t, type()}
     | {record_t, [field_t()]}
     | {variant_t, [constructor_t()]}.

-type field_t() :: {field_t, ann(), id(), type()}.

-type constructor_t() :: {constr_t, ann(), con(), [type()]}.

-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
              | {app_t, ann(), type(), [type()]}
              | {tuple_t, ann(), [type()]}
              | {args_t, ann(), [type()]}   %% old tuple syntax, old for error messages
              | {bytes_t, ann(), integer() | any}
              | id()  | qid()
              | con() | qcon()  %% contracts
              | tvar().

-type named_arg_t() :: {named_arg_t, ann(), id(), type(), expr()}.

-type constant()
    :: {int, ann(), integer()}
     | {bool, ann(), true | false}
     | {bytes, ann(), binary()}
     | {account_pubkey, ann(), binary()}
     | {contract_pubkey, ann(), binary()}
     | {oracle_pubkey, ann(), binary()}
     | {oracle_query_id, ann(), binary()}
     | {signature, ann(), binary()}
     | {string, ann(), binary()}
     | {char, ann(), integer()}.

-type op() :: bin_op() | un_op().

-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
                | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
                | '||' | '&&' | '..' | 'band' | 'bor' | 'bxor' | '>>' | '<<' | '|>'.
-type un_op() :: '-' | '!' | 'bnot'.

-type expr()
    :: {lam, ann(), [arg()], expr()}
     | {'if', ann(), expr(), expr(), expr()}
     | {switch, ann(), expr(), [alt()]}
     | {app, ann(), expr(), [arg_expr()]}
     | {proj, ann(), expr(), id()}
     | {tuple, ann(), [expr()]}
     | {list, ann(), [expr()]}
     | {list_comp, ann(), expr(), [comprehension_exp()]}
     | {typed, ann(), expr(), type()}
     | {record_or_map(), ann(), [field(expr())]}
     | {record_or_map(), ann(), expr(), [field(expr())]} %% record/map update
     | {map, ann(), [{expr(), expr()}]}
     | {map_get, ann(), expr(), expr()}
     | {map_get, ann(), expr(), expr(), expr()}
     | {block, ann(), [stmt()]}
     | {op(), ann()}
     | id() | qid() | con() | qcon()
     | constant()
     | letpat().

-type record_or_map() :: record | map | record_or_map_error.

-type comprehension_exp() :: [ {comprehension_bind, pat(), expr()}
                             | {comprehension_if, ann(), expr()}
                             | letbind() ].

-type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}.

%% When lvalue is a projection this is sugar for accessing fields in nested
%% records. For instance,
%%    r { x.y: 5 }
%% is the same as
%%    r { x: r.x { y: 5 } }
-type field(E) :: {field, ann(), lvalue(), E}
                | {field, ann(), lvalue(), id(), E}.  %% modifying a field (id is previous value)

-type stmt() :: letbind()
              | expr().

-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}.

-type lvalue() :: nonempty_list(elim()).

-type elim() :: {proj, ann(), id()}
              | {map_get, ann(), expr()}
              | {map_get, ann(), expr(), expr()}.

-type pat() :: {app, ann(), con() | op(), [pat()]}
             | {tuple, ann(), [pat()]}
             | {list, ann(), [pat()]}
             | {typed, ann(), pat(), type()}
             | {record, ann(), [field(pat())]}
             | letpat()
             | constant()
             | con()
             | id().

get_ann(Node) when is_tuple(Node) -> element(2, Node);
get_ann(Ann)  when is_list(Ann)   -> Ann.

set_ann(Ann1, Node) when is_tuple(Node) -> setelement(2, Node, Ann1);
set_ann(Ann1, Ann) when is_list(Ann) -> Ann1.

get_ann(Key, Node) ->
    proplists:get_value(Key, get_ann(Node)).

get_ann(Key, Node, Default) ->
    proplists:get_value(Key, get_ann(Node), Default).

qualify({con, Ann, N}, X)             -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X})  -> {qid, Ann, NS ++ [X]}.
