The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#! /usr/bin/perl -w
#
# What: A perl version of Unix ed editor.
#
#    Based on the v7 documentation using GNU ed version 0.2 as a reference.
#
#   Currently implemented:
#	- most addressing modes (".","$","#","/pat/","?pat?","[+-]#, etc.)
#	- command parsing
#	- regular expressions (using perl's built in regexps)
#	- Both regular and extended (-v) error messages
#	- The following command:
#	    a - append
#	    c - change
#	    d - delete
#	    e - edit file
#	    E - edit file - no questions asked
#	    f - set/show filename
#	    i - insert
#	    p - print
#	    P - print
#	    q - quit
#	    Q - no questions asked
#	    r - read file
#	    s - substitute regexp
#	    w - write file
#	    W - write file append
#	    = - display line number
#
#
# Why?:
#	- Because ed is "always there" (or supposed to be anyways)
#	- Because I violently dislike vi (on Unix) and NOTEPAD on WinDoze
#	- Also see the "Perl Power Tools: The Unix Reconstruction Project"
# 	  at http://language.perl.com/ppt/
#	- Because working on this is more fun than Y2K testing !
#
# Who:
#
#	George M Jones (gmj@infinet.com).  Please send changes to me.
#
# When:
#	Version 0.1
#	  - 06/09/99 - Created (note that non-Y2K compliant date !!!)
#
# How (Usage):
#	ed [-ddebugflags] [-v] [-] [file]
#
# Irony:  This was, of course, edited originaly with emacs... 
#
# Commentary:
#	The guiding principals of this implentation are:
#		- Functionality
#		- Understandability
#
#	"perl coolness" was not a guiding principal, as the author is at best
#	an accomplished perl user.  If you like the program, use it.
#	If you don't, don't.
#
# Legaleese:
#	This program is released under the GNU Public License.
#
# Todo:
#	- Implement the following commands from the v7 docs:
#		g - global command
#		j - join
#		k - mark
#		l - "list" lines, show special chars
#		m - move
#		t - move/apend
#		u - undo
#		v - global command "inVerted"
#		x - get encryption key
#		! - shell command
#
#	- Create regression test suite...test against "real" ed.
#	- add a "-e" flag to allow it to be used in sed(1) like fashion.
#	- add buffer size limitations for strict compatability
#	- Save buffer on SIGHUP (meaningless on Windoz ?)
#	- discard NULS, chars after \n
#	- refuse to read non-ascii files
#	- Add BSD/GNU extentions
#	- add POD documentation
#

use Getopt::Std;

# important globals

$maxline = 0;		# lines are numered internally starting at 1
$CurrentLineNum = 0;	# default to before first line.
$RememberedFilename = ""; # the default filename for writes, etc.
$NeedToSave = 0;	# buffer modified
$UserHasBeenWarned = 0; # has the user been warned about attempt
			# to trash buffer/file?
@lines = ();		# buffer for file being edited.
$command = "";		# single letter command entered by user
@adrs = ();		# array of line numbers (1 or 2) for command to operate on
@args = ();		# command arguments (filenames, search patterns...)

# configuration vars

$EXTENDED_MESSAGES = 0; # use useful error messages, not ed compatability messages

# constants

$NO_APPEND_MODE = 0;
$NO_INSERT_MODE = 0;
$INSERT_MODE = 1;
$APPEND_MODE = 2;
$QUESTIONS_MODE = 1;
$NO_QUESTIONS_MODE = 0;

$VERSION = "0.1";

#
# Parse command line ... 
# 
#

$opt_v = 0;
$opt_d = "";
$SupressCounts = 0;

unless (getopts('d:v')) {
    $EXTENDED_MESSAGES = 1; 
    &edDie("Usgae: ed [-v] [-ddebugstring] [-] [filename]","\?",1)
}

if ($opt_v) {
    $EXTENDED_MESSAGES = 1; 
}

