diff: diff [part 2]

Andrew Bromage bromage at cs.mu.oz.au
Thu Jan 8 14:04:24 AEDT 1998


New File: TODO
===================================================================

Things which should be straightforward:

 * The following options should be supported but aren't:

	--expand-tabs
	--initial-tab
	--ignore-case
	--ignore-all-space
	--ignore-space-change
	--sdiff-merge-assist

 * Optimise the case of the --brief output style, where a full-blown diff
   isn't necessary but we currently do it anyway.  Similarly, if no diff
   implies no output (which it does for some output styles) we could
   avoid a full diff sometimes.

 * We currently aren't careful about noticing the difference between a
   file which has a return/new line on the last line and one which
   doesn't.  Admittedly this distinction has never made a difference to
   any diffing I've done, but if we're going try to be compliant...


Things which need a bit more work:

 * Implement a more efficient diff algorithm.

 * Support diffing of binary files.  Mostly this just requires being
   more careful than we currently are.


Things which would be nice but may/will require a lot of work:

 * Implement a "regexp" module so we can handle more options.

 * Output file dates on context/unified diffs.

 * Directory diffs.



New File: diff_out.m
===================================================================
%-----------------------------------------------------------------------------%
% Copyright (C) 1995-1997 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%

% Main author: bromage
% Based on diffs.m, written by bromage and simplified by
% Marnix Klooster <marnix at worldonline.nl>

% This module contains the predicates to display a diff in various
% output styles, based on the command-line options supplied.

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

:- module diff_out.

:- interface.
:- import_module file, io, int, string, difftype.

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

:- type diff_out__output_style --->
		normal
	;	help_only
	;	version_only
	;	context(int)
	;	unified(int)
	;	ed
	;	forward_ed
	;	rcs
	;	ifdef(string)
	;	brief
	;	side_by_side.

:- pred diff_out__default_output_style(diff_out__output_style :: out) is det.

	% display_diff takes a diff and displays it
	% in the user's specified output format.
:- pred display_diff(file, file, diff, io__state, io__state).
:- mode display_diff(in, in, in, di, uo) is det.

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

:- implementation.
:- import_module globals, options.
:- import_module require, std_util, int, list, char, string, bool.

diff_out__default_output_style(normal).

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

	% diff_out__show_file shows the segment of the file
	% from Low to High, with each line preceeded by
	% the Prefix characher and a space.  The diff(1)
	% format specifies that the lines effected in the
	% first file should be flagged by '<' and the
	% lines effected in the second file should be
	% flagged by '>'.
	%
	% NOTE: GCC 2.7.2 under Digital Unix 3.2 doesn't compile
	%       this predicate correctly with optimisation turned on.
:- pred diff_out__show_file(file, string, segment, io__state, io__state).
:- mode diff_out__show_file(in, in, in, di, uo) is det.

diff_out__show_file(File, Prefix, Low - High) -->
	( { Low < High } ->
		( { file__get_line(File, Low, Line) } ->
			{ Low1 is Low + 1 },
			io__write_strings([Prefix, Line]),
			diff_out__show_file(File, Prefix, Low1 - High)
		;
			{ error("diff_out_show_file: file ended prematurely") }
		)
	;
		[]
	).

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

	% display_diff: Determine which output style to use, then call
	% the predicate to display that output.
	%
	% Some of these options (notably the ones which require no
	% output) should have been handled already by the time we
	% reach here.  In those cases, we just call error/1.
display_diff(File1, File2, Diff) -->
	globals__io_get_output_style(OutputStyle),
	( { OutputStyle = normal },
		display_diff_normal(File1, File2, Diff)
	; { OutputStyle = help_only },
		{ error("display_diff: help_only") }
	; { OutputStyle = version_only },
		{ error("display_diff: version_only") }
	; { OutputStyle = context(Context) },
		display_context_diff(Context, File1, File2, Diff)
	; { OutputStyle = unified(Context) },
		display_unified_diff(Context, File1, File2, Diff)
	; { OutputStyle = ed },
		display_diff_ed(File1, File2, Diff)
	; { OutputStyle = forward_ed },
		display_diff_forward_ed(File1, File2, Diff)
	; { OutputStyle = rcs },
		display_diff_rcs(File1, File2, Diff)
	; { OutputStyle = ifdef(Sym) },
		display_diff_ifdef(Sym, File1, File2, Diff)
	; { OutputStyle = brief },
		% XXX For this output style, we really don't need to
		%     perform a complete diff.  This should be handled
		%     higher up for efficiency.
		( { Diff \= [] } ->
			{ file__get_file_name(File1, FileName1) },
			{ file__get_file_name(File2, FileName2) },
			io__write_strings(["Files ", FileName1, " and ",
				FileName2, " differ\n"])
		;
			[]
		)
	; { OutputStyle = side_by_side },
		display_diff_side_by_side(File1, File2, Diff)
	).

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

	% display_diff_normal takes a diff and displays it
	% in the standard diff(1) output format.
:- pred display_diff_normal(file, file, diff, io__state, io__state).
:- mode display_diff_normal(in, in, in, di, uo) is det.

display_diff_normal(_, _, []) --> [].
display_diff_normal(File1, File2, [SingDiff | Diff]) -->
	( { SingDiff = add(X, Y1 - Y2) },
		diff_out__write_command(X - X, 'a', Y1 - Y2),
		diff_out__show_file(File2, "> ", Y1 - Y2)
	; { SingDiff = delete(X1 - X2, Y) },
		diff_out__write_command(X1 - X2, 'd', Y - Y),
		diff_out__show_file(File1, "< ", X1 - X2)
	; { SingDiff = change(X1 - X2, Y1 - Y2) },
		diff_out__write_command(X1 - X2, 'c', Y1 - Y2),
		diff_out__show_file(File1, "< ", X1 - X2),
		io__write_string("---\n"),
		diff_out__show_file(File2, "> ", Y1 - Y2)
	),
	display_diff_normal(File1, File2, Diff).


	% diff_out__write_command displays a diff(1) command.
	% Like ed(1), a pair of numbers which are identical
	% are abbreviated by a single number.
	% MK: Assumption X=<X2
	% AJB: And, similarly, Y=<Y2.  This is actually an
	%      invariant of the segment type.  See difftype.m.
:- pred diff_out__write_command(segment, char, segment, io__state, io__state).
:- mode diff_out__write_command(in, in, in, di, uo) is det.

diff_out__write_command(X - X2, C, Y - Y2) -->
	{ X1 is X + 1 },	% Convert from pos to line number
	( { X1 >= X2 } ->
		% either empty or singleton segment
		io__write_int(X2)
	;
		io__write_int(X1),
		io__write_char(','),
		io__write_int(X2)
	),
	io__write_char(C),
	{ Y1 is Y + 1 },	% Convert from pos to line number
	( { Y1 >= Y2 } ->
		% either empty or singleton segment
		io__write_int(Y2)
	;
		io__write_int(Y1),
		io__write_char(','),
		io__write_int(Y2)
	),
	io__write_char('\n').

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

	% display_diff_rcs takes a diff and displays it
	% in the RCS difference format.
:- pred display_diff_rcs(file, file, diff, io__state, io__state).
:- mode display_diff_rcs(in, in, in, di, uo) is det.

display_diff_rcs(_File1, _File2, []) --> [].
display_diff_rcs(File1, File2, [Cmd | Diff]) -->
	( { Cmd = add(X, Y1 - Y2) },
		{ Y is Y2 - Y1 },
		diff_out__write_command_rcs('a', X, Y),
		diff_out__show_file(File2, "", Y1 - Y2)
	; { Cmd = delete(X1 - X2, _Y) },
		{ X is X2 - X1 },
		diff_out__write_command_rcs('d', X1, X)
	; { Cmd = change(X1 - X2, Y1 - Y2) },
		{ X is X2 - X1 },
		{ Y is Y2 - Y1 },
		diff_out__write_command_rcs('d', X1, X),
		diff_out__write_command_rcs('a', X1, Y),
		diff_out__show_file(File2, "", Y1 - Y2)
	),
	display_diff_rcs(File1, File2, Diff).


	% diff_out__write_command_rcs displays a diff command in
	% the RCS ,v format.
