mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-20 20:03:44 +00:00
415 lines
15 KiB
Mathematica
415 lines
15 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ts=4 sw=4 et ft=mercury
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% This program is a filter for text streams. The only part of the text stream
|
|
% that flows through it that it changes is any sequence of lines that all
|
|
% end with the same user-specifiable pattern string, and the only change
|
|
% it makes to those lines is to put spaces before those strings as necessary
|
|
% to make all the pattern strings align in the rightmost column in which
|
|
% any of those lines end with the pattern string.
|
|
%
|
|
% This is intended to make it easier to work on multiline C macros,
|
|
% in which each line ends with a backslash. Programmers can change the code
|
|
% of the macros themselves, and then make the macro pretty by invoking this
|
|
% filter either on the whole file or just the macro. This is why the default
|
|
% string is a single backslash.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module align_right.
|
|
:- interface.
|
|
|
|
:- import_module io.
|
|
|
|
:- pred main(io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module bool.
|
|
:- import_module char.
|
|
:- import_module getopt.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module maybe.
|
|
:- import_module string.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
main(!IO) :-
|
|
io.command_line_arguments(Args, !IO),
|
|
OptionOps = option_ops_multi(short_options, long_options, option_defaults),
|
|
getopt.process_options(OptionOps, Args, _OptionArgs, NonOptionArgs,
|
|
OptionsResult),
|
|
(
|
|
OptionsResult = error(Error),
|
|
set_exit_status(1, !IO),
|
|
io.write_string(option_error_to_string(Error), !IO),
|
|
io.nl(!IO)
|
|
;
|
|
OptionsResult = ok(OptionTable),
|
|
getopt.lookup_bool_option(OptionTable, help, Help),
|
|
(
|
|
Help = no,
|
|
align_right(OptionTable, NonOptionArgs, !IO)
|
|
;
|
|
Help = yes,
|
|
usage(!IO)
|
|
)
|
|
).
|
|
|
|
:- pred align_right(option_table(option)::in, list(string)::in,
|
|
io::di, io::uo) is det.
|
|
|
|
align_right(OptionTable, NonOptionArgs, !IO) :-
|
|
(
|
|
NonOptionArgs = [],
|
|
MaybeStream = yes(io.stdin_stream)
|
|
;
|
|
NonOptionArgs = [FileName],
|
|
io.open_input(FileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(FileStream),
|
|
MaybeStream = yes(FileStream)
|
|
;
|
|
OpenResult = error(OpenError),
|
|
MaybeStream = no,
|
|
set_exit_status(1, !IO),
|
|
OpenErrorMsg = io.error_message(OpenError),
|
|
io.write_string(OpenErrorMsg, !IO),
|
|
io.nl(!IO)
|
|
)
|
|
;
|
|
NonOptionArgs = [_, _ | _],
|
|
MaybeStream = no,
|
|
set_exit_status(1, !IO),
|
|
usage(!IO)
|
|
),
|
|
(
|
|
MaybeStream = no
|
|
;
|
|
MaybeStream = yes(Stream),
|
|
io.read_file(Stream, ReadResult, !IO),
|
|
(
|
|
ReadResult = ok(FileChars),
|
|
getopt.lookup_int_option(OptionTable, tab_width, TabWidth),
|
|
getopt.lookup_bool_option(OptionTable, squeeze, SqueezeBool),
|
|
( SqueezeBool = no, Squeeze = do_not_squeeze
|
|
; SqueezeBool = yes, Squeeze = squeeze
|
|
),
|
|
getopt.lookup_string_option(OptionTable, pattern, PatternStr),
|
|
string.to_char_list(PatternStr, PatternChars),
|
|
list.reverse(PatternChars, RevPatternChars),
|
|
list.length(PatternChars, PatternLen),
|
|
getopt.lookup_int_option(OptionTable, lo_pattern_start_column,
|
|
LoPatternStartColumn),
|
|
getopt.lookup_int_option(OptionTable, hi_pattern_end_column,
|
|
HiPatternEndColumn),
|
|
LoBeforePatternColumn = LoPatternStartColumn,
|
|
HiBeforePatternColumn = HiPatternEndColumn - PatternLen,
|
|
Params = params(TabWidth, Squeeze,
|
|
PatternStr, RevPatternChars, PatternLen,
|
|
LoBeforePatternColumn, HiBeforePatternColumn),
|
|
|
|
break_into_lines(Params, FileChars, [], RevFileLines),
|
|
list.reverse(RevFileLines, FileLines),
|
|
write_out_lines(Params, FileLines, !IO)
|
|
;
|
|
ReadResult = error(_PartialFileChars, Error),
|
|
set_exit_status(1, !IO),
|
|
ErrorMsg = io.error_message(Error),
|
|
io.write_string(ErrorMsg, !IO),
|
|
io.nl(!IO)
|
|
)
|
|
).
|
|
|
|
:- pred usage(io::di, io::uo) is det.
|
|
|
|
usage(!IO) :-
|
|
io.progname("align_right", ProgName, !IO),
|
|
io.format(
|
|
"usage: %s [-p<pattern_string>] [-lN] [-hM] [-tT] [-s] [FileName]\n",
|
|
[s(ProgName)], !IO),
|
|
io.write_string(string.join_list("\n", option_desc), !IO).
|
|
|
|
:- func option_desc = list(string).
|
|
|
|
option_desc = [
|
|
"",
|
|
"The value of the -p option specifies the string whose presence",
|
|
"at the ends of on two or more consecutive lines asks the program",
|
|
"to right-align the ends of those lines by adding padding before them.",
|
|
"The default is a single backslash, to help align the ends of C macros.",
|
|
"",
|
|
"The value of the -h option specifies the highest column number",
|
|
"that the program will push the end of the pattern string to.",
|
|
"By default, there is no maximum.",
|
|
"",
|
|
"The value of the -l option specifies the lowest column number",
|
|
"that the program will start the pattern string at. Even if",
|
|
"all the lines in a block of lines that all end in the specified pattern",
|
|
"start the pattern before this column, the program will add padding",
|
|
"to push them out to start in this column.",
|
|
"By default, there is no minimum.",
|
|
"",
|
|
"In the absence of the -s (squeeze) option, the program will never",
|
|
"delete any spaces before the pattern string. In its presence, however,",
|
|
"the program will replace any run of white space before the pattern",
|
|
"string with a single space before doing its job. This means that",
|
|
"-s allows the program to remove existing padding before the pattern",
|
|
"string if it is more extensive than necessary.",
|
|
"",
|
|
"The program is designed primarily to work on text in which tabs",
|
|
"do not occur. However, if tabs do occur in the input, the default",
|
|
"setting is to consider tab stops to occur in every 8th column.",
|
|
"However, the value of the -t (tabs) option, if given, overrides",
|
|
"this setting.",
|
|
""
|
|
].
|
|
|
|
:- type option
|
|
---> tab_width
|
|
; lo_pattern_start_column
|
|
; hi_pattern_end_column
|
|
; pattern
|
|
; squeeze
|
|
; help.
|
|
|
|
:- pred short_options(char::in, option::out) is semidet.
|
|
|
|
short_options('t', tab_width).
|
|
short_options('l', lo_pattern_start_column).
|
|
short_options('h', hi_pattern_end_column).
|
|
short_options('p', pattern).
|
|
short_options('s', squeeze).
|
|
|
|
:- pred long_options(string::in, option::out) is semidet.
|
|
|
|
long_options("tab_width", tab_width).
|
|
long_options("lo_pattern_start_column", lo_pattern_start_column).
|
|
long_options("hi_pattern_end_column", hi_pattern_end_column).
|
|
long_options("pattern", pattern).
|
|
long_options("squeeze", squeeze).
|
|
long_options("help", help).
|
|
|
|
:- pred option_defaults(option::out, option_data::out) is multi.
|
|
|
|
option_defaults(tab_width, int(8)).
|
|
option_defaults(lo_pattern_start_column, int(-1)).
|
|
option_defaults(hi_pattern_end_column, int(-1)).
|
|
option_defaults(pattern, string("\\")).
|
|
option_defaults(squeeze, bool(no)).
|
|
option_defaults(help, bool(no)).
|
|
|
|
:- type params
|
|
---> params(
|
|
p_tab_width :: int,
|
|
p_squeeze :: squeeze,
|
|
p_pattern_str :: string,
|
|
p_rev_pattern_chars :: list(char),
|
|
p_pattern_len :: int,
|
|
p_lo_before_pattern_column :: int,
|
|
p_hi_before_pattern_column :: int
|
|
).
|
|
|
|
:- type squeeze
|
|
---> do_not_squeeze
|
|
; squeeze.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type pat_line
|
|
---> non_pattern_line(
|
|
% This line DOES NOT end with the pattern.
|
|
|
|
% The list of all the characters on the line,
|
|
% including the final newline, if there is one.
|
|
% (The final line of a file may or may not be
|
|
% terminated by a newline.)
|
|
list(char)
|
|
)
|
|
; pattern_line(
|
|
% This line DOES end with the pattern.
|
|
|
|
% The list of all the characters on the line,
|
|
% EXCEPT the final pattern and newline.
|
|
% (We expect lines that end with the pattern
|
|
% to also have a newline, since in the use-case that
|
|
% this tool is built for, there will always be one.)
|
|
list(char),
|
|
|
|
% The column in which the last character before the
|
|
% pattern was.
|
|
int
|
|
).
|
|
|
|
:- inst pat_line for pat_line/0
|
|
---> pattern_line(ground, ground).
|
|
|
|
:- pred break_into_lines(params::in, list(char)::in,
|
|
list(pat_line)::in, list(pat_line)::out) is det.
|
|
|
|
break_into_lines(_Params, [], !RevLines).
|
|
break_into_lines(Params, [Char | Chars], !RevLines) :-
|
|
get_next_line(Params, Char, 1, Chars, [], Line, LeftOverChars),
|
|
!:RevLines = [Line | !.RevLines],
|
|
break_into_lines(Params, LeftOverChars, !RevLines).
|
|
|
|
:- pred get_next_line(params::in, char::in, int::in,
|
|
list(char)::in, list(char)::in, pat_line::out, list(char)::out) is det.
|
|
|
|
get_next_line(Params, FirstChar, FirstCharColumn, LaterChars,
|
|
!.RevCharsSoFar, Line, LeftOverChars) :-
|
|
!:RevCharsSoFar = [FirstChar | !.RevCharsSoFar],
|
|
( if FirstChar = '\n' then
|
|
make_line(Params, !.RevCharsSoFar, FirstCharColumn, Line),
|
|
LeftOverChars = LaterChars
|
|
else
|
|
(
|
|
LaterChars = [],
|
|
% The column does not matter, since the test in make_line
|
|
% cannot succeed without the final newline.
|
|
make_line(Params, !.RevCharsSoFar, FirstCharColumn, Line),
|
|
LeftOverChars = []
|
|
;
|
|
LaterChars = [FirstLaterChar | LaterLaterChars],
|
|
FirstLaterCharColumn = update_column(Params ^ p_tab_width,
|
|
FirstCharColumn, FirstChar),
|
|
get_next_line(Params, FirstLaterChar, FirstLaterCharColumn,
|
|
LaterLaterChars, !.RevCharsSoFar, Line, LeftOverChars)
|
|
)
|
|
).
|
|
|
|
:- func update_column(int, int, char) = int.
|
|
|
|
update_column(TabWidth, CurColumn, Char) = NextColumn :-
|
|
( if Char = '\t' then
|
|
RoundUp = TabWidth * ((CurColumn + TabWidth - 1) / TabWidth),
|
|
NextColumn = RoundUp + 1
|
|
else
|
|
NextColumn = CurColumn + 1
|
|
).
|
|
|
|
:- pred make_line(params::in, list(char)::in, int::in, pat_line::out) is det.
|
|
|
|
make_line(Params, RevCharsSoFar, NextColumn, Line) :-
|
|
( if
|
|
RevCharsSoFar = ['\n' | RevBeforeNewlineChars],
|
|
RevPattern = Params ^ p_rev_pattern_chars,
|
|
list.append(RevPattern, RevBeforePatternChars0, RevBeforeNewlineChars)
|
|
then
|
|
% Subtract the length of the newline and the pattern.
|
|
MaxColumn0 = NextColumn - 1 - Params ^ p_pattern_len,
|
|
|
|
Squeeze = Params ^ p_squeeze,
|
|
(
|
|
Squeeze = do_not_squeeze,
|
|
RevBeforePatternChars = RevBeforePatternChars0,
|
|
MaxColumn = MaxColumn0
|
|
;
|
|
Squeeze = squeeze,
|
|
squeeze(RevBeforePatternChars0, RevBeforePatternChars,
|
|
MaxColumn0, MaxColumn)
|
|
),
|
|
list.reverse(RevBeforePatternChars, BeforePatternChars),
|
|
Line = pattern_line(BeforePatternChars, MaxColumn)
|
|
else
|
|
list.reverse(RevCharsSoFar, LineChars),
|
|
Line = non_pattern_line(LineChars)
|
|
).
|
|
|
|
% If the reversed line ends with more than one space character,
|
|
% squeeze out all but one of them.
|
|
%
|
|
:- pred squeeze(list(char)::in, list(char)::out, int::in, int::out) is det.
|
|
|
|
squeeze(Chars, SqueezedChars, !MaxColumn) :-
|
|
( if Chars = [' ', ' ' | MoreChars] then
|
|
!:MaxColumn = !.MaxColumn - 1,
|
|
squeeze([' ' | MoreChars], SqueezedChars, !MaxColumn)
|
|
else
|
|
SqueezedChars = Chars
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred write_out_lines(params::in, list(pat_line)::in, io::di, io::uo) is det.
|
|
|
|
write_out_lines(_Params, [], !IO).
|
|
write_out_lines(Params, [Line | Lines], !IO) :-
|
|
(
|
|
Line = non_pattern_line(LineChars),
|
|
write_out_chars(LineChars, !IO),
|
|
write_out_lines(Params, Lines, !IO)
|
|
;
|
|
Line = pattern_line(_, Column),
|
|
get_pattern_block(Lines, [Line], RevBlockLines, Column, MaxColumn0,
|
|
LeftOverLines),
|
|
LoBeforePatternColumn = Params ^ p_lo_before_pattern_column,
|
|
HiBeforePatternColumn = Params ^ p_hi_before_pattern_column,
|
|
int.max(MaxColumn0, LoBeforePatternColumn, MaxColumn1),
|
|
( if HiBeforePatternColumn < 0 then
|
|
MaxColumn = MaxColumn1
|
|
else
|
|
int.min(MaxColumn1, HiBeforePatternColumn, MaxColumn)
|
|
),
|
|
BlockLines = list.inst_preserving_reverse(RevBlockLines),
|
|
write_out_pattern_lines(Params, BlockLines, MaxColumn, !IO),
|
|
write_out_lines(Params, LeftOverLines, !IO)
|
|
).
|
|
|
|
:- pred get_pattern_block(list(pat_line)::in,
|
|
list(pat_line)::in(list_skel(pat_line)),
|
|
list(pat_line)::out(list_skel(pat_line)),
|
|
int::in, int::out, list(pat_line)::out) is det.
|
|
|
|
get_pattern_block([], !RevBlockLines, !MaxColumn, []).
|
|
get_pattern_block([Line | Lines], !RevBlockLines, !MaxColumn,
|
|
LeftOverLines) :-
|
|
(
|
|
Line = non_pattern_line(_),
|
|
LeftOverLines = [Line | Lines]
|
|
;
|
|
Line = pattern_line(_, LineColumn),
|
|
!:RevBlockLines = [Line | !.RevBlockLines],
|
|
!:MaxColumn = int.max(!.MaxColumn, LineColumn),
|
|
get_pattern_block(Lines, !RevBlockLines, !MaxColumn, LeftOverLines)
|
|
).
|
|
|
|
:- pred write_out_pattern_lines(params::in,
|
|
list(pat_line)::in(list_skel(pat_line)), int::in, io::di, io::uo) is det.
|
|
|
|
write_out_pattern_lines(_Params, [], _, !IO).
|
|
write_out_pattern_lines(Params, [FirstLine | LaterLine], MaxColumn, !IO) :-
|
|
trace [compile_time(flag("dump_block_lines")), io(!TIO)] (
|
|
io.write(FirstLine, !TIO),
|
|
io.write_char('\n', !TIO)
|
|
),
|
|
FirstLine = pattern_line(LineCharsBeforePattern, LineColumn),
|
|
write_out_chars(LineCharsBeforePattern, !IO),
|
|
write_out_n_spaces(MaxColumn - LineColumn, !IO),
|
|
io.write_string(Params ^ p_pattern_str, !IO),
|
|
io.write_char('\n', !IO),
|
|
write_out_pattern_lines(Params, LaterLine, MaxColumn, !IO).
|
|
|
|
:- pred write_out_chars(list(char)::in, io::di, io::uo) is det.
|
|
|
|
write_out_chars([], !IO).
|
|
write_out_chars([Char | Chars], !IO) :-
|
|
io.write_char(Char, !IO),
|
|
write_out_chars(Chars, !IO).
|
|
|
|
:- pred write_out_n_spaces(int::in, io::di, io::uo) is det.
|
|
|
|
write_out_n_spaces(N, !IO) :-
|
|
( if N =< 0 then
|
|
true
|
|
else
|
|
io.write_char(' ', !IO),
|
|
write_out_n_spaces(N - 1, !IO)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|