%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
%-----------------------------------------------------------------------------%
%
% Emulation of Prolog's expand_term/term_expansion mechanism.
% This program provides pre-processing of Mercury programs,
% using an arbitrary term-to-term translation given by the
% `term_expansion' predicate.
%
% To use, copy this file to the directory containing your source code, and
% modify the `term_expansion' predicate at the end of this file to provide your
% own term expansion.  Then add
%
%   *.m: expand_terms
%   %.m: %.m.in
%       expand_terms $*.m.in > $*.m
%
% to your Mmake file, and rename your `.m' files with the suffix `.m.in'.
%
% This source file is hereby placed in the public domain.  -fjh (the author).
%
%-----------------------------------------------------------------------------%

:- module expand_terms.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module list.
:- import_module string.
:- import_module mercury_term_parser.
:- import_module term.
:- import_module term_io.
:- import_module varset.

%-----------------------------------------------------------------------------%

main(!IO) :-
    io.command_line_arguments(Args, !IO),
    (
        Args = [],
        expand_terms(!IO)
    ;
        Args = [_ | _],
        expand_terms_file_list(Args, !IO)
    ).

:- pred expand_terms_file_list(list(string)::in, io::di, io::uo) is det.

expand_terms_file_list([], !IO).
expand_terms_file_list([File | Files], !IO) :-
    expand_terms_file(File, !IO),
    expand_terms_file_list(Files, !IO).

:- pred expand_terms_file(string::in, io::di, io::uo) is det.

expand_terms_file(File, !IO) :-
    io.open_input(File, Result, !IO),
    (
        Result = ok(Stream),
        expand_terms_stream(Stream, !IO)
    ;
        Result = error(Error),
        io.progname("expand_terms", Progname, !IO),
        io.error_message(Error, Message),
        io.write_strings([
            Progname, ": ",
            "error opening file `", File, "' for input:\n\t",
            Message, "\n"
        ], !IO),
        io.set_exit_status(1, !IO)
    ).

:- pred expand_terms_stream(io.input_stream::in, io::di, io::uo) is det.

expand_terms_stream(Stream, !IO) :-
    io.set_input_stream(Stream, _OldStream, !IO),
    expand_terms(!IO).

:- pred expand_terms(io::di, io::uo) is det.

expand_terms(!IO) :-
    mercury_term_parser.read_term(Result, !IO),
    expand_terms_2(Result, !IO).

:- pred expand_terms_2(read_term::in, io::di, io::uo) is det.

expand_terms_2(Result, !IO) :-
    (
        Result = term(VarSet0, Term0),
        expand_term(Term0, VarSet0, Term, VarSet),
        term_io.write_term(VarSet, Term, !IO),
        io.write_string(".\n", !IO),
        mercury_term_parser.read_term(NextResult, !IO),
        expand_terms_2(NextResult, !IO)
    ;
        Result = eof
    ;
        Result = error(Message, LineNum),
        io.input_stream_name(StreamName, !IO),
        string.format("%s:%03d: %s\n", [s(StreamName), i(LineNum),
            s(Message)], FullMessage),
        io.write_string(FullMessage, !IO),
        io.set_exit_status(1, !IO)
    ).

%-----------------------------------------------------------------------------%

:- pred expand_term(term::in, varset::in, term::out, varset::out) is det.

expand_term(Term0, VarSet0, Term, VarSet) :-
    ( if term_expansion(Term0, VarSet0, Term1, VarSet1) then
        Term = Term1,
        VarSet = VarSet1
    else
        Term = Term0,
        VarSet = VarSet0
    ).

%-----------------------------------------------------------------------------%

% Insert your clauses for term_expansion here.
% As a trivial example, here is one which replaces
% `A <=> B' with `A :- B'.

:- pred term_expansion(term::in, varset::in, term::out, varset::out)
    is semidet.

term_expansion(Term0, VarSet, Term, VarSet) :-
    Term0 = term.functor(term.atom("<=>"), [A, B], Context),
    Term = term.functor(term.atom(":-"), [A, B], Context).

%-----------------------------------------------------------------------------%
:- end_module expand_terms.
%-----------------------------------------------------------------------------%