:- pred diff_out__write_command_rcs(char, int, int, io__state, io__state).
:- mode diff_out__write_command_rcs(in, in, in, di, uo) is det.

diff_out__write_command_rcs(C, X, Y) -->
	{ X1 is X + 1 },		% Convert from pos to line number
	io__write_char(C),
	io__write_int(X1),
	io__write_char(' '),
	io__write_int(Y),
	io__write_char('\n').

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

	% display_diff_ed takes a diff and displays it
	% in ed(1) format, but with all diffs backward.
:- pred display_diff_ed(file, file, diff, io__state, io__state).
:- mode display_diff_ed(in, in, in, di, uo) is det.

display_diff_ed(_File1, _File2, []) --> [].
display_diff_ed(File1, File2, [Cmd | Diff]) -->
	display_diff_ed(File1, File2, Diff),
	( { Cmd = add(X, Y1 - Y2) },
		diff_out__write_command_ed(X - X, 'a'),
		diff_out__show_file(File2, "", Y1 - Y2),
		io__write_string(".\n")
	; { Cmd = delete(X, _Y) },
		diff_out__write_command_ed(X, 'd')
	; { Cmd = change(X, Y) },
		diff_out__write_command_ed(X, 'c'),
		diff_out__show_file(File2, "", Y),
		io__write_string(".\n")
	).


	% diff_out__write_command_ed displays an ed(1) command.
:- pred diff_out__write_command_ed(segment, char, io__state, io__state).
:- mode diff_out__write_command_ed(in, in, di, uo) is det.

diff_out__write_command_ed(X - X2, C) -->
	{ X1 is X + 1 },		% Convert from pos to line number
	( { X1 >= X2 } ->
		% either empty or singleton segment
		io__write_int(X2)
	;
		io__write_int(X1),
		io__write_char(','),
		io__write_int(X2)
	),
	io__write_char(C),
	io__write_char('\n').

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

	% display_diff_forward_ed takes a diff and displays it
	% in ed(1) format, but with all diff_out forward.  This
	% is actually useless for feeding to ed(1), but nicer
	% to read.
:- pred display_diff_forward_ed(file, file, diff, io__state, io__state).
:- mode display_diff_forward_ed(in, in, in, di, uo) is det.

display_diff_forward_ed(_File1, _File2, []) --> { true }.
display_diff_forward_ed(File1, File2, [Cmd | Diff]) -->
	( { Cmd = add(X, Y1 - Y2) },
		diff_out__write_command_forward_ed(X - X, 'a'),
		diff_out__show_file(File2, "", Y1 - Y2),
		io__write_string(".\n")
	; { Cmd = delete(X, _Y) },
		diff_out__write_command_forward_ed(X, 'd')
	; { Cmd = change(X, Y) },
		diff_out__write_command_forward_ed(X, 'c'),
		diff_out__show_file(File2, "", Y),
		io__write_string(".\n")
	),
	display_diff_forward_ed(File1, File2, Diff).

	% diff_out__write_command_ed displays a forward ed(1) command.
:- pred diff_out__write_command_forward_ed(segment, char, io__state, io__state).
:- mode diff_out__write_command_forward_ed(in, in, di, uo) is det.
diff_out__write_command_forward_ed(X - X2, C) -->
	io__write_char(C),
	{ X1 is X + 1 },		% Convert from pos to line number
	( { X1 >= X2 } ->
		% either empty or singleton segment
		io__write_int(X2)
	;
		io__write_int(X1),
		io__write_char(' '),
		io__write_int(X2)
	),
	io__write_char('\n').

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

	% display_diff_ifdef writes out the files in a unified diff,
	% using #ifdefs around each edit.
	%
	% TO DO: GNU diff makes this output style much more
	%        configurable.  We should too.
:- pred display_diff_ifdef(string, file, file, diff, io__state, io__state).
:- mode display_diff_ifdef(in, in, in, in, di, uo) is det.

display_diff_ifdef(Sym, File1, File2, Diff) -->
	display_diff_ifdef_2(0, Sym, File1, File2, Diff).

	% Argument 1 (prev) is the last pos displayed before
	% the current edit (or end of edits, in the base case).
	% This is important for when we have to display the
	% "non-diffed" text between edits.
:- pred display_diff_ifdef_2(int, string, file, file, diff,
		io__state, io__state).
:- mode display_diff_ifdef_2(in, in, in, in, in, di, uo) is det.

display_diff_ifdef_2(Prev, _Sym, File1, _File2, []) -->
	{ file__get_numlines(File1, SegEnd) },
	diff_out__show_file(File1, "", Prev - SegEnd).
display_diff_ifdef_2(Prev, Sym, File1, File2, [Edit | Diff]) -->
	{ first_mentioned_positions(Edit, StartOfEdit, _) },
	diff_out__show_file(File1, "", Prev - StartOfEdit),
	( { Edit = add(X, Seg2) },
		io__write_strings(["#ifdef ", Sym, "\n"]),
		diff_out__show_file(File2, "", Seg2),
		io__write_strings(["#endif /* ", Sym, " */\n"]),
		{ Next = X }
	; { Edit = delete(X1 - X2, _) },
		io__write_strings(["#ifndef ", Sym, "\n"]),
		diff_out__show_file(File1, "", X1 - X2),
		io__write_strings(["#endif /* not ", Sym, " */\n"]),
		{ Next = X2 }
	; { Edit = change(X1 - X2, Y1 - Y2) },
		io__write_strings(["#ifndef ", Sym, "\n"]),
		diff_out__show_file(File1, "", X1 - X2),
		io__write_strings(["#else /* ", Sym, " */\n"]),
		diff_out__show_file(File2, "", Y1 - Y2),
		io__write_strings(["#endif /* ", Sym, " */\n"]),
		{ Next = X2 }
	),
	display_diff_ifdef_2(Next, Sym, File1, File2, Diff).

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

	% Types for context/unified diffs.

	% A context diff is a bit more complicated than a "standard"
	% diff, because it requires the display of some parts of the
	% files which are not actually part of the diff, but not all
	% of it.
	%
	% Because context and unified diffs both require the same
	% kind of information, we factor out the code to turn a
	% normal diff into a context diff.

:- type context_edit
	--->	context_edit(segment, segment, diff).

:- type context_diff == list(context_edit).

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

:- pred diff_to_context_diff(int :: in, int :: in, int :: in, diff :: in,
		context_diff :: out) is det.

diff_to_context_diff(_Xsize, _Ysize, _Context, [], []).
diff_to_context_diff(Xsize, Ysize, Context, [Edit | Diff], CDiff) :-
	diff_to_context_diff(Xsize, Ysize, Context, Diff, CDiff0),
	first_mentioned_positions(Edit, Xfirst0, Yfirst0),
	last_mentioned_positions(Edit, Xlast0, Ylast0),
	adjust_context(Context, Xsize, Xfirst0, Xlast0, Xfirst, Xlast),
	adjust_context(Context, Ysize, Yfirst0, Ylast0, Yfirst, Ylast),
	( CDiff0 = [],
		CDiff = [context_edit(Xfirst - Xlast, Yfirst - Ylast, [Edit])]
	; CDiff0 = [context_edit(XsegLo - XsegHi, YsegLo - YsegHi, DDiff) |
				CDiff1],
		% Should we merge this edit into the next one?
		(
			( XsegLo =< Xlast
			; YsegLo =< Ylast
			)
		->
			CDiff = [context_edit(Xfirst - XsegHi, Yfirst - YsegHi,
					[Edit | DDiff]) | CDiff1]
		;
			CDiff = [context_edit(Xfirst - Xlast, Yfirst - Ylast,
					[Edit]) | CDiff0]
		)
	).