print "@ARGV\n";
&edDie("Usgae: ed [-v] [-ddebugstring] [-] [filename]","\?",1)
    if ($#ARGV > 1);

while ($_ = shift) {
    if (/-/) {
	$SupressCounts = 1;
    } else {
	$args[0] = $_;
    }
}

if ($EXTENDED_MESSAGES) {
    print STDERR "perl ed version $VERSION\n";
}
&edEdit($NO_QUESTIONS_MODE,$NO_APPEND_MODE);

#
# parse commands and execute them...
#


while(<>) {
    chomp;

    if (&edParse) {

	if ($opt_d =~ /parse/) {
	    print "command: /$command/ ";
	    print "adrs[0]: /$adrs[0]/ " if defined($adrs[0]);
	    print "adrs[1]: /$adrs[1]/ " if defined($adrs[1]);
	    print "args[0]: /$args[0]/ " if defined($args[0]);
	    print "\n";
	}

	# sanity check addresses

	if (defined($adrs[0])) {
	    if ($adrs[0] > $maxline || $adrs[0] < 0) {
		edWarn("address out of range","\?");
		next;
	    }
	}

	if (defined($adrs[1])) {
	    if ($adrs[1] > $maxline || $adrs[1] < 0) {
		edWarn("address out of range","\?");
		next;
	    }
	}

	if (defined($adrs[0]) and defined($adrs[1])) {
	    unless ($adrs[1] >= $adrs[0]) {
		edWarn("Second address ($adrs[0]) must be >= first ($adrs[1])","\?");
		next;
	    }
	}

	#
	# To add a new command:
	#   - add to the following if...elsif... list,
	#   - add to the command parsing regexp in edParse
	#   - add a new routine to implement the command
	#   - do command specific checks of adrs and args in the routine
	#   - have command operate on important globals (see top of file),
	#     such as @lines, $CurrentLineNum, $maxline, $NeedToSave...
	#

	# navigation operations

	if ($command =~ /^$/) {
	    &edSetCurrentLine;
	} elsif ($command =~ /^=$/) {
	    &edPrintLineNum;

	# file operations

	} elsif ($command =~ /^[w]$/) {
	    &edWrite($NO_APPEND_MODE);
	} elsif ($command =~ /^[W]$/) {
	    &edWrite($APPEND_MODE);
	} elsif ($command =~ /^e$/) {
	    &edEdit($QUESTIONS_MODE,$NO_INSERT_MODE);
	} elsif ($command =~ /^E$/) {
	    &edEdit($NO_QUESTIONS_MODE,$NO_INSERT_MODE);
	} elsif ($command =~ /^r$/) {
	    &edEdit($QUESTIONS_MODE,$INSERT_MODE);
	} elsif ($command =~ /^f$/) {
	    &edFilename;

	# text manipulation commands

	} elsif ($command =~ /^[d]$/) {
	    &edDelete;
	} elsif ($command =~ /^i$/) {
	    &edInsert($INSERT_MODE);
	} elsif ($command =~ /^a$/) {
	    &edInsert($APPEND_MODE);
	} elsif ($command =~ /^c$/) {
	    &edDelete;
	    $adrs[1] = undef;
	    &edInsert($INSERT_MODE);
	} elsif ($command =~ /^s$/) {
	    &edSubstitute;

	# misc commands

	} elsif ($command =~ /^[pP]$/) {
	    &edPrint;
	} elsif ($command =~ /^q$/) {
	    &edQuit($QUESTIONS_MODE);
	} elsif ($command =~ /^Q$/) {
	    &edQuit($NO_QUESTIONS_MODE);
	}
	
    } else {
	edWarn("unrecognized command", "\?");
    }


}

#
# Print contents of requested lines
#

sub edPrint {

    $adrs[0] = $CurrentLineNum unless (defined($adrs[0]));
    $adrs[1] = $CurrentLineNum unless (defined($adrs[1]));

    unless ($adrs[1] >= $adrs[0]) {
	edWarn("Second address must be >= first","\?");
	return;
    }

    if (defined($args[0])) {
	edWarn("Extra arguments detected","\?");
	return;
    }

    for $i ($adrs[0]..$adrs[1]) {
	print "$lines[$i]";
    }

    $CurrentLineNum = $adrs[1];
}

#
# Perform text substitution
#

sub edSubstitute {
    my($LastMatch,$char,$first,$middle,$last,$whole,$flags,$PrintLastLine);

    $PrintLastLine = 0;

    # parse args

    $adrs[0] = $CurrentLineNum unless (defined($adrs[0]));
    $adrs[1] = $CurrentLineNum unless (defined($adrs[1]));

    $NumLines = $adrs[1]-$adrs[0]+1;

    unless ($adrs[1] >= $adrs[0]) {
	edWarn("Second address must be >= first","\?");
	return;
    }

    unless (defined($args[0])) {
	edWarn("Need a substitution string","\?");
	return;
    }

    # do wierdness to match semantics if last character
    # is present or absent

    $args[0] =~ /(.).*/;
    $char = $1;
    ($whole,$first,$middle,$last,$flags) = ($args[0] =~ /(($char)[^"$char"]*($char)[^"$char"]*($char)?)([imsx]*)/);

    if (defined($char) and defined($whole) and
	($flags eq "") and (not defined($last))) {
	$args[0] .= "$char";
	$PrintLastLine = 1;
    }
	
    # do the search and substitution

    $LastMatch = $CurrentLineNum;

    for $lineno ($adrs[0]..$adrs[1]) {
	$evalstring = "\$lines[\$lineno] =~ s$args[0]";

	if (eval $evalstring) {
	    $LastMatch = $lineno;
	    $NeedToSave = 1;
	    $UserHasBeenWarned = 0;
	}

    }

    $CurrentLineNum = $LastMatch;

    print $lines[$LastMatch] if ($PrintLastLine);
}

#
# Delete requested lines
#

sub edDelete {
    my($Numlines);


    $adrs[0] = $CurrentLineNum unless (defined($adrs[0]));
    $adrs[1] = $CurrentLineNum unless (defined($adrs[1]));

    $NumLines = $adrs[1]-$adrs[0]+1;

    unless ($adrs[1] >= $adrs[0]) {
	edWarn("Second address must be >= first","\?");
	return;
    }

    splice(@lines,$adrs[0],$NumLines);

    $NeedToSave = 1;
    $UserHasBeenWarned = 0;
    $maxline -= $NumLines; 
    $CurrentLineNum = $adrs[0] >= $maxline ? $maxline : $adrs[0]+1;
}

#
# Print or set filename
#

sub edFilename {
 

    if (defined($adrs[0]) or defined($adrs[1])) {
	edWarn("too many addresses for command: $#adrs (@adrs)", "\?");
	return;
    }

    if (defined($args[0])) {
	$RememberedFilename = $args[0];
    } elsif (defined($RememberedFilename)) {
	print "$RememberedFilename\n";
    }
}

#
# Write requested lines
#

sub edWrite {
    my($AppendMode) = @_;
    my($Numlines,$filename,$chars);

    $chars = 0;

    $adrs[0] = 1 unless (defined($adrs[0]));
    $adrs[1] = $maxline unless (defined($adrs[1]));

    $filename = defined($args[0]) ? $args[0] : $RememberedFilename;

    if (defined($filename)) {
	$RememberedFilename = $filename;
    } else {
	edWarn("no default filename","\?");
    }

    $NumLines = $adrs[1]-$adrs[0]+1;


    if ($AppendMode) {
	unless (open(FILE,">>$filename")) {
	    edWarn("Unable to open $filename for writing: $!","\?");
	    return;
	}
    } else {
	unless (open(FILE,">$filename")) {
	    edWarn("Unable to open $filename for writing: $!","\?");
	    return;
	}
    }

    for $line (@lines[$adrs[0]..$adrs[1]]) {
	print FILE $line;
	$chars += length($line);
    }

    $NeedToSave = 0;
    $UserHasBeenWarned = 0;
    print "$chars\n" unless ($SupressCounts);
    close(FILE);

    # v7 docs say to chmod 666 the file ... we're not going to
    # follow the docs *that* closely today (6/16/99 ---gmj)
}


#
# Read in the named file
#
# return:
#	0 - failure
#	1 - success

sub edEdit {
    my($QuestionsMode,$InsertMode) = @_;
    my(@tmp_lines,@tmp_lines2,$tmp_maxline,$tmp_chars);

    if ($InsertMode) {
	$adrs[0] = $maxline unless (defined($adrs[0]));

	if (defined($args[1])) {
	    edWarn("Too many addressses", "\?");
	    return;
	}

    } else {
	if (defined($adrs[0]) or defined($adrs[1])) {
	    edWarn("too many addresses for command: $#adrs (@adrs)", "\?");
	    return;
	}
    }

    $filename = defined($args[0]) ? $args[0] : $RememberedFilename;
    $RememberedFilename = $filename;

    if ($filename eq "" ) {
	$CurrentLineNum = 0;
	$maxline = 0;
	return 1;
    }

    # suck the file into a temp array

    unless (open(SOURCE, "<$filename")) {
	edWarn ("unable to open $filename: $!","\?");
	return 0;
    }
    
    @tmp_lines = ();
    $tmp_maxline = 0;
    $tmp_chars = 0;
	
    while(<SOURCE>) {
	push(@tmp_lines,$_);
	$tmp_chars += length;
	$tmp_maxline++;
	}

    close(SOURCE);

    # now that we've got it, figure out what to do with it

    if ($InsertMode) {

	if ($adrs[0] == $maxline) {
	    push(@lines,@tmp_lines);
	    $CurrentLineNum += $tmp_maxline;
	} elsif ($adrs[0] == 0) {
	    shift(@lines); # get rid of undefined line
	    unshift(@lines,@tmp_lines);
	    unshift(@lines,undef); # put 0 line back
	    $CurrentLineNum = $tmp_maxline;
	} else {
	    shift(@lines); # get rid 0 line
	    @tmp_lines2 = splice(@lines,0,$adrs[0]);
	    unshift(@lines,@tmp_lines);
	    unshift(@lines,@tmp_lines2);
	    $CurrentLineNum += ($tmp_maxline);
	    unshift(@lines,undef); # put 0 line back
	}

        $maxline += $tmp_maxline;
	$chars = $tmp_chars;
	$NeedToSave = 1;
	$UserHasBeenWarned = 0;

	
    } else {
	if ($NeedToSave) {
	    if ($QuestionsMode) {
		unless ($UserHasBeenWarned) {
		    $UserHasBeenWarned = 1;
		    edWarn("file modified","\?");
		    return;
		}
	    }
	}
	
	@lines = @tmp_lines;
	unshift(@lines,undef); # line 0 is not used
	$maxline = $tmp_maxline;
	$chars = $tmp_chars;
	$NeedToSave = 0;
	$UserHasBeenWarned = 0;
	$CurrentLineNum = $maxline;
    }
    
    unless ($lines[$maxline] =~ /\n$/) {
	$lines[$maxline] .= "\n";
	print "Newline appended\n";
    }
    
    print "$chars\n" unless ($SupressCounts);

    return 1;
}

#
# Insert some text
#

sub edInsert {
    my($Mode) = @_;
    my(@tmp_lines,@tmp_lines2,$tmp_maxline,$tmp_chars);

    if (defined($args[0])) {
	edWarn("Extra arguments detected","\?");
	return;
    }

    $adrs[0] = $CurrentLineNum unless (defined($adrs[0]));

    if (defined($args[1])) {
	edWarn("Too many addressses", "\?");
	return;
    }

    if ($adrs[0] == 0 and ($Mode == $INSERT_MODE)) {
	edWarn("Can't insert before line 0","\?");
	return;
    }

    # suck the text into a temp array

    @tmp_lines = ();
    $tmp_maxline = 0;
    $tmp_chars = 0;
	
    while(<>) {
	last if (/^\.$/);
	push(@tmp_lines,$_);
	$tmp_chars += length;
	$tmp_maxline++;
	}

    # now that we've got it, figure out what to do with it

    if ($Mode == $INSERT_MODE) {

	if ($adrs[0] == 1) {
	    shift(@lines); # get rid of undefined line
	    unshift(@lines,@tmp_lines);
	    unshift(@lines,undef); # put 0 line back
	    $CurrentLineNum = $tmp_maxline;
	} else {
	    shift(@lines); # get rid 0 line
	    @tmp_lines2 = splice(@lines,0,$adrs[0]-1);
	    unshift(@lines,@tmp_lines);
	    unshift(@lines,@tmp_lines2);
	    $CurrentLineNum = $adrs[0] + $tmp_maxline - 1;
	    unshift(@lines,undef); # put 0 line back
	}

    } elsif ($Mode == $APPEND_MODE) {

	if ($adrs[0] == 0) {
	    shift(@lines); # get rid of undefined line
	    unshift(@lines,@tmp_lines);
	    unshift(@lines,undef); # put 0 line back
	    $CurrentLineNum = $tmp_maxline;
	} else {
	    shift(@lines); # get rid 0 line
	    @tmp_lines2 = splice(@lines,0,$adrs[0]);
	    unshift(@lines,@tmp_lines);
	    unshift(@lines,@tmp_lines2);
	    $CurrentLineNum = $adrs[0] + $tmp_maxline;
	    unshift(@lines,undef); # put 0 line back
	}

    }

    $maxline += $tmp_maxline;

    if ($tmp_chars) {
	$NeedToSave = 1;
	$UserHasBeenWarned = 0;
    }

    return 1;
}


#
#  Print current line number
#

sub edPrintLineNum {
    my($adr);

    if (defined($args[0])) {
	edWarn("Extra arguments detected","\?");
	return;
    }

    for $i (0..1) {
	$adr = $adrs[$i] if (defined($adrs[$i]));
    }

    $adr = $maxline unless (defined($adr));

    print "$adr\n";

    # v7 docs say this does not affect current line.  GNU ed sets the line.
    # We go with the v7 docs.
}

#
# Quit ed
#

sub edQuit {
    my($QuestionMode) = @_;

    if (defined($args[0])) {
	edWarn("Extra arguments detected","\?");
	return;
    }

    if ($QuestionMode) {
	if ($NeedToSave) {
	    unless ($UserHasBeenWarned) {
		edWarn("Current buffer has been modified but not saved","\?");
		$UserHasBeenWarned = 1;
		return;
	    }
	}
    }


    exit 0;
}

#
# Set cuurent line
#
# Input:
#	$adrs[0] - the requested new current line
#	$maxline - the maxline
#	@lines - the buffer
#
# Side effects:
#	1. $CurrentLineNum is set
#	2. The new current line is printed.

sub edSetCurrentLine {

    my($i,$adr);

    if (defined($args[0])) {
	edWarn("Extra arguments detected","\?");
	return;
    }

    for $i (0..1) {
	$adr = $adrs[$i] if (defined($adrs[$i]));
    }

    if (defined($adr)) {

	# user gave us a line, go to it

	if (($adr <= $maxline) && ($adr > 0) && ($maxline > 0)) {
	    $CurrentLineNum = $adr;
	} else {
	    edWarn("requested line ($adr) out of range: 1-$maxline","\?");
	    return 0;
	}
	
    } else {

	# simply increment the line

	if ($CurrentLineNum < $maxline) {
	    $CurrentLineNum++;
	} else {
	    edWarn("already at end of file","\?");
	    return 0;
	}
    }

    print "$lines[$CurrentLineNum]";

    return 1;
}

#
# Parse the next command
#
# Input: $_
#
# Output:
#	@adrs - the line number(s) of the lines on the input
#	$command - single character command
#
# Return:
#	1 - success
#	0 - parse failure
#

sub edParse {

    #
    # Parse commands....and yes, this could be done a lot more elegantly
    # with fancy regexps
    #

    @adrs = ();

    @fields =
	     (/^(
		 (
		  ((\d+)|(\.)|(\$)|([\/\?]([^\/\?+-]+)[\/\?]?))?
					# num,.,$,pattern
		  (([+-])?(\d+))? 	# [+]num | -num
		  (([\+]+)|([-^]+))?	# + | -
	        )			# first expression
	        (,			# comma between adrs
		  ((\d+)|(\.)|(\$)|([\/\?]([^\/\?+-]+)[\/\?]?))?
					# num,.,$,pattern
		  (([+-])?(\d+))? 	# [+]num | -num
		  (([\+]+)|([-^]+))?	# + | -
		)?
		 ([acdeEfipPqQrswW=])?	# command char
		 \s*(\S+)?		# argument (filename, etc.)
		 )$/x);


    return 0 if ($#fields == -1);  # bad syntax

    if (defined($fields[27])) {
	$command = $fields[27];
    } else {
	$command = "";

    }

    $args[0] = $fields[28];

    $adrs[0] = &CalculateLine(splice(@fields,1,13));
    $adrs[1] = &CalculateLine(splice(@fields,1,13));

    
    return 1;
}

#
# Given a parsed address expression, calcuate & return the indicated line 
#

sub CalculateLine {

    my($expr1,
       $adrexpr1,
       $decimaladr,$dotadr,$dollaradr,
       $wholesearch,$searchadr,
       $offsetexpr,$offsetdir,$offsetammount,
       $plusesorminusesexpr,$pluses,$minuses) = @_;

    my($myline);

    $myline = undef;

    if ($opt_d =~ /parse/) {
	print "expr1=/$expr1/\n" if (defined($expr1));
	print "decimaladr=/$decimaladr/\n" if (defined($decimaladr));
	print "dotadr=/$dotadr/\n" if (defined($dotadr));
	print "dollaradr=/$dollaradr/\n" if (defined($dollaradr));
	print "wholesearch=/$wholesearch/\n" if (defined($wholesearch));
	print "searchadr=/$searchadr/\n" if (defined($searchadr));
	print "offsetexpr=/$offsetexpr/\n" if (defined($offsetexpr));
	print "offsetdir=/$offsetdir/\n" if (defined($offsetdir));
	print "offsetammount=/$offsetammount/\n"
	    if (defined($offsetammount));
	print "plusesorminusesexpr=/$plusesorminusesexpr/\n"
	    if (defined($plusesorminusesexpr));
	print "pluses=/$pluses/\n" if (defined($pluses));
	print "minuses=/$minuses/\n" if (defined($minuses));
    }

    if (defined($expr1)) {
	if (defined($decimaladr)) {
	    $myline = $decimaladr;
	} elsif (defined($dotadr)) {
	    $myline = $CurrentLineNum; 
	} elsif (defined($dollaradr)) {
	    $myline = $maxline; 
	} elsif (defined($searchadr)) {
	    
	    $pattern = $searchadr;
	    $pattern =~ s/\/$//;
	    $pattern =~ s/^\///;
	    
	    
	    if ($wholesearch =~ /^\/.*\/$/) {
		$myline = edSearchForward($pattern);
		} else {
		    $myline = edSearchBackward($pattern);
		}
	    
#	    $command = "";
	}
    }
    
    if (defined($offsetexpr)) {
	$myline = $CurrentLineNum unless defined($myline);
	
	if ($offsetdir =~ /^-$/) {
	    $myline -= $offsetammount;
	} else {
	    $myline += $offsetammount;
	} 
    }
    
    if (defined($plusesorminusesexpr)) {
	$myline = $CurrentLineNum unless defined($myline);
	
	if (defined($pluses)) {
	    $myline += length($pluses);
	} elsif (defined($minuses)) {
	    $myline -= length($minuses);
	} 
	
    }

    return $myline;
}

#
# Search forward for a pattern...wrap if not found
#
# Inputs:
#	pattern		- via argument
#	CurrentLineNum	- global
#	maxline		- global
#	lines		- global
#
# Return:
#	0		- not found
#	>0		- line where first found
#

sub edSearchForward {
    my($pattern) = @_;
    my($line,$start);

    $start = $CurrentLineNum < $maxline ? $CurrentLineNum : 1;

    for $line ($start..$maxline,1..$CurrentLineNum) {

	if ($lines[$line] =~ /$pattern/) {
	    return $line;
	}
    }

    return 0;
}

#
# Search backward for a pattern...wrap if not found
#
# Inputs:
#	pattern		- via argument
#	CurrentLineNum	- global
#	maxline		- global
#	lines		- global
#
# Return:
#	0		- not found
#	>0		- line where first found
#

sub edSearchBackward {
    my($pattern) = @_;
    my($line,$start);

    # brute force search...

    $start = $CurrentLineNum > 1 ? $CurrentLineNum-1 : $maxline;

    $line = $start;

    while ($line > 0) {
	if ($lines[$line] =~ /$pattern/) {
	    return $line;
	}	
	$line--; 
    }

    $line = $maxline;
    while ($line >= $CurrentLineNum) {
	if ($lines[$line] =~ /$pattern/) {
	    return $line;
	}	
	$line--; 
    }

    return 0;
}


#
# Exit with error
#
# edDie($msg,$status)
#

sub edDie {
    my($useful_msg,$original_ed_msg,$status) = @_;


    if ($EXTENDED_MESSAGES) {
	printf STDERR "$useful_msg\n";

    } else {
	print STDERR "$original_ed_msg\n";
    }

    exit $status;
}

#
# Exit with error
#
# edWarn($msg,$original_ed_msg)
#

sub edWarn {
    my($useful_msg,$original_ed_msg) = @_;


    if ($EXTENDED_MESSAGES) {
	printf STDERR "$useful_msg\n";
    } else {
	print STDERR "$original_ed_msg\n";
    }
}