# define INCLUDE_CTYPE # include "ed.h" # include "edcmd.h" /* * This file contains the command parsing functions. */ /* * These functions are in cmdsub.c */ extern Int cb_search P((cmdbuf*, Int, Int, int)); extern int cb_print P((cmdbuf*)); extern int cb_list P((cmdbuf*)); extern int cb_number P((cmdbuf*)); extern int cb_page P((cmdbuf*)); extern int cb_assign P((cmdbuf*)); extern int cb_mark P((cmdbuf*)); extern int cb_append P((cmdbuf*)); extern int cb_insert P((cmdbuf*)); extern int cb_change P((cmdbuf*)); extern int cb_delete P((cmdbuf*)); extern int cb_copy P((cmdbuf*)); extern int cb_move P((cmdbuf*)); extern int cb_put P((cmdbuf*)); extern int cb_yank P((cmdbuf*)); extern int cb_lshift P((cmdbuf*)); extern int cb_rshift P((cmdbuf*)); extern int cb_indent P((cmdbuf*)); extern int cb_join P((cmdbuf*)); extern int cb_subst P((cmdbuf*)); extern int cb_file P((cmdbuf*)); extern int cb_read P((cmdbuf*)); extern int cb_edit P((cmdbuf*)); extern int cb_quit P((cmdbuf*)); extern int cb_write P((cmdbuf*)); extern int cb_wq P((cmdbuf*)); extern int cb_xit P((cmdbuf*)); extern int cb_set P((cmdbuf*)); /* * NAME: cmdbuf->new() * DESCRIPTION: create and initialize a command edit buffer */ cmdbuf *cb_new(tmpfile) char *tmpfile; { register cmdbuf *cb; m_static(); cb = ALLOC(cmdbuf, 1); memset(cb, '\0', sizeof(cmdbuf)); cb->edbuf = eb_new(tmpfile); cb->regexp = rx_new(); cb->vars = va_new(); m_dynamic(); cb->this = 0; cb->undo = (block) -1; /* not 0! */ return cb; } /* * NAME: cmdbuf->del() * DESCRIPTION: delete a command edit buffer */ void cb_del(cb) register cmdbuf *cb; { eb_del(cb->edbuf); rx_del(cb->regexp); va_del(cb->vars); FREE(cb); } /* * NAME: skipst() * DESCRIPTION: skip white space in a string. return a pointer to the first * character after the white space (could be '\0') */ char *skipst(p) register char *p; { while (*p == ' ' || *p == HT) { p++; } return p; } /* * NAME: pattern() * DESCRIPTION: scan a pattern and copy it to a buffer. */ char *pattern(pat, delim, buffer) char *pat, *buffer; int delim; { register char *p; register unsigned int size; p = pat; while (*p != '\0') { if (*p == delim) { break; } switch (*p++) { case '\\': if (*p != '\0') { p++; } break; case '[': while (*p != '\0' && *p != ']') { if (*p++ == '\\') { if (*p == '\0') { break; } p++; } } break; } } size = p - pat; if (size >= STRINGSZ) { error("Regular expression too large"); } if (size > 0) { memcpy(buffer, pat, size); } buffer[size] = '\0'; if (*p != '\0') { p++; } return p; } /* * NAME: cmdbuf->pattern() * DESCRIPTION: compile a regular expression, up to a delimeter */ static void cb_pattern(cb, delim) register cmdbuf *cb; char delim; { char buffer[STRINGSZ]; cb->cmd = pattern(cb->cmd, delim, buffer); if (buffer[0] == '\0') { if (!cb->regexp->valid) { error("No previous regular expression"); } } else { char *err; err = rx_comp(cb->regexp, buffer); if (err != (char *) NULL) { error(err); } } } /* * NAME: cmdbuf->address() * DESCRIPTION: parse an address. First is the first line to search from if the * address is a search pattern. */ static Int cb_address(cb, first) register cmdbuf *cb; Int first; { register Int l; register char *p; l = 0; switch (*(p = cb->cmd)) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': do { l *= 10; l += *p++ - '0'; } while (isdigit(*p)); cb->cmd = p; break; case '.': cb->cmd++; case '+': case '-': l = cb->this; break; case '$': l = cb->edbuf->lines; cb->cmd++; break; case '\'': if (islower(*++p)) { l = cb->mark[*p - 'a']; } else { error("Marks are a-z"); } if (l == 0) { error("Undefined mark referenced"); } cb->cmd += 2; break; case '/': cb->cmd++; cb_pattern(cb, *p); l = cb->edbuf->lines; if (l == 0) { return 1; /* force out-of-range error */ } if (first < l) { l = cb_search(cb, first + 1, l, FALSE); if (l != 0) { break; } } l = cb_search(cb, (Int) 1, first, FALSE); if (l == 0) { error("Pattern not found"); } break; case '?': cb->cmd++; cb_pattern(cb, *p); if (first > 1) { l = cb_search(cb, (Int) 1, first - 1, TRUE); } if (l == 0) { l = cb->edbuf->lines; if (l == 0) { return 1; /* force out-of-range error */ } l = cb_search(cb, first, l, TRUE); if (l == 0) { error("Pattern not found"); } } break; default: return -1; } cb->cmd = skipst(cb->cmd); while (cb->cmd[0] == '+' || cb->cmd[0] == '-') { register Int r; p = skipst(cb->cmd + 1); if (!isdigit(*p)) { r = 1; } else { r = 0; do { r *= 10; r += *p++ - '0'; } while (isdigit(*p)); } if (cb->cmd[0] == '+') { l += r; } else { l -= r; } cb->cmd = skipst(p); } if (l < 0) { error("Negative address"); } return l; } /* * NAME: cmdbuf->range() * DESCRIPTION: parse line range from the command buffer. Valid range for an * address is 0-$ */ static void cb_range(cb) register cmdbuf *cb; { cb->first = -1; cb->last = -1; cb->cmd = skipst(cb->cmd); if (cb->cmd[0] == '\0') { return; } if (cb->cmd[0] == '%') { cb->first = 1; cb->last = cb->edbuf->lines; cb->cmd = skipst(cb->cmd + 1); } else { cb->first = cb_address(cb, cb->this); if (cb->cmd[0] == ',' || cb->cmd[0] == ';') { if (cb->first < 0) { cb->first = cb->this; } else if (cb->cmd[0] == ';') { cb->this = cb->first; } cb->cmd = skipst(cb->cmd + 1); cb->last = cb_address(cb, cb->this); if (cb->last < 0) { cb->last = cb->this; } } } } /* * NAME: cmdbuf->count() * DESCRIPTION: set the line range according to the (optional) count in the * command buffer */ void cb_count(cb) register cmdbuf *cb; { register char *p; p = cb->cmd; if (isdigit(*p)) { register Int count; count = 0; do { count *= 10; count += *p++ - '0'; } while (isdigit(*p)); cb->cmd = skipst(p); if (cb->first < 0) { cb->first = cb->this; } cb->last = cb->first + count - 1; if (cb->last < cb->first || cb->last > cb->edbuf->lines) { error("Not that many lines in buffer"); } } } /* * NAME: not_in_global() * DESCRIPTION: error if currently executing a global command */ void not_in_global(cb) cmdbuf *cb; { if (cb->flags & CB_GLOBAL) { error("Command not allowed in global"); } } /* * NAME: cmdbuf->do() * DESCRIPTION: copy the present command buffer status in the undo buffer */ void cb_do(cb, this) register cmdbuf *cb; Int this; { cb->undo = cb->edbuf->buffer; cb->uthis = this; memcpy(cb->umark, cb->mark, sizeof(cb->mark)); } /* * NAME: cmdbuf->undo() * DESCRIPTION: undo the effects of a previous command by exchanging the * command buffer status with the undo buffer */ int cb_undo(cb) register cmdbuf *cb; { block b; Int this, mark[26]; not_in_global(cb); if (cb->undo == (block) -1) { error("Nothing to undo"); } b = cb->undo; cb->undo = cb->edbuf->buffer; cb->edbuf->lines = (b == (block) 0) ? 0 : bk_size(cb->edbuf->lb, b); cb->edbuf->buffer = b; this = cb->uthis; if (this == 0 && b != (block) 0) { this = 1; } cb->uthis = cb->othis; cb->this = cb->othis = this; memcpy(mark, cb->umark, sizeof(mark)); memcpy(cb->umark, cb->mark, sizeof(mark)); memcpy(cb->mark, mark, sizeof(mark)); cb->edit++; return RET_FLAGS; } /* * NAME: cmdbuf->buf() * DESCRIPTION: put a block in the appropriate buffers */ void cb_buf(cb, b) register cmdbuf *cb; register block b; { if (isupper(cb->a_buffer)) { register block *zbuf; /* * copy or append to named buffer */ zbuf = &cb->zbuf[cb->a_buffer - 'A']; if (*zbuf != (block) 0) { *zbuf = bk_cat(cb->edbuf->lb, *zbuf, b); } else { *zbuf = b; } } else if (islower(cb->a_buffer)) { cb->zbuf[cb->a_buffer - 'a'] = b; } /* * always put it in the default yank buffer too */ cb->buf = b; } /* * Commands are allowed to affect the first or the last or all of the next lines * to be examined by a global command. The lines must stay in a contiguous * block. */ /* * NAME: add() * DESCRIPTION: add a block of lines to the edit buffer */ void add(cb, ln, b, size) register cmdbuf *cb; register Int ln, size; block b; { register Int *m; /* global checks */ if (cb->flags & CB_GLOBAL) { if (ln < cb->glob_next) { cb->glob_next += size; } else if (ln < cb->glob_next + cb->glob_size - 1) { error("Illegal add in global"); } } eb_put(cb->edbuf, ln, b); /* adjust marks of lines after new block */ for (m = cb->mark; m < &cb->mark[26]; m++) { if (*m > ln) { *m += size; } } cb->this = cb->othis = ln + size; } /* * NAME: delete() * DESCRIPTION: delete a block of lines from the edit buffer */ block delete(cb, first, last) register cmdbuf *cb; register Int first, last; { register Int size, *m; size = last - first + 1; /* global checks */ if (cb->flags & CB_GLOBAL) { if (last < cb->glob_next) { cb->glob_next -= size; } else if (first <= cb->glob_next) { cb->glob_size -= last - cb->glob_next + 1; cb->glob_next = first; } else if (last >= cb->glob_next + cb->glob_size) { cb->glob_size = first - cb->glob_next; } else { error("Illegal delete in global"); } } /* adjust & erase marks */ for (m = cb->mark; m < &cb->mark[26]; m++) { if (*m >= first) { if (*m > last) { *m -= size; } else { *m = 0; } } } cb->othis = first; if (last == cb->edbuf->lines) { cb->othis--; } cb->this = cb->othis; return eb_delete(cb->edbuf, first, last); } /* * NAME: change() * DESCRIPTION: replace a subrange of lines by a block */ void change(cb, first, last, b) register cmdbuf *cb; register Int first, last; block b; { register Int offset, *m; offset = last - first + 1; if (b != (block) 0) { offset -= bk_size(cb->edbuf->lb, b); } /* global checks */ if (cb->flags & CB_GLOBAL) { if (last < cb->glob_next) { cb->glob_next -= offset; } else if (first <= cb->glob_next) { cb->glob_size -= last - cb->glob_next + 1; cb->glob_next = last - offset + 1; } else if (last >= cb->glob_next + cb->glob_size) { cb->glob_size = first - cb->glob_next; } else { error("Illegal change in global"); } } /* adjust marks. If the marks of the changed lines have to be erased, the calling routine must handle it. */ for (m = cb->mark; m < &cb->mark[26]; m++) { if (*m > last) { *m -= offset; } } cb->othis = first; cb->this = last - offset; if (cb->this == 0 && last != cb->edbuf->lines) { cb->this = 1; } eb_change(cb->edbuf, first, last, b); } /* * NAME: startblock() * DESCRIPTION: start a block of lines */ void startblock(cb) cmdbuf *cb; { eb_startblock(cb->edbuf); } /* * NAME: addblock() * DESCRIPTION: add a line to the current block of lines */ void addblock(cb, text) cmdbuf *cb; char *text; { eb_addblock(cb->edbuf, text); } /* * NAME: endblock() * DESCRIPTION: finish the current block */ void endblock(cb) register cmdbuf *cb; { eb_endblock(cb->edbuf); if (cb->flags & CB_CHANGE) { if (cb->first <= cb->last) { change(cb, cb->first, cb->last, cb->edbuf->flines); } else if (cb->first == 0 && cb->edbuf->lines != 0) { cb->this = cb->othis = 1; } else { cb->this = cb->othis = cb->first; } } else { if (cb->edbuf->flines != (block) 0) { add(cb, cb->first, cb->edbuf->flines, bk_size(cb->edbuf->lb, cb->edbuf->flines)); } else if (cb->first == 0 && cb->edbuf->lines != 0) { cb->this = cb->othis = 1; } else { cb->this = cb->othis = cb->first; } } cb->flags &= ~(CB_INSERT | CB_CHANGE); cb->edit++; } /* * NAME: find() * DESCRIPTION: match a pattern in a global command */ static void find(ptr, text) char *ptr, *text; { register cmdbuf *cb; cb = (cmdbuf *) ptr; cb->glob_next++; cb->glob_size--; if (rx_exec(cb->glob_rx, text, 0, cb->ignorecase) != cb->reverse) { longjmp(cb->env, TRUE); } } /* * NAME: cmdbuf->global() * DESCRIPTION: do a global command */ int cb_global(cb) register cmdbuf *cb; { register char *p; char buffer[STRINGSZ], delim; block undo; Int uthis, umark[26]; bool aborted; not_in_global(cb); /* no recursion please */ /* get the regular expression */ delim = cb->cmd[0]; if (delim != '\0' && !isalnum(delim)) { cb->cmd = pattern(cb->cmd + 1, delim, buffer); } else { buffer[0] = '\0'; } if (buffer[0] == '\0') { error("Missing regular expression for global"); } /* keep global undo status */ undo = cb->edbuf->buffer; uthis = cb->first; memcpy(umark, cb->mark, sizeof(cb->mark)); /* * A local error context is created, so the regular expression buffer * can be deallocated in case of an error. */ cb->glob_rx = rx_new(); if (!ec_push((ec_ftn) NULL)) { /* compile regexp */ p = rx_comp(cb->glob_rx, buffer); if (p != (char *) NULL) { error(p); } /* get the command to be done in global */ p = skipst(cb->cmd); cb->cmd = p + strlen(p); if (*p == '\0') { p = "p"; /* default: print lines */ } cb->flags |= CB_GLOBAL; cb->reverse = (cb->flags & CB_EXCL) != 0; cb->ignorecase = IGNORECASE(cb->vars); cb->glob_next = cb->first; cb->glob_size = cb->last - cb->first + 1; do { if (setjmp(cb->env)) { /* found: do the commands */ cb->this = cb->glob_next - 1; cb_command(cb, p); } else { /* search */ eb_range(cb->edbuf, cb->glob_next, cb->glob_next + cb->glob_size - 1, find, (char *) cb, FALSE); } } while (cb->glob_size > 0); /* pop error context */ ec_pop(); aborted = FALSE; } else { aborted = TRUE; } /* come here if global is finished or in case of an error */ /* clean up regular expression */ rx_del(cb->glob_rx); /* set undo status */ cb->undo = undo; cb->uthis = uthis; memcpy(cb->umark, umark, sizeof(umark)); /* no longer in global */ cb->flags &= ~CB_GLOBAL; if (aborted) { error((char *) NULL); } return 0; } /* * NAME: cmdbuf->vglobal() * DESCRIPTION: v == g! */ int cb_vglobal(cb) cmdbuf *cb; { cb->flags |= CB_EXCL; return cb_global(cb); } typedef struct { char flags; /* type of command */ char chr; /* first char of command */ char *cmd; /* full command string */ int (*ftn)(); /* command function */ } cmd; # define CM_LNMASK 0x03 # define CM_LNNONE 0x00 /* range checking in function */ # define CM_LN0 0x01 /* (.)0 */ # define CM_LNDOT 0x02 /* (.,.) */ # define CM_LNRNG 0x03 /* (1,$) */ # define CM_EXCL 0x04 # define CM_BUFFER 0x10 /* buffer argument */ # define CM_ADDR 0x20 /* address argument */ # define CM_COUNT 0x40 /* count argument */ static cmd ed_commands[] = { { CM_LN0, 'a', "append", cb_append }, # define CM_ASSIGN 1 { CM_LNNONE, '=', (char *) NULL, cb_assign }, { CM_LNDOT | CM_COUNT, 'c', "change", cb_change }, { CM_LNDOT | CM_BUFFER | CM_COUNT, 'd', "delete", cb_delete }, { CM_LNNONE | CM_EXCL, 'e', "edit", cb_edit }, { CM_LNNONE, 'f', "file", cb_file }, { CM_LNRNG | CM_EXCL, 'g', "global", cb_global }, { CM_LN0 | CM_BUFFER, 0, "put", cb_put }, { CM_LN0, 'i', "insert", cb_insert }, { CM_LNNONE | CM_EXCL | CM_COUNT, 'j', "join", cb_join }, { CM_LNDOT, 'k', "mark", cb_mark }, { CM_LNDOT | CM_COUNT, 'l', "list", cb_list }, { CM_LNDOT | CM_ADDR, 'm', "move", cb_move }, # define CM_NUMBER 13 { CM_LNDOT | CM_COUNT, '#', "number", cb_number }, { CM_LNRNG | CM_EXCL, 0, "wq", cb_wq }, { CM_LNDOT | CM_COUNT, 'p', "print", cb_print }, { CM_LNNONE | CM_EXCL, 'q', "quit", cb_quit }, { CM_LN0, 'r', "read", cb_read }, { CM_LNDOT, 's', "substitute", cb_subst }, { CM_LNDOT | CM_ADDR, 't', "copy", cb_copy }, { CM_LNNONE, 'u', "undo", cb_undo }, { CM_LNRNG, 'v', (char *) NULL, cb_vglobal }, { CM_LNRNG | CM_EXCL, 'w', "write", cb_write }, { CM_LNRNG, 'x', "xit", cb_xit }, { CM_LNDOT | CM_BUFFER | CM_COUNT, 'y', "yank", cb_yank }, { CM_LNNONE, 'z', (char *) NULL, cb_page }, { CM_LNNONE, 0, "set", cb_set }, # define CM_LSHIFT 27 { CM_LNDOT | CM_COUNT, '<', (char *) NULL, cb_lshift }, # define CM_RSHIFT 28 { CM_LNDOT | CM_COUNT, '>', (char *) NULL, cb_rshift }, # define CM_INDENT 29 { CM_LNRNG, 'I', (char *) NULL, cb_indent }, }; # define NR_CMD 27 /* not including <, > and I */ /* * NAME: cmdbuf->command() * DESCRIPTION: Parse and execute an editor command. Return TRUE if this command * did not terminate the editor. Multiple commands may be * specified, separated by | */ bool cb_command(cb, command) register cmdbuf *cb; char *command; { cb->cmd = command; for (;;) { if (cb->flags & CB_INSERT) { /* insert mode */ if (strlen(command) >= MAX_LINE_SIZE) { endblock(cb); error("Line too long"); } if (strcmp(command, ".") == 0) { /* finish block */ endblock(cb); } else { /* add the "command" to the current block */ addblock(cb, command); } } else { register cmd *cm; register char *p; int ltype, ret; cb->flags &= ~(CB_EXCL | CB_NUMBER | CB_LIST); cb->a_addr = -1; cb->a_buffer = 0; /* * parse the command line: [range] [command] [arguments] */ cb->cmd = skipst(cb->cmd); if (cb->cmd[0] == '\0') { /* no command: print next line */ if (cb->this == cb->edbuf->lines) { error("End-of-file"); } cm = &ed_commands['p' - 'a']; cb->first = cb->last = cb->this + 1; } else { /* parse [range] */ cb_range(cb); p = cb->cmd = skipst(cb->cmd); cm = (cmd *) NULL; /* parse [command] */ if (*p == 'k') { p++; } else { while (isalpha(*p)) { p++; } } if (p == cb->cmd) { /* length == 0 */ switch (*p++) { case '=': cm = &ed_commands[CM_ASSIGN]; break; case '#': cm = &ed_commands[CM_NUMBER]; break; case '<': cm = &ed_commands[CM_LSHIFT]; break; case '>': cm = &ed_commands[CM_RSHIFT]; break; case '\0': case '|': cm = &ed_commands['p' - 'a']; default: --p; break; } } else if (p - cb->cmd == 1) { /* length == 1 */ if (ed_commands[p[-1] - 'a'].chr == p[-1]) { cm = &ed_commands[p[-1] - 'a']; } else if (p[-1] == 'I') { cm = &ed_commands[CM_INDENT]; } } else { /* length > 1 */ cm = ed_commands; for (;;) { if (cm->cmd && strncmp(cb->cmd, cm->cmd, p - cb->cmd) == 0) { break; } if (++cm == &ed_commands[NR_CMD]) { cm = (cmd *) NULL; break; } } } if (cm == (cmd *) NULL) { error("No such command"); } /* CM_EXCL */ if ((cm->flags & CM_EXCL) && *p == '!') { cb->flags |= CB_EXCL; p++; } p = skipst(p); /* CM_BUFFER */ if ((cm->flags & CM_BUFFER) && isalpha(*p)) { cb->a_buffer = *p; p = skipst(p + 1); } cb->cmd = p; /* CM_COUNT */ if (cm->flags & CM_COUNT) { cb_count(cb); } /* CM_ADDR */ if (cm->flags & CM_ADDR) { cb->a_addr = cb_address(cb, cb->this); if (cb->a_addr < 0) { error("Command requires a trailing address"); } cb->cmd = skipst(cb->cmd); } } /* * check/adjust line range */ ltype = cm->flags & CM_LNMASK; if (ltype != CM_LN0) { if ((ltype == CM_LNDOT || ltype == CM_LNRNG) && cb->edbuf->lines == 0) { error("No lines in buffer"); } if (cb->first == 0) { error("Nonzero address required on this command"); } } switch (ltype) { case CM_LNDOT: case CM_LN0: if (cb->first < 0) { cb->first = cb->this; } if (cb->last < 0) { cb->last = cb->first; } break; case CM_LNRNG: if (cb->first < 0) { cb->first = 1; cb->last = cb->edbuf->lines; } else if ( cb->last < 0) { cb->last = cb->first; } break; } if (cb->first > cb->edbuf->lines || cb->last > cb->edbuf->lines || cb->a_addr > cb->edbuf->lines) { error("Not that many lines in buffer"); } if (cb->last >= 0 && cb->last < cb->first) { error("Inverted address range"); } ret = (*cm->ftn)(cb); p = skipst(cb->cmd); if (ret == RET_FLAGS) { for (;;) { switch (*p++) { case '-': --cb->this; continue; case '+': cb->this++; continue; case 'p': /* ignore */ continue; case 'l': cb->flags |= CB_LIST; continue; case '#': cb->flags |= CB_NUMBER; continue; } --p; break; } if (cb->this <= 0) { cb->this = 1; } if (cb->this > cb->edbuf->lines) { cb->this = cb->edbuf->lines; } if (cb->this != 0 && !(cb->flags & CB_GLOBAL)) { /* no autoprint in global */ cb->first = cb->last = cb->this; cb_print(cb); } p = skipst(cb->cmd); } /* another command? */ if (*p == '|' && (cb->flags & CB_GLOBAL) && ret != RET_QUIT) { cb->cmd = p + 1; continue; } /* it has to be finished now */ if (*p != '\0') { error("Illegal characters after command"); } if (ret == RET_QUIT) { return FALSE; } } return TRUE; } }