:- pred first_mentioned_positions(edit :: in, pos :: out, pos :: out) is det.

first_mentioned_positions(add(X, Y - _), X, Y).
first_mentioned_positions(delete(X - _, Y), X, Y).
first_mentioned_positions(change(X - _, Y - _), X, Y).

:- pred last_mentioned_positions(edit :: in, pos :: out, pos :: out) is det.

last_mentioned_positions(add(X, _ - Y), X, Y).
last_mentioned_positions(delete(_ - X, Y), X, Y).
last_mentioned_positions(change(_ - X, _ - Y), X, Y).

	% Adjust a range to incorporate a given number of lines
	% of context.  Ensure that the new range stays within the
	% size of the file being considered.
:- pred adjust_context(int :: in, int :: in, int :: in, int :: in,
		int :: out, int :: out) is det.
adjust_context(Context, Size, First0, Last0, First, Last) :-
	First1 is First0 - Context,
	Last1 is Last0 + Context,
	( First1 < 0 ->
		First = 0
	;
		First = First1
	),
	( Last1 > Size ->
		Last = Size
	;
		Last = Last1
	).

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

	% Display a diff in unified format.
:- pred display_unified_diff(int, file, file, diff, io__state, io__state).
:- mode display_unified_diff(in, in, in, in, di, uo) is det.

display_unified_diff(Context, File1, File2, Diff) -->
	{ file__get_numlines(File1, Size1) },
	{ file__get_numlines(File2, Size2) },
	{ diff_to_context_diff(Size1, Size2, Context, Diff, CDiff) },
	{ file__get_file_name(File1, Name1) },
	{ file__get_file_name(File2, Name2) },
		% XXX Should also print out file dates.  But how??
	io__write_strings(["--- ", Name1, "\n"]),
	io__write_strings(["+++ ", Name2, "\n"]),
	display_unified_diff_2(File1, File2, CDiff).

:- pred display_unified_diff_2(file, file, context_diff, io__state, io__state).
:- mode display_unified_diff_2(in, in, in, di, uo) is det.

display_unified_diff_2(_File1, _File2, []) --> [].
display_unified_diff_2(File1, File2, [Edit | CDiff]) -->
	{ Edit = context_edit(Xlow - Xhigh, Ylow - Yhigh, Diff) },
	{ Xlow1 is Xlow + 1 },
	{ Ylow1 is Ylow + 1 },
	{ Xsize is Xhigh - Xlow },
	{ Ysize is Yhigh - Ylow },
	io__format("@@ -%d,%d +%d,%d @@\n",
		[i(Xlow1), i(Xsize), i(Ylow1), i(Ysize)]),
	display_unified_diff_3(Xlow, Xhigh, File1, File2, Diff),
	display_unified_diff_2(File1, File2, CDiff).

:- pred display_unified_diff_3(int, int, file, file, diff,
		io__state, io__state).
:- mode display_unified_diff_3(in, in, in, in, in, di, uo) is det.

display_unified_diff_3(Prev, Size1, File1, _File2, []) -->
	diff_out__show_file(File1, " ", Prev - Size1).
display_unified_diff_3(Prev, Size1, File1, File2, [Edit | Diff]) -->
	{ first_mentioned_positions(Edit, StartOfEdit, _) },
	diff_out__show_file(File1, " ", Prev - StartOfEdit),
	( { Edit = add(X, Seg2) },
		diff_out__show_file(File2, "+", Seg2),
		{ Next = X }
	; { Edit = delete(X1 - X2, _) },
		diff_out__show_file(File1, "-", X1 - X2),
		{ Next = X1 }
	; { Edit = change(X1 - X2, Y1 - Y2) },
		diff_out__show_file(File1, "-", X1 - X2),
		diff_out__show_file(File2, "+", Y1 - Y2),
		{ Next = X1 }
	),
	display_unified_diff_3(Next, Size1, File1, File2, Diff).

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

	% Display a diff in context format.
:- pred display_context_diff(int, file, file, diff, io__state, io__state).
:- mode display_context_diff(in, in, in, in, di, uo) is det.

display_context_diff(Context, File1, File2, Diff) -->
	{ file__get_numlines(File1, Size1) },
	{ file__get_numlines(File2, Size2) },
	{ diff_to_context_diff(Size1, Size2, Context, Diff, CDiff) },
	{ file__get_file_name(File1, Name1) },
	{ file__get_file_name(File2, Name2) },
		% XXX Should also print out file dates.  But how??
	io__write_strings(["*** ", Name1, "\n"]),
	io__write_strings(["--- ", Name2, "\n"]),
	display_context_diff_2(File1, File2, CDiff).

:- pred display_context_diff_2(file, file, context_diff, io__state, io__state).
:- mode display_context_diff_2(in, in, in, di, uo) is det.

display_context_diff_2(_File1, _File2, []) --> [].
display_context_diff_2(File1, File2, [Edit | CDiff]) -->
	{ Edit = context_edit(Xlow - Xhigh, Ylow - Yhigh, Diff) },
	{ Xlow1 is Xlow + 1 },
	{ Ylow1 is Ylow + 1 },
	io__write_string("***************\n"),
	io__format("*** %d,%d ****\n", [i(Xlow1), i(Xhigh)]),

		% Don't display the "context from" lines if there's
		% nothing deleted or changed.
	( { list__member(AddEdit, Diff) => AddEdit = add(_, _) } ->
		[]
	;
		display_context_diff_left(Xlow, Xhigh, File1, Diff)
	),
	io__format("--- %d,%d ----\n", [i(Ylow1), i(Yhigh)]),

		% Don't display the "context to" lines if there's
		% nothing added or changed.
	( { list__member(DelEdit, Diff) => DelEdit = delete(_, _) } ->
		[]
	;
		display_context_diff_right(Ylow, Yhigh, File2, Diff)
	),
	display_context_diff_2(File1, File2, CDiff).

:- pred display_context_diff_left(int, int, file, diff, io__state, io__state).
:- mode display_context_diff_left(in, in, in, in, di, uo) is det.

display_context_diff_left(Prev, Size1, File1, []) -->
	diff_out__show_file(File1, "  ", Prev - Size1).
display_context_diff_left(Prev, Size1, File1, [Edit | Diff]) -->
	{ first_mentioned_positions(Edit, StartOfEdit, _) },
	diff_out__show_file(File1, "  ", Prev - StartOfEdit),
	( { Edit = add(X, _) },
		{ Next = X }
	; { Edit = delete(X1 - X2, _) },
		diff_out__show_file(File1, "- ", X1 - X2),
		{ Next = X2 }
	; { Edit = change(X1 - X2, _) },
		diff_out__show_file(File1, "! ", X1 - X2),
		{ Next = X2 }
	),
	display_context_diff_left(Next, Size1, File1, Diff).

:- pred display_context_diff_right(int, int, file, diff, io__state, io__state).
:- mode display_context_diff_right(in, in, in, in, di, uo) is det.

display_context_diff_right(Prev, Size2, File2, []) -->
	diff_out__show_file(File2, "  ", Prev - Size2).
display_context_diff_right(Prev, Size2, File2, [Edit | Diff]) -->
	{ first_mentioned_positions(Edit, StartOfEdit, _) },
	diff_out__show_file(File2, "  ", Prev - StartOfEdit),
	( { Edit = add(_, Y1 - Y2) },
		diff_out__show_file(File2, "+ ", Y1 - Y2),
		{ Next = Y2 }
	; { Edit = delete(_, Y) },
		{ Next = Y }
	; { Edit = change(_, Y1 - Y2) },
		diff_out__show_file(File2, "! ", Y1 - Y2),
		{ Next = Y2 }
	),
	display_context_diff_right(Next, Size2, File2, Diff).

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

	% Side-by-side diffs are incredibly complex, as you'll see if
	% you inspect the code below.
	%
	% TO DO: GNU diff has --sdiff-merge-assist, but I can find no
	%        documentation on what this actually does, and haven't
	%        had the time to investigate.  For the moment, we accept
	%        the option and note here whether or not it's turned on,
	%        but do nothing with it.

	% Parameters to pass around.
