BSD 4_4 release
[unix-history] / usr / src / contrib / ed / main.c
/*-
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Rodney Ruddock of the University of Guelph.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char copyright[] =
"@(#) Copyright (c) 1992, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
static char sccsid[] = "@(#)main.c 8.1 (Berkeley) 5/31/93";
#endif /* not lint */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <regex.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef DBI
#include <db.h>
#endif
#include "ed.h"
#include "extern.h"
/*
* This is where all of the "global" variables are declared. They are
* set for extern in the ed.h header file (so everyone can get them).
*/
int nn_max, nn_max_flag, Start_default, End_default, address_flag;
int zsnum, filename_flag, add_flag=0, join_flag=0;
int help_flag=0, gut_num=-1;
#ifdef STDIO
FILE *fhtmp;
int file_seek;
#endif
#ifdef DBI
DB *dbhtmp;
#endif
LINE *nn_max_start, *nn_max_end;
struct MARK mark_matrix[26]; /* in init set all to null */
char *text;
LINE **gut=NULL;
char *filename_current, *prompt_string=NULL, help_msg[130];
char *template=NULL;
int prompt_str_flg=0, start_up_flag=0, name_set=0;
LINE *top, *current, *bottom, *Start, *End;
struct u_layer *u_stk;
struct d_layer *d_stk;
LINE *u_current, *u_top, *u_bottom;
int u_set;
regex_t RE_comp;
regmatch_t RE_match[RE_SEC];
int RE_sol=0, RE_flag=0;
char *RE_patt=NULL;
int ss; /* for the getc() */
int explain_flag=1, g_flag=0, GV_flag=0, printsfx=0, exit_code=0;
long change_flag=0L;
int line_length;
jmp_buf ctrl_position, ctrl_position2, ctrl_position3; /* For SIGnal handling. */
int sigint_flag, sighup_flag, sigspecial=0, sigspecial2=0, sigspecial3=0;
static void sigint_handler __P((int));
static void sighup_handler __P((int));
/*
* Starts the whole show going. Set things up as the arguments spec
* in the shell and set a couple of the global variables.
*
* Note naming viol'n with errnum for consistancy.
*/
int
main(argc, argv)
int argc;
char *argv[];
{
int l_num, errnum=0, l_err=0;
char *l_fnametmp, *l_col, buf[2];
struct winsize win;
setbuffer(stdin, buf, 1);
line_length = ((l_col = getenv("COLUMNS")) == NULL ? 0 : atoi(l_col));
if ((line_length == 0 && isatty(STDOUT_FILENO) &&
ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1))
line_length = win.ws_col;
if (line_length == 0)
line_length = 78;
line_length -= 3; /* for the octal to break properly in 'l' */
Start = End = NULL;
top = bottom = NULL;
current = NULL;
nn_max_flag = 0;
nn_max_start = nn_max_end = NULL;
l_fnametmp = calloc(FILENAME_LEN, sizeof(char));
if (l_fnametmp == NULL)
ed_exit(4);
text = calloc(NN_MAX_START + 2, sizeof(char));
if (text == NULL)
ed_exit(4);
Start_default = End_default = 0;
zsnum = 22; /* for the 'z' command */
help_msg[0] = '\0';
u_stk = NULL;
d_stk = NULL;
u_current = u_top = u_bottom = NULL;
u_set = 0; /* for in d after a j */
filename_flag = 0;
filename_current = NULL;
l_num = 1;
for (;;) {
/* Process the command line options */
if (l_num >= argc)
break;
switch (argv[l_num][0]) {
case '-':
switch (argv[l_num][1]) {
case '\0': /* this is why 'getopt' not used */
case 's':
explain_flag = 0;
break;
case 'p':
if (++l_num < argc) {
prompt_string =
calloc(strlen(argv[l_num]),
sizeof(char));
if (prompt_string == NULL)
ed_exit(4);
strcpy(prompt_string, argv[l_num]);
prompt_str_flg = 1;
break;
}
l_err = 1;
case 'v':
#ifdef BSD
(void)printf("ed: in BSD mode:\n");
#endif
#ifdef POSIX
(void)printf("ed: in POSIX mode:\n");
#endif
break;
default:
l_err++;
ed_exit(l_err);
}
break;
default:
if (name_set)
ed_exit(3);
strcpy(l_fnametmp, argv[l_num]);
filename_current = l_fnametmp;
name_set = 1;
if (prompt_str_flg)
break;
/* default ed prompt */
prompt_string = (char *) calloc(3, sizeof(char));
strcpy(prompt_string, "*");
break;
}
l_num++;
}
start_up_flag = 1;
cmd_loop(stdin, &errnum);
/* NOTREACHED */
}
/*
* The command loop. What the command is that the user has specified
* is determined here. This is not just for commands coming from
* the terminal but any standard i/o stream; see the global commands.
* Some of the commands are handled within here (i.e. 'H') while most
* are handled in their own functions (as called).
*/
void
cmd_loop(inputt, errnum)
FILE *inputt;
int *errnum;
{
LINE *l_tempp;
int l_last, l_jmp_flag;
l_last = 0; /* value in l_last may be clobbered (reset to = 0) by longjump, but that's okay */
if (g_flag == 0) { /* big, BIG trouble if we don't check! think. */
/* set the jump point for the signals */
l_jmp_flag = setjmp(ctrl_position);
signal(SIGINT, sigint_handler);
signal(SIGHUP, sighup_handler);
switch (l_jmp_flag) {
case JMP_SET:
break;
/* Some general cleanup not specific to the jmp pt. */
case INTERUPT:
sigint_flag = 0;
GV_flag = 0; /* safest place to do these flags */
g_flag = 0;
(void)printf("\n?\n");
break;
case HANGUP: /* shouldn't get here. */
break;
default:
(void)fprintf(stderr, "Signal jump problem\n");
}
/* Only do this once! */
if (start_up_flag) {
start_up_flag = 0;
/* simulate the 'e' at startup */
e2(inputt, errnum);
if (*errnum == 0)
goto errmsg2;
}
}
for (;;) {
if (prompt_str_flg == 1)
(void)printf("%s", prompt_string);
ss = getc(inputt);
*errnum = 0;
l_tempp = Start = End = NULL;
Start_default = End_default = 1;
/*
* This isn't nice and alphabetical mainly because of
* restrictions with 'G' and 'V' (see ed(1)).
*/
for (;;) {
switch (ss) {
case 'd':
d(inputt, errnum);
break;
case 'e':
case 'E':
e(inputt, errnum);
if (*errnum == 0)
goto errmsg2;
break;
case 'f':
f(inputt, errnum);
break;
case 'a':
case 'c':
case 'i':
case 'g':
case 'G':
case 'v':
case 'V':
if (GV_flag == 1) {
(void)sprintf(help_msg,
"command `%c' illegal in G/V", ss);
*errnum = -1;
break;
}
switch (ss) {
case 'a':
a(inputt, errnum);
break;
case 'c':
c(inputt, errnum);
break;
case 'i':
i(inputt, errnum);
break;
default:
g(inputt, errnum);
}
break;
case 'h':
if (rol(inputt, errnum))
break;
if (help_msg[0])
(void)printf("%s\n", help_msg);
*errnum = 1;
break;
case 'H':
if (rol(inputt, errnum))
break;
if (help_flag == 0) {
help_flag = 1;
if (help_msg[0])
(void)printf("%s\n",
help_msg);
} else
help_flag = 0;
*errnum = 1;
break;
case 'j':
j(inputt, errnum);
break;
case 'k':
set_mark(inputt, errnum);
break;
case 'l':
l(inputt, errnum);
break;
case 'm':
m(inputt, errnum);
break;
#ifdef POSIX
/* In POSIX-land 'P' toggles the prompt. */
case 'P':
if (rol(inputt, errnum))
break;
prompt_str_flg = prompt_str_flg ? 0 : 1;
*errnum = 1;
break;
#endif
case '\n':
if (GV_flag == 1)
return;
/* For 'p' to consume. */
ungetc(ss, inputt);
if ((current == bottom) && (End == NULL)) {
strcpy(help_msg, "at end of buffer");
*errnum = -1;
break;
}
current = current->below;
#ifdef BSD
/* In BSD 'P'=='p'. */
case 'P':
#endif
case 'p':
p(inputt, errnum, 0);
break;
case 'n':
p(inputt, errnum, 1);
break;
/*
* An EOF means 'q' unless we're still in the middle
* of a global command, in which case it was just the
* end of the command list found.
*/
case EOF:
clearerr(inputt);
if (g_flag > 0)
return;
/*ss = 'q';*/
case 'q':
case 'Q':
if ((!isatty(STDIN_FILENO)) && (ss == 'q'))
ss = 'Q';
q(inputt, errnum);
break;
case 'r':
r(inputt, errnum);
if (*errnum == 0)
goto errmsg2;
break;
case 's':
s(inputt, errnum);
break;
case 't':
t(inputt, errnum);
break;
case 'u':
u(inputt, errnum);
break;
case 'w':
case 'W':
w(inputt, errnum);
break;
case 'z':
z(inputt, errnum);
break;
case '!':
bang(inputt, errnum);
break;
case '=':
equal(inputt, errnum);
break;
/*
* Control of address forms from here down.
*
* It's a head-game to understand why ";" and "," look
* as they do below, but a lot of it has to do with ";"
* and "," being special address pair forms themselves
* and the compatibility for address "chains".
*/
case ';':
if (End_default == 1 && Start_default == 1) {
Start = current;
End = bottom;
Start_default = End_default = 0;
} else {
Start = current = End;
Start_default = 0;
End_default = 1;
}
l_tempp = NULL;
break;
/*
* Note address ".,x" where x is a cmd is legal; not a
* bug - for backward compatability.
*/
case ',':
if (End_default == 1 && Start_default == 1) {
Start = top;
End = bottom;
Start_default = End_default = 0;
} else {
Start = End;
Start_default = 0;
End_default = 1;
}
l_tempp = NULL;
break;
case '%':
if (End_default == 0) {
strcpy(help_msg,
"'%' is an address pair");
*errnum = -1;
break;
}
Start = top;
End = bottom;
Start_default = End_default = 0;
l_tempp = NULL;
break;
/*
* Within address_conv => l_last = '+', foobar, but
* historical and now POSIX...
*/
case ' ':
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
case '^':
case '+':
case '\'':
case '$':
case '?':
case '/':
case '.':
ungetc(ss, inputt);
if (Start_default == 0 && End_default == 0) {
strcpy(help_msg,
"badly formed address");
*errnum = -1;
break;
}
ss = l_last;
l_tempp = address_conv(l_tempp, inputt, errnum);
if (*errnum < 0)
break;
End = l_tempp;
End_default = 0;
if (Start_default == 0)
*errnum = address_check(Start, End);
break;
default:
*errnum = -1;
strcpy(help_msg, "unknown command");
break;
} /* end-switch(ss) */
/* Things came out okay with the last command. */
if (*errnum > 0) {
if (GV_flag == 1)
return;
/* Do the suffixes if there were any. */
if (printsfx > 0) {
Start = End = current;
ungetc(ss, inputt);
if (printsfx == 1)
p(inputt, errnum, 0);
else
if (printsfx == 2)
p(inputt, errnum, 1);
else if (printsfx == 4)
l(inputt, errnum);
/* Unlikely it's needed, but... */
if (*errnum < 0)
goto errmsg;
}
break;
}
/* There was a problem with the last command. */
else if (*errnum < 0) {
errmsg: while (((ss = getc(inputt)) != '\n') &&
(ss != EOF));
(void)printf("?\n");
errmsg2: if (help_flag)
(void)printf("%s\n", help_msg);
exit_code = 4;
/* for people wanting scripts to carry on after a cmd error, then
* define NOENDONSCRIPT on the compile line.
*/
#ifndef NOENDONSCRIPT
if (!isatty(STDIN_FILENO)) {
ss = 'Q';
ungetc('\n', inputt);
q(inputt, errnum);
}
#endif
break;
}
l_last = ss;
ss = getc(inputt);
}
}
}
/*
* Exits ed and prints an appropriate message about the command line
* being malformed (see below).
*/
void
ed_exit(err)
int err;
{
switch (err) {
case 1:
(void)fprintf(stderr, "ed: illegal option\n");
break;
case 2:
(void)fprintf(stderr, "ed: missing promptstring\n");
break;
case 3:
(void)fprintf(stderr, "ed: too many filenames\n");
break;
case 4:
(void)fprintf(stderr, "ed: out of memory error\n");
break;
case 5:
(void)fprintf(stderr, "ed: unable to create buffer\n");
break;
default:
(void)fprintf(stderr, "ed: command line error\n");
break;
}
(void)fprintf(stderr,
"ed: ed [ -s ] [ -p promptstring ] [ filename ]\n");
exit(1);
}
/*
* SIGINT is never turned off. We flag it happened and then pay attention
* to it at certain logical locations in the code we don't do more here
* cause some of our buffer pointer's may be in an inbetween state at the
* time of the SIGINT. So we flag it happened, let the local fn handle it
* and do a jump back to the cmd_loop
*/
static void
sigint_handler(signo)
int signo;
{
sigint_flag = 1;
if (sigspecial3) {
sigspecial3 = 0;
SIGINT_ILACTION;
}
else
if (sigspecial);
else
SIGINT_ACTION;
}
static void
sighup_handler(signo)
int signo;
{
sighup_flag = 1;
undo();
do_hup();
/* NOTREACHED */
SIGHUP_ACTION;
}