%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, aeternity Anstalt
%%% @doc
%%%     Handling FATE code.
%%% @end
%%% ###REPLACEWITHNOTE###
%%%-------------------------------------------------------------------

Definitions.
DIGIT    = [0-9]
HEXDIGIT = [0-9a-fA-F]
LOWER    = [a-z_]
UPPER    = [A-Z]
BASE58   = [123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]
BASE64   = [A-Za-z0-9+/=]
INT      = {DIGIT}+
HEX      = 0x{HEXDIGIT}+
OBJECT   = @[a-z][a-z]_{BASE58}+
BYTES    = #{BASE64}+
WS       = [\000-\s]
ID       = {LOWER}[a-zA-Z0-9_]*
STRING   = "[^"]*"
BITS     = (\!)?\<[\s01]*\>

Rules.
arg{INT}       : {token, {arg, TokenLine, parse_arg(TokenChars)}}.
var{INT}       : {token, {var, TokenLine, parse_var(TokenChars)}}.
a              : {token, {stack, TokenLine}}.

true           : {token, {boolean, TokenLine, true}}.
false          : {token, {boolean, TokenLine, false}}.

%% ###REPLACEWITHOPTOKENS###

FUNCTION       : {token, {function, TokenLine, 'FUNCTION' }}.

{BYTES} :
 {token, {bytes, TokenLine, parse_hash(TokenChars)}}.
{OBJECT} :
 {token, {object, TokenLine, parse_object(TokenChars)}}.
{ID} :
 {token, {id, TokenLine, TokenChars}}.
{HEX} :
 {token, {int, TokenLine, parse_hex(TokenChars)}}.
{INT} :
 {token, {int, TokenLine, parse_int(TokenChars)}}.
-{INT} :
 {token, {int, TokenLine, parse_int(TokenChars)}}.

%% Due to the definition of STRING the tokens start and end with a quote ".
{STRING} :
 {token, {string, TokenLine, unicode:characters_to_binary(
                lists:sublist(TokenChars, 2, length(TokenChars) - 2))}}.
{BITS} :
 {token, {bits, TokenLine, bits(TokenChars)}}.


%% Symbols
\-\>  : {token, {to, TokenLine}}.
\:    : {token, {to, TokenLine}}.

\=\>  : {token, {arrow, TokenLine}}.
\(\|  : {token, {start_variant, TokenLine}}.
\|\)  : {token, {end_variant, TokenLine}}.

,   : {token, {',', TokenLine}}.
\(  : {token, {'(', TokenLine}}.
\)  : {token, {')', TokenLine}}.
\[  : {token, {'[', TokenLine}}.
\]  : {token, {']', TokenLine}}.
\{  : {token, {'{', TokenLine}}.
\}  : {token, {'}', TokenLine}}.
\|  : {token, {'|', TokenLine}}.
\'  : {token, {typerep, TokenLine}}.

;;.* :
 {token, {comment, TokenLine, drop_prefix($;, TokenChars)}}.

\.  : skip_token.


%% Whitespace ignore
{WS} : skip_token.

%% Comments (TODO: nested comments)


. : {error, "Unexpected token: " ++ TokenChars}.

Erlang code.

-export([scan/1]).

-dialyzer({nowarn_function, yyrev/2}).

-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).

-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").


parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).

parse_int(Chars) -> list_to_integer(Chars).

parse_arg("arg" ++ N) -> list_to_integer(N).
parse_var("var" ++ N) -> list_to_integer(N).



parse_hash("#" ++ Chars) ->
    base64:decode(Chars).

parse_object([_|Chars]) ->
    case aeser_api_encoder:decode(unicode:characters_to_binary(Chars)) of
       {account_pubkey, Bin}  -> {address, Bin};
       {contract_pubkey, Bin} -> {contract, Bin};
       {oracle_pubkey, Bin}   -> {oracle, Bin};
       {oracle_query_id, Bin} -> {oracle_query, Bin};
       {channel, Bin}         -> {channel, Bin};
       {signature, Bin}       -> {signature, Bin}
    end.

scan(S) ->
    string(S).

drop_prefix(C, [C|Rest]) ->
    drop_prefix(C, Rest);
drop_prefix(_, Tail) -> Tail.

bits([$!, $< | Rest]) ->
    bits(Rest, -1);
bits([$< | Rest]) ->
    bits(Rest, 0).

bits([$> |_Rest], Acc) -> Acc;
bits([$0 | Rest], Acc) -> bits(Rest, Acc bsl 1);
bits([$1 | Rest], Acc) -> bits(Rest, (Acc bsl 1) bor 1);
bits([$  | Rest], Acc) -> bits(Rest, Acc).
