1% * -*- Mode: Prolog -*- */
    2
    3:- module(utils,
    4          [
    5	   get_opt/3,
    6	   halt_success/0,
    7	   halt_error/0,
    8           show_type/1,
    9           type_of/2,
   10	   string_from_codes/4,
   11	   atom_from_codes/4,
   12	   code_list/4,
   13	   string_from_chars/4,
   14	   atom_from_chars/4,
   15	   char_list/4,
   16	   whitespace/2,
   17	   opt_whitespace/2,
   18	   space/2,
   19	   opt_space/2,
   20	   blank_line/2,
   21	   alphanum_char/3,
   22	   alphanum_code/3,
   23	   parse_num_char/3,
   24	   parse_num_code/3,
   25	   n_chars/3,
   26	   concat_string_list/2,
   27	   concat_string_list/3,
   28	   concat_string_list_spaced/2,
   29	   split_spaces/2,
   30	   split_newlines/2,
   31	   last_element/2,
   32	   nth_element/3,
   33	   slice/4,
   34	   find_on_path/2,
   35	   shell_path/1,
   36	   shell_quote/2,
   37	   shell_wrap/2,
   38	   shell_comment/2,
   39	   shell_eval/2,
   40	   shell_eval_str/2,
   41	   echo_wrap/2,
   42	   shell_echo_wrap/2,
   43	   file_directory_slash/2,
   44	   newlines_to_spaces/2,
   45	   to_string/2,
   46	   equal_as_strings/2,
   47	   makefile_var_char/3,
   48	   makefile_var_chars/3,
   49	   makefile_var_atom_from_chars/3,
   50	   makefile_var_string_from_chars/3,
   51	   makefile_var_code/3,
   52	   makefile_var_codes/3,
   53	   makefile_var_atom_from_codes/3,
   54	   makefile_var_string_from_codes/3,
   55	   biomake_private_filename/3,
   56	   biomake_private_filename_dir_exists/3,
   57	   open_biomake_private_file/4,
   58	   open_biomake_private_file/5
   59	  ]).   60
   61get_opt(Name,Val,Opts) :-
   62    Template =.. [Name,Val],
   63    member(Template,Opts),
   64    !.
   65
   66halt_success :- halt(0).
   67halt_error :- halt(2).
   68
   69string_from_codes(S,XS) --> {string_codes(XS,XL)}, code_list(C,XL), {C\=[], string_codes(S,C)}.
   70atom_from_codes(S,XS) --> {string_codes(XS,XL)}, code_list(C,XL), {C\=[], atom_codes(S,C)}.
   71
   72code_list([C|Cs],XL) --> [0'\\,C], {C\=0'\n,member(C,XL)}, !, code_list(Cs,XL).  % allow escapes here, but not split lines
   73code_list([C|Cs],XL) --> [C], {C\=0'\n,forall(member(X,XL),C\=X)}, code_list(Cs,XL).
   74code_list([],_) --> [].
   75
   76string_from_chars(S,XS) --> {string_chars(XS,XL)}, char_list(C,XL), {C\=[], string_chars(S,C)}.
   77atom_from_chars(S,XS) --> {string_chars(XS,XL)}, char_list(C,XL), {C\=[], atom_chars(S,C)}.
   78
   79char_list([C|Cs],XL) --> ['\\',C], {C\='\n',member(C,XL)}, char_list(Cs,XL).  % allow escapes here, but not split lines
   80char_list([C|Cs],XL) --> [C], {C\='\n',forall(member(X,XL),C\=X)}, char_list(Cs,XL).
   81char_list([],_) --> [].
   82
   83whitespace --> "\\\n", !, opt_whitespace.   % bug: this breaks line-number counting
   84whitespace --> " ", !, opt_whitespace.
   85whitespace --> "\t", !, opt_whitespace.
   86
   87opt_whitespace --> whitespace.
   88opt_whitespace --> !.
   89
   90space --> "\\\n", !, opt_space.   % bug: this breaks line-number counting
   91space --> " ", !, opt_space.
   92
   93opt_space --> space.
   94opt_space --> !.
   95
   96blank_line --> "\n", !.
   97blank_line --> space, opt_whitespace, "\n", !.
   98
   99alphanum_char(X) --> [X],{X@>='A',X@=<'Z'},!.
  100alphanum_char(X) --> [X],{X@>='a',X@=<'z'},!.
  101alphanum_char(X) --> parse_num_char(X),!.
  102parse_num_char(X) --> [X],{X@>='0',X@=<'9'}.
  103
  104alphanum_code(X) --> [X],{X@>=0'A,X@=<0'Z},!.  % A through Z
  105alphanum_code(X) --> [X],{X@>=0'a,X@=<0'z},!.  % a through z
  106alphanum_code(X) --> parse_num_code(X),!.
  107parse_num_code(X) --> [X],{X@>=0'0,X@=<0'9}.  % 0 through 9
  108
  109n_chars(N,_,[]) :- N =< 0, !.
  110n_chars(N,C,[C|Ls]) :- Ndec is N - 1, n_chars(Ndec,C,Ls), !.
  111
  112concat_string_list_spaced(L,S) :- concat_string_list(L,S," ").
  113concat_string_list(L,S) :- concat_string_list(L,S,"").
  114concat_string_list([],"",_).
  115concat_string_list([S],S,_).
  116concat_string_list([L|Ls],F,Sep) :- concat_string_list(Ls,R,Sep), string_concat(L,Sep,Lsep), string_concat(Lsep,R,F).
  117
  118split_spaces(S,L) :-
  119	split_string(S," \t"," \t",L).
  120
  121split_newlines(S,L) :-
  122        string_codes(S,C),
  123	phrase(split_unescaped_newlines(A),C),
  124	maplist(string_codes,L,A).
  125
  126split_unescaped_newlines([['\\','\n'|Cs]|L]) --> ['\\','\n'], !, split_unescaped_newlines([Cs|L]).
  127split_unescaped_newlines([[]|L]) --> ['\n'], !, split_unescaped_newlines(L).
  128split_unescaped_newlines([[C|Cs]|L]) --> [C], !, split_unescaped_newlines([Cs|L]).
  129split_unescaped_newlines([[]]) --> [].
  130
  131last_element([],"").
  132last_element([X],X).
  133last_element([_|Ls],X) :- last_element(Ls,X).
  134
  135nth_element(_,[],"").
  136nth_element(1,[X|_],X).
  137nth_element(N,[_|Ls],X) :- Np is N - 1, nth_element(Np,Ls,X).
  138
  139slice(_S,_E,[],[]).
  140slice(1,E,[L|Ls],[L|Rs]) :- E > 0, En is E - 1, slice(1,En,Ls,Rs).
  141slice(S,E,[_L|Ls],R) :- Sn is S - 1, En is E - 1, slice(Sn,En,Ls,R).
  142
  143show_type(X) :- type_of(X,T), format("Type of ~w is ~w.~n",[X,T]).
  144type_of(X,"var") :- var(X), !.
  145type_of(X,"integer") :- integer(X), !.
  146type_of(X,"float") :- float(X), !.
  147type_of(X,"rational") :- rational(X), !.
  148type_of(X,"number") :- number(X), !.  % should never be reached
  149type_of(X,"string") :- string(X), !.
  150type_of(X,"compound") :- compound(X), !.
  151type_of(X,"atom") :- atom(X), !.
  152type_of(_,"unknown").
  153
  154find_on_path(Exec,Path) :-
  155	expand_file_search_path(path(Exec),Path),
  156	exists_file(Path),
  157	!.
  158
  159shell_path(Path) :- find_on_path(sh,Path).
  160
  161shell_wrap(Exec,ShellExec) :-
  162	string_chars(Exec,['@'|SilentChars]),
  163	!,
  164	string_chars(SilentExec,SilentChars),
  165	shell_wrap(SilentExec,ShellExec).
  166
  167shell_wrap(Exec,ShellExec) :-
  168	shell_path(Sh),
  169	!,
  170	shell_quote(Exec,Escaped),
  171	format(string(ShellExec),"~w -c ~w",[Sh,Escaped]).
  172
  173suppress_errors(Exec,SExec) :-
  174	string_chars(Exec,['-'|RealExecChars]),
  175	string_chars(RealExec,RealExecChars),
  176	format(string(SExec),'(~w) || true',[RealExec]).
  177
  178echo_wrap(Exec,Result) :-
  179        suppress_errors(Exec,SExec),
  180        !,
  181	echo_wrap(SExec,Result).
  182	
  183echo_wrap(Exec,Result) :-
  184	string_chars(Exec,['@'|SilentChars]),
  185	!,
  186	string_chars(Result,SilentChars).
  187
  188echo_wrap(Exec,Result) :-
  189        shell_quote(Exec,Escaped),
  190        format(string(Result),"echo ~w; ~w",[Escaped,Exec]).
  191
  192shell_echo_wrap(Exec,Result) :-
  193        suppress_errors(Exec,SExec),
  194        !,
  195	shell_echo_wrap(SExec,Result).
  196
  197shell_echo_wrap(Exec,Result) :-
  198	string_chars(Exec,['@'|SilentChars]),
  199	!,
  200	string_chars(SilentChars,SilentExec),
  201	shell_wrap(SilentExec,Result).
  202
  203shell_echo_wrap(Exec,Result) :-
  204        echo_wrap(Exec,EchoExec),
  205	shell_wrap(EchoExec,Result).
  206
  207shell_comment(Comment,ShellComment) :-
  208	format(string(ShellComment),"# ~w",[Comment]).
  209
  210shell_eval(Exec,CodeList) :-
  211	shell_path(Sh),
  212	working_directory(CWD,CWD),
  213        setup_call_cleanup(process_create(Sh,['-c',Exec],[stdout(pipe(Stream)),
  214							  stderr(pipe(ErrStream)),
  215							  cwd(CWD),
  216							  process(Pid)]),
  217			   (read_stream_to_codes(Stream,CodeList),
  218			    process_wait(Pid,Status)),
  219			   ((Status = exit(0)
  220			     -> true
  221			     ; (read_string(ErrStream,_,Err),
  222				format("biomake: ~w~n",[Err]))),
  223			    close(ErrStream),
  224			    close(Stream))).
  225
  226shell_eval_str(Exec,Result) :-
  227        shell_eval(Exec,Rnl),
  228	chomp(Rnl,Rchomp),
  229	newlines_to_spaces(Rchomp,Rspc),
  230	string_codes(Result,Rspc).
  231
  232shell_quote(S,QS) :-
  233        string_chars(S,Cs),
  234        phrase(escape_quotes(ECs),Cs),
  235        append(['\''|ECs],['\''],QCs),
  236        string_chars(QS,QCs).
  237
  238escape_quotes([]) --> [].
  239escape_quotes(['\'','"','\'','"','\''|Cs]) --> ['\''], !, escape_quotes(Cs).  % ' --> '"'"'
  240escape_quotes([C|Cs]) --> [C], !, escape_quotes(Cs).
  241
  242chomp([],_) :- !.
  243chomp([0'\n],_) :- !.
  244chomp([C|In],[C|Out]) :- chomp(In,Out).
  245
  246newlines_to_spaces([],[]).
  247newlines_to_spaces([0'\n|N],[0'\s|S]) :- newlines_to_spaces(N,S).
  248newlines_to_spaces([C|N],[C|S]) :- newlines_to_spaces(N,S).
  249
  250file_directory_slash(Path,Result) :-
  251	file_directory_name(Path,D),
  252	string_concat(D,"/",Result).  % GNU make adds the trailing '/'
  253
  254to_string(A,S) :- atomics_to_string([A],S).
  255equal_as_strings(X,Y) :-
  256	to_string(X,S),
  257	to_string(Y,S).
  258
  259
  260% We allow only a restricted subset of characters in variable names,
  261% compared to the GNU make specification.
  262% (seriously, does anyone use makefile variable names containing brackets, commas, colons, etc?)
  263makefile_var_char(C) --> alphanum_char(C).
  264makefile_var_char('_') --> ['_'].
  265makefile_var_char('-') --> ['-'].
  266
  267makefile_var_chars([]) --> [].
  268makefile_var_chars([C|Cs]) --> makefile_var_char(C), makefile_var_chars(Cs).
  269
  270makefile_var_atom_from_chars(A) --> makefile_var_chars(Cs), {atom_chars(A,Cs)}.
  271makefile_var_string_from_chars(S) --> makefile_var_chars(Cs), {string_chars(S,Cs)}.
  272
  273% define these again as character codes, because Prolog is so annoying
  274makefile_var_code(C) --> alphanum_code(C).
  275makefile_var_code(95) --> [95].  % underscore '_'
  276makefile_var_code(45) --> [45].  % hyphen '-'
  277
  278makefile_var_codes([]) --> [].
  279makefile_var_codes([C|Cs]) --> makefile_var_code(C), makefile_var_codes(Cs).
  280
  281makefile_var_atom_from_codes(A) --> makefile_var_codes(Cs), {atom_codes(A,Cs)}.
  282makefile_var_string_from_codes(S) --> makefile_var_codes(Cs), {string_codes(S,Cs)}.
  283
  284biomake_private_subdir_list(Target,Subdirs,[D,".biomake"|Subdirs]) :-
  285	absolute_file_name(Target,F),
  286	file_directory_name(F,D).
  287
  288biomake_private_dir(Target,Subdirs,Path) :-
  289        biomake_private_subdir_list(Target,Subdirs,List),
  290	concat_string_list(List,Path,"/").
  291
  292biomake_private_filename(Target,Subdirs,Filename) :-
  293	biomake_private_dir(Target,Subdirs,Private),
  294	absolute_file_name(Target,F),
  295	file_base_name(F,N),
  296	format(string(Filename),"~w/~w",[Private,N]).
  297
  298biomake_private_filename_dir_exists(Target,Subdirs,Filename) :-
  299        biomake_private_subdir_list(Target,Subdirs,[Root|List]),
  300	biomake_make_subdir_list(Root,List),
  301	biomake_private_filename(Target,Subdirs,Filename).
  302
  303biomake_make_subdir_list(_,[]).
  304biomake_make_subdir_list(Root,[Dir|Subdirs]) :-
  305        format(string(Next),"~w/~w",[Root,Dir]),
  306	safe_make_directory(Next),
  307	biomake_make_subdir_list(Next,Subdirs).
  308	
  309safe_make_directory(Path) :-
  310        exists_directory(Path),
  311	!.
  312
  313safe_make_directory(Path) :-
  314        catch(make_directory(Path),_,fail),
  315        !.
  316
  317safe_make_directory(Path) :-
  318        absolute_file_name(Path,AbsPath),
  319	format(string(Exec),"mkdir -p ~w",[AbsPath]),
  320	shell(Exec).
  321
  322open_biomake_private_file(Target,Subdir,Filename,Stream) :-
  323	open_biomake_private_file(Target,Subdir,Filename,Stream,[]).
  324
  325open_biomake_private_file(Target,Subdir,Filename,Stream,Options) :-
  326	biomake_private_filename_dir_exists(Target,Subdir,Filename),
  327	open(Filename,write,Stream,Options)