lib/aptos/smart_contract_parser.ex

defmodule Web3AptosEx.Aptos.SmartContractParser do
    import NimbleParsec
    import NimbleJson.Parser.Helper


    @doc """
    TODO: Parse the Move Smart Contract.
    """

    text = ascii_string([not: ?}], min: 1)

    defparsecp(
        :object,
        ignore(string("{"))
        |> ignore(white_space())
        |> concat(text)
        |> ignore(white_space())
        |> ignore(string("}"))
        |> unwrap_and_tag(:object)
      )

    # defparsec :parse, parsec(:node) |> eos()
    defparsec(
        :parse,
        parsec(:object)
        # choice([parsec(:object), parsec(:list)])
    )

    tag = ascii_string([?a..?z, ?A..?Z], min: 1)



    opening_tag = ignore(string("<")) |> concat(tag) |> ignore(string(">"))
    closing_tag = ignore(string("</")) |> concat(tag) |> ignore(string(">"))

    defcombinatorp :node,
                    opening_tag
                    |> repeat(lookahead_not(string("</")) |> choice([parsec(:node), text]))
                    |> wrap()
                    |> concat(closing_tag)
                    |> post_traverse(:match_and_emit_tag)

    defp match_and_emit_tag(_rest, [tag, [tag, text]], context, _line, _offset),
    do: {[{String.to_atom(tag), [], text}], context}

    defp match_and_emit_tag(_rest, [tag, [tag | nodes]], context, _line, _offset),
    do: {[{String.to_atom(tag), [], nodes}], context}

    defp match_and_emit_tag(_rest, [opening, [closing | _]], _context, _line, _offset),
    do: {:error, "closing tag #{inspect(closing)} did not match opening tag #{inspect(opening)}"}

    @doc """
        module hello_blockchain::message {
    use std::error;
    use std::signer;
    use std::string;
    use aptos_framework::account;
    use aptos_framework::event;

//:!:>resource
    struct MessageHolder has key {
        message: string::String,
        message_change_events: event::EventHandle<MessageChangeEvent>,
    }
//<:!:resource

    struct MessageChangeEvent has drop, store {
        from_message: string::String,
        to_message: string::String,
    }

    /// There is no message present
    const ENO_MESSAGE: u64 = 0;

    #[view]
    public fun get_message(addr: address): string::String acquires MessageHolder {
        assert!(exists<MessageHolder>(addr), error::not_found(ENO_MESSAGE));
        borrow_global<MessageHolder>(addr).message
    }

    public entry fun set_message(account: signer, message: string::String)
    acquires MessageHolder {
        let account_addr = signer::address_of(&account);
        if (!exists<MessageHolder>(account_addr)) {
            move_to(&account, MessageHolder {
                message,
                message_change_events: account::new_event_handle<MessageChangeEvent>(&account),
            })
        } else {
            let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
            let from_message = old_message_holder.message;
            event::emit_event(&mut old_message_holder.message_change_events, MessageChangeEvent {
                from_message,
                to_message: copy message,
            });
            old_message_holder.message = message;
        }
    }

    #[test(account = @0x1)]
    public entry fun sender_can_set_message(account: signer) acquires MessageHolder {
        let addr = signer::address_of(&account);
        aptos_framework::account::create_account_for_test(addr);
        set_message(account,  string::utf8(b"Hello, Blockchain"));

        assert!(
          get_message(addr) == string::utf8(b"Hello, Blockchain"),
          ENO_MESSAGE
        );
    }
}
    """
    def parse_code(contract_code) do
        {:ok, tokens, _} = :smart_move_leex.string(String.to_charlist(contract_code))
        :smart_move_yecc.parse(tokens)
    end
    def parse_struct(contract_code) do
        do_parse(:struct, contract_code)
    end
    def parse_event(contract_code) do
         do_parse(:event, contract_code)
    end
    def parse_fun(contract_code) do
        do_parse(:function, contract_code)
    end
    defp do_parse(name, conttrace_code) do
        {:ok, l} = parse_code(conttrace_code)
        l |> Enum.filter(&(match(name,&1)))
          |> Enum.map(&(add_new_line(:erlang.element(2, &1))))
    end
    defp match(name,{name, _}) do
         true
    end
    defp match(_, _) do
         false
    end
    defp add_new_line(lines) do
        :erlang.list_to_binary(lines |> Enum.map(&("#{&1}\n")))
    end
end