/* $header: /belch_a/users/rearl/tm/src/RCS/edit.c,v 1.3 90/07/29 17:33:10 rearl Exp $ */
/*
* $Log: edit.c,v $
* Revision 1.14 90/10/06 16:32:43 rearl
* Fixes for 2.2 distribution.
*
* Revision 1.13 90/09/28 12:20:39 rearl
* Fixed macro function declarations.
*
* Revision 1.12 90/09/18 07:56:03 rearl
* Added number toggling in source listing.
*
* Revision 1.11 90/09/16 04:42:05 rearl
* Preparation code added for disk-based MUCK.
*
* Revision 1.10 90/09/15 22:18:58 rearl
* Made "show" and "abridged" a little more friendly.
*
* Revision 1.9 90/09/13 06:22:08 rearl
* Took out some tabs so the server agrees with more clients.
*
* Revision 1.8 90/09/10 02:22:15 rearl
* Fixed a NULL string bug in program listing.
*
* Revision 1.7 90/09/01 05:57:37 rearl
* Fixed bug with program header listing.
*
* Revision 1.6 90/08/27 03:24:06 rearl
* Disk-based MUF source code, added necessary locking.
*
* Revision 1.5 90/08/17 03:42:35 rearl
* Fixed @list once and for all. Default is now to list all lines of a program
* if no arguments are given.
*
* Revision 1.4 90/08/05 03:19:15 rearl
* Redid match routines.
*
* Revision 1.3 90/07/29 17:33:10 rearl
* Got rid of some unused variables.
*
* Revision 1.2 90/07/23 03:12:35 casie
* Cleaned up various gcc warnings.
*
* Revision 1.1 90/07/19 23:03:31 casie
* Initial revision
*
*
*/
#include "copyright.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "externs.h"
#include "params.h"
#include "match.h"
#include "strings.h"
#include <ctype.h>
#define DOWNCASE(x) (lowercase[x])
void editor(dbref, const char *);
void do_insert(dbref, dbref, int [], int);
void do_delete(dbref, dbref, int [], int);
void do_quit(dbref, dbref);
void do_list(dbref, dbref, int [], int);
void insert(dbref, const char *);
struct line * get_new_line(void);
struct line * read_program(dbref);
char * macro_expansion(struct macrotable *, char *);
void do_compile(dbref, dbref);
void free_line(struct line *);
void free_prog_text(struct line *);
void prog_clean(struct frame *);
void val_and_head(dbref, int[], int);
void do_list_header(dbref, dbref);
void toggle_numbers(dbref);
/* Editor routines --- Also contains routines to handle input */
/* This routine determines if a player is editing or running an interactive
command. It does it by checking the frame pointer field of the player ---
if the program counter is NULL, then the player is not running anything
The reason we don't just check the pointer but check the pc too is because
I plan to leave the frame always on to save the time required allocating space
each time a program is run.
*/
void
interactive(dbref player, const char *command)
{
struct frame *fr;
if (DBFETCH(player)->run)
{
/* process command, push onto stack, and return control to
forth program */
FLAGS(player) &= ~INTERACTIVE;
if (!string_compare(command, BREAK_COMMAND))
{
prog_clean(DBFETCH(player)->run);
if (!(DBFETCH(player)->run -> pc))
{
free((void *) DBFETCH(player)->run);
DBFETCH(player)->run = 0;
}
DBDIRTY(player);
return;
}
fr = DBFETCH(player)->run;
if (fr -> argument.top >= STACK_SIZE)
{
notify(player, "Program stack overflow.");
prog_clean(fr);
DBDIRTY(player);
return;
}
fr -> argument.st[fr -> argument.top++].type = PROG_STRING;
fr -> argument.st[fr -> argument.top - 1].data.string =
alloc_prog_string(command);
(void) interp_loop(player, DBFETCH(player)->curr_prog, fr);
if (!(DBFETCH(player)->run -> pc))
{
free((void *) DBFETCH(player)->run);
DBFETCH(player)->run = 0;
}
}
else
editor(player, command);
DBDIRTY(player);
}
char
*macro_expansion(struct macrotable *node, char *match)
{
if (!node) return NULL;
else {
register int value = string_compare(match, node -> name);
if (value < 0)
return macro_expansion(node -> left, match);
else if (value > 0)
return macro_expansion(node -> right, match);
else return alloc_string (node -> definition);
}
}
struct macrotable
*new_macro(const char *name, const char *definition, dbref player)
{
struct macrotable *newmacro =
(struct macrotable *)malloc (sizeof (struct macrotable));
char buf[BUFFER_LEN];
int i;
for (i = 0; name[i]; i++)
buf[i] = DOWNCASE(name[i]);
buf[i] = '\0';
newmacro -> name = alloc_string(buf);
newmacro -> definition = alloc_string(definition);
newmacro -> implementor = player;
newmacro -> left = NULL;
newmacro -> right = NULL;
return (newmacro);
}
int
grow_macro_tree(struct macrotable *node, struct macrotable *newmacro)
{
register int value = strcmp (newmacro -> name, node -> name);
if (!value) return 0;
else if (value < 0) {
if (node -> left)
return grow_macro_tree (node -> left, newmacro);
else {
node -> left = newmacro;
return 1;
}
} else
if (node -> right)
return grow_macro_tree (node -> right, newmacro);
else {
node -> right = newmacro;
return 1;
}
}
void
insert_macro(const char *word[], dbref player)
{
struct macrotable *newmacro;
newmacro = new_macro (word[1], word[2], player);
if (!macrotop) macrotop = newmacro;
else if (!grow_macro_tree(macrotop, newmacro))
notify (player, "That macro's already been defined.");
else notify (player, "Entry created.");
}
void
do_list_tree(struct macrotable *node, const char *first, const char *last,
int length, dbref player)
{
static char buf[BUFSIZ];
if (!node) return;
else {
if (strncmp(node -> name, first, strlen(first)) >= 0)
do_list_tree(node -> left, first, last, length, player);
if ((strncmp(node -> name, first, strlen(first)) >= 0) &&
(strncmp(node -> name, last, strlen(last)) <= 0)) {
if (length) {
sprintf(buf, "%-16s %-16s %s", node -> name,
NAME(node -> implementor),
node -> definition);
notify(player, buf);
buf[0] = '\0';
} else {
sprintf(buf + strlen(buf), "%-16s", node -> name);
if (strlen(buf) > 70) {
notify(player, buf);
buf[0] = '\0';
}
}
}
if (strncmp(last, node -> name, strlen(last)) >= 0)
do_list_tree(node -> right, first, last, length, player);
if ((node == macrotop) && !length) {
notify(player, buf);
buf[0] = '\0';
}
}
}
void
list_macros(const char *word[], int k, dbref player, int length)
{
if (!k--) {
do_list_tree(macrotop, "a", "z", length, player);
} else {
do_list_tree(macrotop, word[0], word[k], length, player);
}
notify(player, "End of list.");
return;
}
int
erase_node(struct macrotable *oldnode, struct macrotable *node,
const char *killname)
{
if (!node) return 0;
else if(strcmp(killname, node -> name) < 0)
return erase_node(node, node -> left, killname);
else if(strcmp(killname, node -> name))
return erase_node(node, node -> right, killname);
else {
if (node == oldnode -> left) {
oldnode -> left = node -> left;
if (node -> right) grow_macro_tree (macrotop, node -> right);
free ((void *) node);
return 1;
} else {
oldnode -> right = node -> right;
if (node -> left) grow_macro_tree (macrotop, node -> left);
free ((void *) node);
return 1;
}
}
}
void
kill_macro(const char *word[], dbref player)
{
if (!Wizard(player)) {
notify (player, "I'm sorry, Dave, I can't let you do that.");
return;
} else if (!macrotop) {
notify (player, "You've got nothing and you want to kill? Sheesh!");
return;
} else if (!string_compare(word[0], macrotop -> name)) {
struct macrotable *macrotemp = macrotop;
int whichway = (macrotop -> left) ? 1 : 0;
macrotop = whichway ? macrotop -> left : macrotop -> right;
if (macrotop && (whichway ? macrotemp -> right : macrotemp -> left))
grow_macro_tree(macrotop, whichway ?
macrotemp -> right : macrotemp -> left);
free ((void *) macrotemp);
notify (player, "Entry removed.");
} else
if (erase_node(macrotop, macrotop, word[0]))
notify (player, "Entry removed.");
else notify (player, "Entry to remove not found.");
}
/* The editor itself --- this gets called each time every time to
* parse a command.
*/
void
editor(dbref player, const char *command)
{
dbref program;
int arg[MAX_ARG+1];
char buf[BUFFER_LEN];
const char *word[MAX_ARG+1];
int i, j; /* loop variables */
program = DBFETCH(player)->curr_prog;
/* check to see if we are insert mode */
if (DBFETCH(player)->insert_mode)
{
insert(player, command); /* insert it! */
return;
}
/* parse the commands */
for (i = 0; i <= MAX_ARG && *command; i++)
{
while (*command && isspace(*command))
command++;
j = 0;
while (*command && !isspace(*command))
{
buf[j] = *command;
command++, j++;
}
buf[j] = '\0';
word[i] = alloc_string(buf);
if ((i == 1) && !string_compare(word[0], "def")) {
while (*command && isspace(*command)) command++;
word[2] = alloc_string(command);
if (!word[2])
notify (player, "Invalid definition syntax.");
else insert_macro(word, player);
for (; i >= 0; i--) {
if (word[i]) free ((void *) word[i]);
}
return;
}
arg[i] = atoi(buf);
if (arg[i] < 0)
{
notify(player, "Negative arguments not allowed!");
for (; i >= 0; i--) {
if (word[i]) free ((void *) word[i]);
}
return;
}
}
i--;
while ((i >= 0) && !word[i]) i--;
if (i < 0) {
return;
} else {
switch (word[i][0]) {
case KILL_COMMAND:
kill_macro(word, player);
break;
case SHOW_COMMAND:
list_macros(word, i, player, 1);
break;
case SHORTSHOW_COMMAND:
list_macros(word, i, player, 0);
break;
case INSERT_COMMAND:
do_insert(player, program, arg, i);
notify(player, "Entering insert mode.");
break;
case DELETE_COMMAND:
do_delete(player, program, arg, i);
break;
case QUIT_EDIT_COMMAND:
do_quit(player, program);
notify(player, "Editor exited.");
break;
case COMPILE_COMMAND:
/* compile code belongs in compile.c, not in the editor */
do_compile(player, program);
notify(player, "Compiler done.");
break;
case LIST_COMMAND:
do_list(player, program, arg, i);
break;
case EDITOR_HELP_COMMAND:
spit_file(player, EDITOR_HELP_FILE);
break;
case VIEW_COMMAND:
val_and_head(player, arg, i);
break;
case UNASSEMBLE_COMMAND:
disassemble(player, program);
break;
case NUMBER_COMMAND:
toggle_numbers(player);
break;
default:
notify(player, "Illegal editor command.");
break;
}
}
for (; i >= 0; i--) {
if (word[i]) free ((void *) word[i]);
}
}
/* puts program into insert mode */
void
do_insert(dbref player, dbref program, int arg[], int argc)
{
DBFETCH(player)->insert_mode++;
DBDIRTY(player);
if (argc)
DBSTORE(program, sp.program.curr_line, arg[0] - 1); /* set current line to something else */
}
/* deletes line n if one argument,
lines arg1 -- arg2 if two arguments
current line if no argument */
void
do_delete(dbref player, dbref program, int arg[], int argc)
{
struct line *curr, *temp;
char buf[BUFFER_LEN];
int i;
switch (argc)
{
case 0:
arg[0] = DBFETCH(program)->sp.program.curr_line;
case 1:
arg[1] = arg[0];
case 2:
/* delete from line 1 to line 2 */
/* first, check for conflict */
if (arg[0] > arg[1])
{
notify(player, "Nonsensical arguments.");
return;
}
i = arg[0] - 1;
for (curr = DBFETCH(program)->sp.program.first; curr && i; i--)
curr = curr -> next;
if (curr)
{
DBFETCH(program)->sp.program.curr_line = arg[0];
i = arg[1] - arg[0] + 1;
/* delete n lines */
while (i && curr)
{
temp = curr;
if (curr -> prev)
curr -> prev -> next = curr -> next;
else
DBFETCH(program)->sp.program.first = curr -> next;
if (curr -> next)
curr -> next -> prev = curr -> prev;
curr = curr -> next;
free_line(temp);
i--;
}
sprintf(buf, "%d lines deleted", arg[1] - arg[0] - i + 1);
notify(player, buf);
}
else
notify(player, "No line to delete!");
break;
default:
notify(player, "Too many arguments!");
break;
}
}
/* quit from edit mode. Put player back into the regular game mode */
void
do_quit(dbref player, dbref program)
{
write_program(DBFETCH(program)->sp.program.first, program);
free_prog_text(DBFETCH(program)->sp.program.first);
FLAGS(program) &= ~INTERNAL;
FLAGS(player) &= ~INTERACTIVE;
DBFETCH(player)->curr_prog = NOTHING;
DBDIRTY(player);
DBDIRTY(program);
}
void match_and_list(dbref player, const char *name, char *linespec)
{
dbref thing;
char *p;
char *q;
int range[2];
int argc;
struct match_data md;
init_match(player, name, TYPE_PROGRAM, &md);
match_neighbor(&md);
match_possession(&md);
match_absolute(&md);
if ((thing = noisy_match_result(&md)) == NOTHING) return;
if (Typeof(thing) != TYPE_PROGRAM) {
notify(player, "You can't list anything but a program.");
return;
}
if (!(controls(player, thing) || (FLAGS(thing)&VISIBLE)
|| (Arch(player) && Mucker(player)))) {
notify(player, "Permission denied.");
return;
}
if (!*linespec) {
range[0] = 1;
range[1] = -1;
argc = 2;
} else {
q = p = linespec;
while(*p) {
while(*p && !isspace(*p)) *q++ = *p++;
while(*p && isspace(*++p));
}
*q = '\0';
argc = 1;
if (isdigit(*linespec)) {
range[0] = atoi(linespec);
while(*linespec && isdigit(*linespec)) linespec++;
} else {
range[0] = 1;
}
if (*linespec) {
argc = 2;
while(*linespec && !isdigit(*linespec)) linespec++;
if (*linespec) range[1] = atoi(linespec);
else range[1] = -1;
}
}
DBSTORE(thing, sp.program.first, read_program(thing));
do_list(player, thing, range, argc);
if (!(FLAGS(thing) & INTERNAL))
free_prog_text(DBFETCH(thing)->sp.program.first);
return;
}
/* list --- if no argument, redisplay the current line
if 1 argument, display that line
if 2 arguments, display all in between */
void
do_list(dbref player, dbref program, int oarg[], int argc)
{
struct line *curr;
int i, count;
int arg[2];
char buf[BUFFER_LEN];
if (oarg) {
arg[0] = oarg[0];
arg[1] = oarg[1];
} else
arg[0] = arg[1] = 0;
switch (argc)
{
case 0:
arg[0] = DBFETCH(program)->sp.program.curr_line;
case 1:
arg[1] = arg[0];
case 2:
if ((arg[0] > arg[1]) && (arg[1] != -1))
{
notify(player, "Arguments don't make sense!");
return;
}
i = arg[0] - 1;
for (curr = DBFETCH(program)->sp.program.first; i && curr; i--)
curr = curr -> next;
if (curr)
{
i = arg[1] - arg[0] + 1;
/* display n lines */
for (count = arg[0]; curr && (i || (arg[1] == -1)); i--)
{
if (FLAGS(player) & INTERNAL)
sprintf(buf, "%3d: %s", count, DoNull(curr -> this_line));
else
sprintf(buf, "%s", DoNull(curr -> this_line));
notify(player, buf);
count++;
curr = curr -> next;
}
if (count - arg[0] > 1) {
sprintf(buf, "%d lines displayed.", count - arg[0]);
notify(player, buf);
}
}
else
notify(player, "Line not available for display.");
break;
default:
notify(player, "Too many arguments!");
break;
}
}
void
val_and_head(dbref player, int arg[], int argc)
{
dbref program;
if (argc != 1) {
notify(player, "I don't understand which header you're trying to look at.");
return;
}
program = arg[0];
if (Typeof(program) != TYPE_PROGRAM) {
notify(player, "That isn't a program.");
return;
}
if (!(controls(player, program) || Linkable(program))) {
notify(player, "That's not a public program.");
return;
}
do_list_header(player, program);
}
void
do_list_header(dbref player, dbref program)
{
struct line *curr = read_program(program);
while (curr && (curr -> this_line)[0] == '(') {
notify (player, curr -> this_line);
curr = curr -> next;
}
if (!(FLAGS(program) & INTERNAL))
free_prog_text(curr);
notify (player, "Done.");
}
void toggle_numbers(dbref player)
{
if (FLAGS(player) & INTERNAL) {
FLAGS(player) &= ~INTERNAL;
notify(player, "Line numbers off.");
} else {
FLAGS(player) |= INTERNAL;
notify(player, "Line numbers on.");
}
}
/* insert this line into program */
void
insert(dbref player, const char *line)
{
dbref program;
int i;
struct line *curr;
struct line *new_line;
program = DBFETCH(player)->curr_prog;
if (!string_compare(line, EXIT_INSERT))
{
DBSTORE(player, insert_mode, 0); /* turn off insert mode */
return;
}
i = DBFETCH(program)->sp.program.curr_line - 1;
for (curr = DBFETCH(program)->sp.program.first; curr && i && i + 1; i--)
curr = curr -> next;
new_line = get_new_line(); /* initialize line */
new_line -> this_line = alloc_string(line);
if (!DBFETCH(program)->sp.program.first) /* nothing --- insert in front */
{
DBFETCH(program)->sp.program.first = new_line;
DBFETCH(program)->sp.program.curr_line = 2; /* insert at the end */
DBDIRTY(program);
return;
}
if (!curr) /* insert at the end */
{
i = 1;
for (curr = DBFETCH(program)->sp.program.first; curr -> next; curr = curr -> next)
i++; /* count lines */
DBFETCH(program)->sp.program.curr_line = i + 2;
new_line -> prev = curr;
curr -> next = new_line;
DBDIRTY(program);
return;
}
if (!DBFETCH(program)->sp.program.curr_line) /* insert at the beginning */
{
DBFETCH(program)->sp.program.curr_line = 1; /* insert after this new line */
new_line -> next = DBFETCH(program)->sp.program.first;
DBFETCH(program)->sp.program.first = new_line;
DBDIRTY(program);
return;
}
/* inserting in the middle */
DBFETCH(program)->sp.program.curr_line++;
new_line -> prev = curr;
new_line -> next = curr -> next;
if (new_line -> next)
new_line -> next -> prev = new_line;
curr -> next = new_line;
DBDIRTY(program);
}