:- type side_by_side_info
	--->	side_by_side_info(
			int,		% Half width
			int,		% Column 2 offset
			bool,		% Left column only
			bool,		% Suppress common lines
			bool		% Help sdiff
		).

:- pred display_diff_side_by_side(file, file, diff, io__state, io__state).
:- mode display_diff_side_by_side(in, in, in, di, uo) is det.

display_diff_side_by_side(File1, File2, Diff) -->
	globals__io_lookup_int_option(width, Width0),

	% Calculate the half-width and offset stuff.

		% XXX If we're expanding tabs, we should
		%     factor this in.
	{ Off is (Width0 + 4) // 8 * 4 },
	{ Max is Off - 3 },
	{ HalfWidth0 is Width0 - Off + 1 },
	{ HalfWidth0 =< 0 ->
		HalfWidth = 0
	; HalfWidth0 > Max ->
		HalfWidth = Max
	;
		HalfWidth = HalfWidth0
	},
	{ HalfWidth > 0 ->
		Col2Off = Off
	;
		Col2Off = Width0
	},
	globals__io_lookup_bool_option(left_column, LeftCol),
	globals__io_lookup_bool_option(suppress_common_lines, Suppress),
	globals__io_lookup_bool_option(sdiff_merge_assist, Sdiff),
	{ SBS = side_by_side_info(HalfWidth, Col2Off, LeftCol,
		Suppress, Sdiff) },
	display_diff_side_by_side_2(0, SBS, File1, File2, Diff).

:- pred display_diff_side_by_side_2(int, side_by_side_info, file, file, diff,
		io__state, io__state).
:- mode display_diff_side_by_side_2(in, in, in, in, in, di, uo) is det.

display_diff_side_by_side_2(Prev, SBS, File1, _File2, []) -->
	{ SBS = side_by_side_info(_, _, _, Suppress, _) },
	( { Suppress = no } ->
		{ file__get_numlines(File1, SegEnd) },
		show_sbs_same_lines(File1, SBS, Prev - SegEnd)
	;
		[]
	).
display_diff_side_by_side_2(Prev, SBS, File1, File2, [Edit | Diff]) -->
	{ first_mentioned_positions(Edit, StartOfEdit, _) },
	{ SBS = side_by_side_info(_, _, _, Suppress, _) },
	( { Suppress = no } ->
		show_sbs_same_lines(File1, SBS, Prev - StartOfEdit)
	;
		[]
	),
	( { Edit = add(X, Seg2) },
		show_sbs_added_lines(File2, SBS, Seg2),
		{ Next = X }
	; { Edit = delete(X1 - X2, _) },
		show_sbs_deleted_lines(File1, SBS, X1 - X2),
		{ Next = X2 }
	; { Edit = change(X1 - X2, Y1 - Y2) },
		% The side-by-side change diff format is sort of weird.
		% We have to compute the minimum of the two change sizes,
		% and display "changed" lines for the minimum of these
		% sizes.  Then we display "added" or "deleted" lines for
		% whatever is left over.
		{
			SizeX is X2 - X1,
			SizeY is Y2 - Y1,
			int__min(SizeX, SizeY, Size)

		},
		show_sbs_changed_lines(File1, File2, SBS, X1, Y1, Size),
		{
			NewX1 is X1 + Size,
			NewY1 is Y1 + Size
		},
		show_sbs_deleted_lines(File1, SBS, NewX1 - X2),
		show_sbs_added_lines(File2, SBS, NewY1 - Y2),
		{ Next = X2 }
	),
	display_diff_side_by_side_2(Next, SBS, File1, File2, Diff).

:- pred show_sbs_changed_lines(file, file, side_by_side_info, int, int, int,
		io__state, io__state).
:- mode show_sbs_changed_lines(in, in, in, in, in, in, di, uo) is det.

show_sbs_changed_lines(File1, File2, SBS, X1, Y1, Size) -->
	( { Size > 0 } ->
		(
			{ file__get_line(File1, X1, Line1),
			  file__get_line(File2, Y1, Line2)
			}
		->
			{ SBS = side_by_side_info(Width, _, _, _, _) },
			{ string__to_char_list(Line1, Chars1) },
			print_half_line(Chars1, SBS, 0, 0, Width, OutPos),
			tab_to_column(OutPos, Width),
			io__write_string("|"),
			{ Width1 is Width + 1 },
			{ Width2 is Width + 2 },
			tab_to_column(Width1, Width2),
			{ string__to_char_list(Line2, Chars2) },
			print_half_line(Chars2, SBS, 0, 0, Width, _),
			io__write_string("\n"),
			{ X2 is X1 + 1, Y2 is Y1 + 1, Size1 is Size - 1 },
			show_sbs_changed_lines(File1, File2, SBS, X2, Y2, Size1)
		;
			{ error("show_sbs_changed_lines: file ended prematurely") }
		)
	;
		[]
	).

:- pred show_sbs_same_lines(file, side_by_side_info, segment,
		io__state, io__state).
:- mode show_sbs_same_lines(in, in, in, di, uo) is det.

show_sbs_same_lines(File, SBS, Low - High) -->
	( { Low < High } ->
		( { file__get_line(File, Low, Line) } ->
			{ SBS = side_by_side_info(Width, _, LeftCol, _, _) },
			{ Low1 is Low + 1 },
			{ string__to_char_list(Line, Chars) },
			print_half_line(Chars, SBS, 0, 0, Width, OutPos),

			% If the user specified --left, don't
			% display the right column here.
			( { LeftCol = yes } ->
				tab_to_column(OutPos, Width),
				io__write_string("(")
			;
				{ Width2 is Width + 2 },
				tab_to_column(OutPos, Width2),
				print_half_line(Chars, SBS, 0, 0, Width, _)
			),
			io__write_string("\n"),
			show_sbs_same_lines(File, SBS, Low1 - High)
		;
			{ error("show_sbs_same_lines: file ended prematurely") }
		)
	;
		[]
	).

:- pred show_sbs_added_lines(file, side_by_side_info, segment,
		io__state, io__state).
:- mode show_sbs_added_lines(in, in, in, di, uo) is det.

show_sbs_added_lines(File, SBS, Low - High) -->
	( { Low < High } ->
		( { file__get_line(File, Low, Line) } ->
			{ SBS = side_by_side_info(Width, _, _, _, _) },
			{ Low1 is Low + 1 },
			{ string__to_char_list(Line, Chars) },
			tab_to_column(0, Width),
			io__write_string("> "),
			print_half_line(Chars, SBS, 0, 0, Width, _),
			io__write_string("\n"),
			show_sbs_added_lines(File, SBS, Low1 - High)
		;
			{ error("show_sbs_added_lines: file ended prematurely") }
		)
	;
		[]
	).

:- pred show_sbs_deleted_lines(file, side_by_side_info, segment,
		io__state, io__state).
:- mode show_sbs_deleted_lines(in, in, in, di, uo) is det.

show_sbs_deleted_lines(File, SBS, Low - High) -->
	( { Low < High } ->
		( { file__get_line(File, Low, Line) } ->
			{ SBS = side_by_side_info(Width, _, _, _, _) },
			{ Low1 is Low + 1 },
			{ string__to_char_list(Line, Chars) },
			print_half_line(Chars, SBS, 0, 0, Width, OutPos),
			tab_to_column(OutPos, Width),
			io__write_string("<\n"),
			show_sbs_deleted_lines(File, SBS, Low1 - High)
		;
			{ error("show_sbs_deleted_lines: file ended prematurely") }
		)
	;
		[]
	).

:- pred tab_width(int :: out) is det.
tab_width(8).

	% Put a number of spaces on the output stream.  Update
	% the output column as we go.
:- pred put_spaces(int, int, int, io__state, io__state).
:- mode put_spaces(in, in, out, di, uo) is det.

put_spaces(Spaces, OutPos0, OutPos) -->
	( { Spaces =< 0 } ->
		{ OutPos = OutPos0 }
	;
		io__write_char(' '),
		{ Spaces1 is Spaces - 1 },
		{ OutPos1 is OutPos0 + 1 },
		put_spaces(Spaces1, OutPos1, OutPos)
	).

	% Given a "from" column and a "to" column, put sufficient
	% spaces on the output stream to reach that column.  Use
	% tabs if we can.
:- pred tab_to_column(int, int, io__state, io__state).
:- mode tab_to_column(in, in, di, uo) is det.

tab_to_column(From, To) -->
	{ tab_width(Tab) },
	{ AfterTab is From + Tab - (From mod Tab) },
	( { AfterTab > To } ->
		( { From < To } ->
			io__write_char(' '),
			{ From1 is From + 1 },
			tab_to_column(From1, To)
		;
			[]
		)
	;
		io__write_char('\t'),
		tab_to_column(AfterTab, To)
	).

	% Display half a line in a side-by-side diff, stopping when
	% we reach a certain column.
	%
	% This is actually a very simple thing to do, except for one
	% complication, which is the displaying of tab characters.
	%
	% The important variables are:
	% 
	%	InPos: The current column in the input line.
	%	OutPos: The current column in the output line.
	%	OutBound: The column that we must stop at.
	%
:- pred print_half_line(list(char) :: in, side_by_side_info :: in,
		int :: in, int :: in, int :: in, int :: out,
		io__state :: di, io__state :: uo) is det.

print_half_line([], _SBS, _InPos, OutPos, _OutBound, OutPos) --> [].
print_half_line([C | Cs], SBS, InPos0, OutPos0, OutBound, OutPos) -->
	( { C = '\t' } ->
		{ tab_width(Tab) },

			% Calculate how many spaces this tab is worth.
		{ Spaces is Tab - InPos0 mod Tab },
		( { InPos0 = OutPos0 } ->
			globals__io_lookup_bool_option(expand_tabs, ExpandTabs),
			( { ExpandTabs = yes } ->
				% If we're expanding tabs, we just pretend that
				% we had Spaces spaces and write them.
				{ TabStop0 is OutPos0 + Spaces },
				{ TabStop0 > OutBound ->
					TabStop = OutBound
				;
					TabStop = TabStop0
				},
				{ WriteSpaces is TabStop - OutPos0 },
				put_spaces(WriteSpaces, OutPos0, OutPos1)
			;
				% If we're not exanding tabs, just print it and
				% hope everything lines up okay.
				io__write_char('\t'),
				{ OutPos1 is OutPos0 + Spaces }
			)
		;
			{ OutPos1 = OutPos0 }
		),
		{ InPos is InPos0 + Spaces }
	; { C = '\r' ; C = '\b' ; C = '\n' } ->
		% XXX What to do?  For the moment, we'll just ignore it.
		{ InPos = InPos0, OutPos1 = OutPos0 }
	/***********
		% XXX Binary files aren't really supported.
	; { \+ char__is_print(C) } ->
		{ InPos = InPos0, OutPos1 = OutPos0 }
		( { InPos < OutBound } ->
			io__write_char(C)
		;
			[]
		)
	***********/
	;
		% The default case.  Print and be done with it.
		{ InPos is InPos0+1 },
		( { InPos < OutBound } ->
			{ OutPos1 = InPos },
			io__write_char(C)
		;
			{ OutPos1 = OutPos0 }
		)
	),
	print_half_line(Cs, SBS, InPos, OutPos1, OutBound, OutPos).

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

New File: difftype.m
===================================================================
%-----------------------------------------------------------------------------%
% Copyright (C) 1995-1997 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%

% Main author: bromage
% Based on lcsstype.m, written by bromage and simplified by
% Marnix Klooster <marnix at worldonline.nl>

% This module contains the type of a diff.

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

:- module difftype.

:- interface.
:- import_module std_util, list.

	% A pos is a non-negative number representing a position in a
	% list.  The position before all elements is 0, the one
	% between the first and second elements is 1, etc.
:- type pos == int.

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

	% A segment is a pair of positions.  Numbering items from 0,
	% segment P-Q stands for items P up to, but not including, Q.
	% (Rationale: see the interpretation of type pos above.)
	%
	% Invariant: In any segment X - Y, it should always be true
	% that X =< Y.  If X=Y, the segment is empty.
:- type segment == pair(pos,pos).

	% An edit operation is an addition, a deletion or a change.
:- type edit --->
		add(pos,segment)
	;	delete(segment,pos)
	;	change(segment,segment).

	% The complete diff of two file is a list of edit
	% operations.
	%
	% Invariant: The edits must be in order, and must
	% not overlap or touch.
:- type diff == list(edit).

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

New File: filter.m
===================================================================
%-----------------------------------------------------------------------------%
% Copyright (C) 1997 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%

% Main author: bromage

% This module contains code to filter the diff before output, based on
% the command-line options presented.

% At the moment, only one option is handled: --ignore-blank-lines.
% This causes edits to be dropped if they contain changes which only
% add, delete or change blank lines.

% TO DO: What is a blank line, exactly, and does its definition change
%        if --ignore-space-change or --ignore-all-space have been
%        specified?  At the moment, we define a blank line to be a line
%        containing zero or more whitespace characters.

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

:- module filter.

:- interface.
:- import_module difftype, file, io.

:- pred filter_diff(diff :: in, file :: in, file :: in, diff :: out,
		io__state :: di, io__state :: uo) is det.

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

:- implementation.
:- import_module globals, options.
:- import_module bool, list, int, std_util, string, char.

filter_diff(Diff0, File1, File2, Diff) -->
	globals__io_lookup_bool_option(ignore_blank_lines, FilterBlank),

	{ FilterBlank = no ->
		% If we didn't request a filter, skip this pass.

		Diff = Diff0
	;
		filter__blank_lines(Diff0, File1, File2, Diff)
	}.

:- pred filter__blank_lines(diff :: in, file :: in, file :: in, diff :: out)
		is det.

filter__blank_lines([], _, _, []).
filter__blank_lines([Edit | Diff0], File1, File2, Diff) :-
	filter__blank_lines(Diff0, File1, File2, Diff1),
	( Edit = add(_, Y1 - Y2),
		( range_has_only_blank_lines(Y1, Y2, File2) ->
			Diff = Diff1
		;
			Diff = [Edit | Diff1]
		)
	; Edit = delete(X1 - X2, _),
		( range_has_only_blank_lines(X1, X2, File1) ->
			Diff = Diff1
		;
			Diff = [Edit | Diff1]
		)
	; Edit = change(X1 - X2, Y1 - Y2),
		(
			range_has_only_blank_lines(X1, X2, File1),
			range_has_only_blank_lines(Y1, Y2, File2)
		->
			Diff = Diff1
		;
			Diff = [Edit | Diff1]
		)
	).

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

:- pred range_has_only_blank_lines(int, int, file).
:- mode range_has_only_blank_lines(in, in, in) is semidet.

range_has_only_blank_lines(First, Last, File) :-
	(
		First < Last
	=>
		(
			file__get_line(File, First, Line),
			string__to_char_list(Line, Chars),
			(
				list__member(C, Chars)
			=>
				char__is_whitespace(C)
			),
			Next is First + 1,
			range_has_only_blank_lines(Next, Last, File)
		)
	).

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

New File: globals.m
===================================================================
%-----------------------------------------------------------------------------%
% Copyright (C) 1994-1997 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%

:- module globals.

% Main author: conway, bromage.

% This module exports the `globals' type and associated access predicates.
% The globals type is used to collect together all the various data
% that would be global variables in an imperative language.
% This global data is stored in the io__state.

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

:- interface.
:- import_module options, diff_out.
:- import_module getopt, bool, int, string, list.

:- type globals.

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

	% Access predicates for the `globals' structure.

:- pred globals__init(option_table::in, globals::out) is det.

:- pred globals__get_options(globals::in, option_table::out) is det.

:- pred globals__set_options(globals::in, option_table::in, globals::out)
		is det.

:- pred globals__get_output_style(globals::in, diff_out__output_style::out)
		is det.

:- pred globals__set_output_style(globals::in, diff_out__output_style::in,
		globals::out) is det.

:- pred globals__lookup_option(globals::in, option::in, option_data::out)
		is det.

:- pred globals__lookup_bool_option(globals, option, bool).
:- mode globals__lookup_bool_option(in, in, out) is det.
:- pred globals__lookup_int_option(globals::in, option::in, int::out) is det.
:- pred globals__lookup_string_option(globals::in, option::in, string::out)
		is det.
:- pred globals__lookup_accumulating_option(globals::in, option::in,
		list(string)::out) is det.

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

	% Access predicates for storing a `globals' structure in the
	% io__state using io__set_globals and io__get_globals.

:- pred globals__io_init(option_table::in,
			io__state::di, io__state::uo) is det.

:- pred globals__io_get_globals(globals::out, io__state::di, io__state::uo)
			is det.

:- pred globals__io_set_globals(globals::in, io__state::di, io__state::uo)
			is det.

:- pred globals__io_get_output_style(diff_out__output_style::out,
			io__state::di, io__state::uo) is det.

:- pred globals__io_set_output_style(diff_out__output_style::in,
			io__state::di, io__state::uo) is det.

:- pred globals__io_lookup_option(option::in, option_data::out,
			io__state::di, io__state::uo) is det.

:- pred globals__io_set_option(option::in, option_data::in,
			io__state::di, io__state::uo) is det.

:- pred globals__io_lookup_bool_option(option, bool, io__state, io__state).
:- mode globals__io_lookup_bool_option(in, out, di, uo) is det.

:- pred globals__io_lookup_int_option(option::in, int::out,
			io__state::di, io__state::uo) is det.

:- pred globals__io_lookup_string_option(option::in, string::out,
			io__state::di, io__state::uo) is det.

:- pred globals__io_lookup_accumulating_option(option::in, list(string)::out,
			io__state::di, io__state::uo) is det.

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

:- implementation.
:- import_module map, std_util, io, require.

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

:- type globals
	--->	globals(
			option_table,		% Current options
			diff_out__output_style	% Current module name
		).

globals__init(Options, globals(Options, OutputType)) :-
	diff_out__default_output_style(OutputType).

globals__get_options(globals(Options, _), Options).

globals__set_options(globals(_, Scanner), Options, globals(Options, Scanner)).

globals__get_output_style(globals(_, Output), Output).

globals__set_output_style(globals(A, _), Output, globals(A, Output)).

globals__lookup_option(Globals, Option, OptionData) :-
	globals__get_options(Globals, OptionTable),
	map__lookup(OptionTable, Option, OptionData).

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

globals__lookup_bool_option(Globals, Option, Value) :-
	globals__lookup_option(Globals, Option, OptionData),
	( OptionData = bool(Bool) ->
		Value = Bool
	;
		error("globals__lookup_bool_option: invalid bool option")
	).

globals__lookup_string_option(Globals, Option, Value) :-
	globals__lookup_option(Globals, Option, OptionData),
	( OptionData = string(String) ->
		Value = String
	;
		error("globals__lookup_string_option: invalid string option")
	).

globals__lookup_int_option(Globals, Option, Value) :-
	globals__lookup_option(Globals, Option, OptionData),
	( OptionData = int(Int) ->
		Value = Int
	;
		error("globals__lookup_int_option: invalid int option")
	).

globals__lookup_accumulating_option(Globals, Option, Value) :-
	globals__lookup_option(Globals, Option, OptionData),
	( OptionData = accumulating(Accumulating) ->
		Value = Accumulating
	;
		error("globals__lookup_accumulating_option: invalid accumulating option")
	).

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

globals__io_init(Options) -->
	{ globals__init(Options, Globals) },
	globals__io_set_globals(Globals).

globals__io_get_globals(Globals) -->
	io__get_globals(UnivGlobals),
	{
		univ_to_type(UnivGlobals, Globals0)
	->
		Globals = Globals0
	;
		error("globals__io_get_globals: univ_to_type failed")
	}.

globals__io_set_globals(Globals) -->
	{ copy(Globals, UniqGlobals) },
	{ type_to_univ(UniqGlobals, UnivGlobals) },
	io__set_globals(UnivGlobals).

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

globals__io_lookup_option(Option, OptionData) -->
	globals__io_get_globals(Globals),
	{ globals__get_options(Globals, OptionTable) },
	{ map__lookup(OptionTable, Option, OptionData) }.

globals__io_set_option(Option, OptionData) -->
	globals__io_get_globals(Globals0),
	{ globals__get_options(Globals0, OptionTable0) },
	{ map__set(OptionTable0, Option, OptionData, OptionTable) },
	{ globals__set_options(Globals0, OptionTable, Globals) },
	globals__io_set_globals(Globals).

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

globals__io_lookup_bool_option(Option, Value) -->
	globals__io_get_globals(Globals),
	{ globals__lookup_bool_option(Globals, Option, Value) }.

globals__io_lookup_int_option(Option, Value) -->
	globals__io_get_globals(Globals),
	{ globals__lookup_int_option(Globals, Option, Value) }.

globals__io_lookup_string_option(Option, Value) -->
	globals__io_get_globals(Globals),
	{ globals__lookup_string_option(Globals, Option, Value) }.

globals__io_lookup_accumulating_option(Option, Value) -->
	globals__io_get_globals(Globals),
	{ globals__lookup_accumulating_option(Globals, Option, Value) }.

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

globals__io_get_output_style(Output) -->
	globals__io_get_globals(Globals),
	{ globals__get_output_style(Globals, Output) }.

globals__io_set_output_style(Output) -->
	globals__io_get_globals(Globals0),
	{ globals__set_output_style(Globals0, Output, Globals) },
	globals__io_set_globals(Globals).

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

New File: options.m
===================================================================
%-----------------------------------------------------------------------------%
% Copyright (C) 1997 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%

% Main author: bromage

% This module contains all the option handling code for diff.

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

:- module options.
:- interface.
:- import_module string, std_util, io, getopt.

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

:- pred options__get_option_ops(option_ops(option) :: out(option_ops)) is det.

	% Process the option table, perhaps returning an error message
	% if there was some inconsistentcy or other error.
:- pred postprocess_options(maybe_option_table :: in, maybe(string) :: out,
		io__state::di, io__state::uo) is det.

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

	% Info on the accepted options, displayed in response to --help.
:- pred options_help(io__state::di, io__state::uo) is det.

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

:- type option_table		== option_table(option).
:- type maybe_option_table	== maybe_option_table(option).

	% The master list of options.

:- type option --->
	% Output styles
	 	help
	;	version
	;	context	
	;	context_style_output
	;	unified
	;	unified_style_output
	;	ed
	;	forward_ed
	;	rcs
	;	brief
	;	ifdef
	;	side_by_side

	% Output options
	;	show_c_function		% Not handled (and unlikely to be soon)
	;	show_function_line	% Not handled (and unlikely to be soon)
	;	label
	;	width
	;	expand_tabs		% Not handled
	;	initial_tab		% Not handled
	;	paginate		% Not handled (and unlikely to be soon)
	;	left_column
	;	suppress_common_lines
	;	sdiff_merge_assist	% Accepted but ignored

	% Matching options
	;	new_file		% Not handled (and unlikely to be soon)
	;	unidirectional_new_file	% Not handled (and unlikely to be soon)
	;	ignore_case		% Not handled
	;	ignore_all_space	% Not handled
	;	ignore_space_change	% Not handled

	% Diff options
	;	minimal			% Accepted but ignored
	;	speed_large_files	% Accepted but ignored
	;	file_split_speed_hack	% Accepted but ignored (GNU diff
					% does this too, so let's not feel
					% too bad about it)

	% Change filter options
	;	ignore_matching_lines	% Not handled (and unlikely to be soon)
	;	ignore_blank_lines

	% Directory comparison options
	% None of these are likely to be handled in the near future.
	;	starting_file		% Not handled
	;	recursive		% Not handled
	;	report_identical_files	% Not handled
	;	exclude			% Not handled
	;	exclude_from		% Not handled

	% #ifdef format options
	% None of these are likely to be handled in the very near future.
	;	old_line_format		% Not handled
	;	new_line_format		% Not handled
	;	unchanged_line_format	% Not handled
	;	line_format		% Not handled
	;	old_group_format	% Not handled
	;	new_group_format	% Not handled
	;	unchanged_group_format	% Not handled
	;	changed_group_format	% Not handled
	;	horizon_lines		% Not handled

	% File input options
	% Neither of these are likely to be handled in the near future.
	;	text			% Not handled
	;	binary.			% Not handled

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

:- implementation.
:- import_module globals, diff_out.
:- import_module list, int, bool, string, map, require.

:- pred long_option(string::in, option::out) is semidet.
long_option("ignore-blank-lines",	ignore_blank_lines).
long_option("context",			context).
long_option("ifdef",			ifdef).
long_option("show-function-line",	show_function_line).
long_option("speed-large-files",	speed_large_files).
long_option("ignore-matching-lines",	ignore_matching_lines).
long_option("file-label",		label).
long_option("label",			label).
long_option("new-file",			new_file).
long_option("entire-new-file",		new_file).
long_option("unidirectional-new-file",	unidirectional_new_file).
long_option("starting-file",		starting_file).
long_option("initial-tab",		initial_tab).
long_option("unified",			unified).
long_option("width",			width).
long_option("exclude-from",		exclude_from).
long_option("text",			text).
long_option("ascii",			text).
long_option("ignore-space-change",	ignore_space_change).
long_option("minimal",			minimal).
long_option("ed",			ed).
long_option("forward-ed",		forward_ed).
long_option("ignore-case",		ignore_case).
long_option("paginate",			paginate).
long_option("print",			paginate).
long_option("rcs",			rcs).
long_option("show-c-function",		show_c_function).
long_option("brief",			brief).
long_option("recursive",		recursive).
long_option("report-identical-files",	report_identical_files).
long_option("expand-tabs",		expand_tabs).
long_option("version",			version).
long_option("ignore-all-space",		ignore_all_space).
long_option("exclude",			exclude).
long_option("side-by-side",		side_by_side).
long_option("left-column",		left_column).
long_option("suppress-common-lines",	suppress_common_lines).
long_option("sdiff-merge-assist",	sdiff_merge_assist).
long_option("old-line-format",		old_line_format).
long_option("new-line-format",		new_line_format).
long_option("unchanged-line-format",	unchanged_line_format).
long_option("line-format",		line_format).
long_option("old-group-format",		old_group_format).
long_option("new-group-format",		new_group_format).
long_option("unchanged-group-format",	unchanged_group_format).
long_option("changed-group-format",	changed_group_format).
long_option("horizon-lines",		horizon_lines).
long_option("help",			help).
long_option("binary",			binary).

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

:- pred short_option(character::in, option::out) is semidet.
short_option('B', ignore_blank_lines).
short_option('C', context).
short_option('D', ifdef).
short_option('F', show_function_line).
short_option('H', speed_large_files).
short_option('I', ignore_matching_lines).
short_option('L', label).
short_option('N', new_file).
short_option('P', unidirectional_new_file).
short_option('S', starting_file).
short_option('T', initial_tab).
short_option('U', unified).
short_option('W', width).
short_option('X', exclude_from).
short_option('a', text).
short_option('b', ignore_space_change).
short_option('c', context_style_output).
short_option('d', minimal).
short_option('e', ed).
short_option('f', forward_ed).
short_option('h', file_split_speed_hack).
short_option('i', ignore_case).
short_option('l', paginate).
short_option('n', rcs).
short_option('p', show_c_function).
short_option('q', brief).
short_option('r', recursive).
short_option('s', report_identical_files).
short_option('t', expand_tabs).
short_option('u', unified_style_output).
short_option('v', version).
short_option('w', ignore_all_space).
short_option('x', exclude).
short_option('y', side_by_side).

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

:- pred option_defaults(option :: out, option_data :: out) is nondet.

	% Output styles
option_defaults(help,				bool(no)).
option_defaults(version, 			bool(no)).
option_defaults(context,			maybe_int(no)).
option_defaults(context_style_output,		bool_special).
option_defaults(unified,			maybe_int(no)).
option_defaults(unified_style_output, 		bool_special).
option_defaults(ed,				bool(no)).
option_defaults(forward_ed,			bool(no)).
option_defaults(rcs,				bool(no)).
option_defaults(brief,				bool(no)).
option_defaults(ifdef,				maybe_string(no)).
option_defaults(side_by_side,			bool(no)).

	% Output options
option_defaults(show_c_function,		bool_special).
option_defaults(show_function_line,		string_special).
option_defaults(label,				accumulating([])).
option_defaults(width,				int(130)).
option_defaults(expand_tabs,			bool(no)).
option_defaults(initial_tab,			bool_special).
option_defaults(paginate,			bool_special).
option_defaults(left_column,			bool(no)).
option_defaults(suppress_common_lines,		bool(no)).
option_defaults(sdiff_merge_assist,		bool(no)).

	% Matching options
option_defaults(new_file,			bool_special).
option_defaults(unidirectional_new_file,	bool_special).
option_defaults(ignore_case,			bool_special).
option_defaults(ignore_all_space,		bool_special).
option_defaults(ignore_space_change,		bool_special).

	% Diff options
option_defaults(minimal,			bool(no)).
option_defaults(speed_large_files,		bool(no)).
option_defaults(file_split_speed_hack,		bool(no)).

	% Change filter options
option_defaults(ignore_matching_lines,		string_special).
option_defaults(ignore_blank_lines,		bool(no)).

	% Directory comparison options
option_defaults(starting_file,			string_special).
option_defaults(recursive,			bool_special).
option_defaults(report_identical_files,		bool_special).
option_defaults(exclude,			string_special).
option_defaults(exclude_from,			string_special).

	% Format options (none of these are handled)
option_defaults(old_line_format,		string_special).
option_defaults(new_line_format,		string_special).
option_defaults(unchanged_line_format,		string_special).
option_defaults(line_format,			string_special).
option_defaults(old_group_format,		string_special).
option_defaults(new_group_format,		string_special).
option_defaults(unchanged_group_format,		string_special).
option_defaults(changed_group_format,		string_special).
option_defaults(horizon_lines,			int_special).

	% File input options (none of these are handled)
option_defaults(text,				bool_special).
option_defaults(binary,				bool_special).

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

:- pred special_handler(option :: in, special_data :: in, option_table :: in,
		maybe_option_table :: out) is semidet.

special_handler(context_style_output, bool(yes), Options0, ok(Options)) :-
	map__lookup(Options0, context, maybe_int(Context0)),
	( Context0 = no,
		Context = yes(3)
	; Context0 = yes(C),
		Context = yes(C)
	),
	map__set(Options0, context, maybe_int(Context), Options).
special_handler(unified_style_output, bool(yes), Options0, ok(Options)) :-
	map__lookup(Options0, unified, maybe_int(Unified0)),
	( Unified0 = no,
		Unified = yes(3)
	; Unified0 = yes(C),
		Unified = yes(C)
	),
	map__set(Options0, unified, maybe_int(Unified), Options).

	% Special handlers for unhandled options
special_handler(show_c_function, _, _, error(Msg)) :-
	Msg = "Option not handled: --show-c-function".
special_handler(show_function_line, _, _, error(Msg)) :-
	Msg = "Option not handled: --show-function-line".
special_handler(expand_tabs, _, _, error(Msg)) :-
	Msg = "Option not handled: --expand-tabs".
special_handler(initial_tab, _, _, error(Msg)) :-
	Msg = "Option not handled: --initial-tab".
special_handler(paginate, _, _, error(Msg)) :-
	Msg = "Option not handled: --paginate".
special_handler(sdiff_merge_assist, _, _, error(Msg)) :-
	Msg = "Option not handled: --sdiff-merge-assist".
special_handler(new_file, _, _, error(Msg)) :-
	Msg = "Option not handled: --new-file".
special_handler(unidirectional_new_file, _, _, error(Msg)) :-
	Msg = "Option not handled: --unidirectional-new-file".
special_handler(ignore_case, _, _, error(Msg)) :-
	Msg = "Option not handled: --ignore-case".
special_handler(ignore_all_space, _, _, error(Msg)) :-
	Msg = "Option not handled: --ignore-all-space".
special_handler(ignore_space_change, _, _, error(Msg)) :-
	Msg = "Option not handled: --ignore-space-change".
special_handler(speed_large_files, _, _, error(Msg)) :-
	Msg = "Option not handled: --speed-large-files".
special_handler(ignore_matching_lines, _, _, error(Msg)) :-
	Msg = "Option not handled: --ignore-matching-lines".
special_handler(ignore_blank_lines, _, _, error(Msg)) :-
	Msg = "Option not handled: --ignore-blank-lines".
special_handler(starting_file, _, _, error(Msg)) :-
	Msg = "Option not handled: --starting-file".
special_handler(recursive, _, _, error(Msg)) :-
	Msg = "Option not handled: --recursive".
special_handler(report_identical_files, _, _, error(Msg)) :-
	Msg = "Option not handled: --report-identical-files".
special_handler(exclude, _, _, error(Msg)) :-
	Msg = "Option not handled: --exclude".
special_handler(exclude_from, _, _, error(Msg)) :-
	Msg = "Option not handled: --exclude-from".
special_handler(old_line_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --old-line-format".
special_handler(new_line_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --new-line-format".
special_handler(unchanged_line_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --unchangedline-format".
special_handler(line_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --line-format".
special_handler(old_group_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --old-group-format".
special_handler(new_group_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --new-group-format".
special_handler(unchanged_group_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --unchanged-group-format".
special_handler(changed_group_format, _, _, error(Msg)) :-
	Msg = "Option not handled: --changed-group-format".
special_handler(horizon_lines, _, _, error(Msg)) :-
	Msg = "Option not handled: --horizon-lines".
special_handler(text, _, _, error(Msg)) :-
	Msg = "Option not handled: --text".
special_handler(binary, _, _, error(Msg)) :-
	Msg = "Option not handled: --binary".

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

options__get_option_ops(OptionOps) :-
	OptionOps = option_ops(
		short_option,
		long_option,
		option_defaults,
		special_handler
	).

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

	% Postprocess the options
postprocess_options(ok(OptionTable0), Result) -->
	( { postprocess_output_style(OptionTable0, OutputStyle) } ->
		globals__io_init(OptionTable0),
		globals__io_set_output_style(OutputStyle),
		{ Result = no }
	;
		{ Result = yes("Can't set more than one output style.") }
	).
postprocess_options(error(Msg), yes(Msg)) --> [].

	% Determine which output style to use from the provided
	% options.
:- pred postprocess_output_style(option_table :: in,
		diff_out__output_style :: out) is semidet.

postprocess_output_style(OptionTable, Style) :-
	(
		map__search(OptionTable, help, bool(UseHelp)),
		map__search(OptionTable, version, bool(UseVersion)),
		map__search(OptionTable, context, maybe_int(UseContext)),
		map__search(OptionTable, unified, maybe_int(UseUnified)),
		map__search(OptionTable, ed, bool(UseEd)),
		map__search(OptionTable, forward_ed, bool(UseForwardEd)),
		map__search(OptionTable, rcs, bool(UseRCS)),
		map__search(OptionTable, brief, bool(UseBrief)),
		map__search(OptionTable, ifdef, maybe_string(UseIfdef)),
		map__search(OptionTable, side_by_side, bool(UseSideBySide))
	->
		postprocess_output_style_2(UseHelp, UseVersion, UseContext,
			UseUnified, UseEd, UseForwardEd, UseRCS, UseBrief,
			UseIfdef, UseSideBySide,
			Style)
	;
		error("postprocess_output_style")
	).

:- pred postprocess_output_style_2(bool, bool, maybe(int), maybe(int), bool,
		bool, bool, bool, maybe(string), bool,
		diff_out__output_style).
:- mode postprocess_output_style_2(in, in, in, in, in, in, in, in, in, in,
		out) is semidet.

postprocess_output_style_2(no, no, no, no, no, no, no, no, no, no,
					normal).
postprocess_output_style_2(yes, no, no, no, no, no, no, no, no, no,
					help_only).
postprocess_output_style_2(no, yes, no, no, no, no, no, no, no, no,
					version_only).
postprocess_output_style_2(no, no, yes(C), no, no, no, no, no, no, no,
					context(C)).
postprocess_output_style_2(no, no, no, yes(U), no, no, no, no, no, no,
					unified(U)).
postprocess_output_style_2(no, no, no, no, yes, no, no, no, no, no,
					ed).
postprocess_output_style_2(no, no, no, no, no, yes, no, no, no, no,
					forward_ed).
postprocess_output_style_2(no, no, no, no, no, no, yes, no, no, no,
					rcs).
postprocess_output_style_2(no, no, no, no, no, no, no, yes, no, no,
					brief).
postprocess_output_style_2(no, no, no, no, no, no, no, no, yes(Sym), no,
					ifdef(Sym)).
postprocess_output_style_2(no, no, no, no, no, no, no, no, no, yes,
					side_by_side).

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

	% Help text for the options.
options_help -->
	io__write_string("Output styles:\n"),
	io__write_string("\t--help\n"),
	io__write_string("\t\tOutput this help.\n"),
	io__write_string("\t-v, --version\n"),
	io__write_string("\t\tOutput version info.\n"),
	io__write_string("\t-c, -C <num>, --context <num>\n"),
	io__write_string("\t\tOutput <num> (default 2) lines of copied context.\n"),
	io__write_string("\t-u, -U <num>, --unified <num>\n"),
	io__write_string("\t\tOutput <num> (default 2) lines of unified context.\n"),
	io__write_string("\t\t-L <label>, --label <label>  Use <label> instead of file name.\n"),
	io__write_string("\t-e, --ed\n"),
	io__write_string("\t\tOutput an ed script.\n"),
	io__write_string("\t-f, --forward-ed\n"),
	io__write_string("\t\tProduce output similar to --ed, not useful to ed(1),\n"),
	io__write_string("\t\tand in the opposite order.\n"),
	io__write_string("\t-n, --rcs\n"),
	io__write_string("\t\tOutput an RCS format diff.\n"),
	io__write_string("\t-q, --brief\n"),
	io__write_string("\t\tOutput only whether or not files differ.\n"),
	io__write_string("\t-D <name>, --ifdef <name>\n"),
	io__write_string("\t\tProduce output in #ifdef <name> format.\n"), 
	io__write_string("\t-y, --side-by-side\n"),
	io__write_string("\t\tProduce output in side-by-side format.\n"),
	io__write_string("\t\t-w <num>, --width <num>  Output at most <num> (default 130)\n"),
	io__write_string("\t\t\tcharacters per line.\n"),
	io__write_string("\t\t--left-column  Output only the left column of common lines.\n"),
	io__write_string("\t\t--suppress-common-lines  Do not output common lines.\n"),
	io__write_string("\nMatching options:\n"),
	io__write_string("\t-d, --minimal\n"),
	io__write_string("\t\tTry hard to find as small a set of changes as possible.\n"),
	io__write_string("\t\t(Currently a no-op --- we always produce the minimal set.)\n"),
	io__write_string("\t-B, --ignore-blank-lines\n"),
	io__write_string("\t\tIgnore changes whose lines are all blank.\n").

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




More information about the developers mailing list