[BACK]Return to cmode.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mg

File: [local] / src / usr.bin / mg / cmode.c (download)

Revision 1.17, Thu Jul 11 18:20:18 2019 UTC (4 years, 10 months ago) by lum
Branch: MAIN
CVS Tags: OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6
Changes since 1.16: +7 -7 lines

Allow functions that have 1 or more parameters receive and process
multiple arguments when evaluated in a startup file or via one of the
'eval' commands.

This diff does treat the '(' and ')' chars differently during
evaluation than previously, in-so-far as they are not ignored if they
are at the end or start of a line now. However, even though these
characters are not ignored, this diff should not change the behaviour
of an extant .mg file, with '(' and ')' chars at the end and start of
a line.  This situation is accomodated for in this diff (with limited
testing though).

/* $OpenBSD: cmode.c,v 1.17 2019/07/11 18:20:18 lum Exp $ */
/*
 * This file is in the public domain.
 *
 * Author: Kjell Wooding <kjell@openbsd.org>
 */

/*
 * Implement an non-irritating KNF-compliant mode for editing
 * C code.
 */

#include <sys/queue.h>
#include <ctype.h>
#include <signal.h>
#include <stdio.h>

#include "def.h"
#include "funmap.h"
#include "kbd.h"

/* Pull in from modes.c */
extern int changemode(int, int, char *);

static int cc_strip_trailp = TRUE;	/* Delete Trailing space? */
static int cc_basic_indent = 8;		/* Basic Indent multiple */
static int cc_cont_indent = 4;		/* Continued line indent */
static int cc_colon_indent = -8;	/* Label / case indent */

static int getmatch(int, int);
static int getindent(const struct line *, int *);
static int in_whitespace(struct line *, int);
static int findcolpos(const struct buffer *, const struct line *, int);
static struct line *findnonblank(struct line *);
static int isnonblank(const struct line *, int);

void cmode_init(void);
int cc_comment(int, int);

/* Keymaps */

static PF cmode_brace[] = {
	cc_brace,	/* } */
};

static PF cmode_cCP[] = {
	compile,		/* C-c P */
};


static PF cmode_cc[] = {
	NULL,		/* ^C */
	rescan,		/* ^D */
	rescan,		/* ^E */
	rescan,		/* ^F */
	rescan,		/* ^G */
	rescan,		/* ^H */
	cc_tab,		/* ^I */
	rescan,		/* ^J */
	rescan,		/* ^K */
	rescan,		/* ^L */
	cc_lfindent,	/* ^M */
};

static PF cmode_spec[] = {
	cc_char,	/* : */
};

static struct KEYMAPE (1) cmode_cmap = {
	1,
	1,
	rescan,
	{
		{ 'P', 'P', cmode_cCP, NULL }
	}
};

static struct KEYMAPE (3) cmodemap = {
	3,
	3,
	rescan,
	{
		{ CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap },
		{ ':', ':', cmode_spec, NULL },
		{ '}', '}', cmode_brace, NULL }
	}
};

/* Funtion, Mode hooks */

void
cmode_init(void)
{
	funmap_add(cmode, "c-mode", 0);
	funmap_add(cc_char, "c-handle-special-char", 0);
	funmap_add(cc_brace, "c-handle-special-brace", 0);
	funmap_add(cc_tab, "c-tab-or-indent", 0);
	funmap_add(cc_indent, "c-indent", 0);
	funmap_add(cc_lfindent, "c-indent-and-newline", 0);
	maps_add((KEYMAP *)&cmodemap, "c");
}

/*
 * Enable/toggle c-mode
 */
int
cmode(int f, int n)
{
	return(changemode(f, n, "c"));
}

/*
 * Handle special C character - selfinsert then indent.
 */
int
cc_char(int f, int n)
{
	if (n < 0)
		return (FALSE);
	if (selfinsert(FFRAND, n) == FALSE)
		return (FALSE);
	return (cc_indent(FFRAND, n));
}

/*
 * Handle special C character - selfinsert then indent.
 */
int
cc_brace(int f, int n)
{
	if (n < 0)
		return (FALSE);
	if (showmatch(FFRAND, 1) == FALSE)
		return (FALSE);
	return (cc_indent(FFRAND, n));
}


/*
 * If we are in the whitespace at the beginning of the line,
 * simply act as a regular tab. If we are not, indent
 * current line according to whitespace rules.
 */
int
cc_tab(int f, int n)
{
	int inwhitep = FALSE;	/* In leading whitespace? */

	inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp));

	/* If empty line, or in whitespace */
	if (llength(curwp->w_dotp) == 0 || inwhitep)
		return (selfinsert(f, n));

	return (cc_indent(FFRAND, 1));
}

/*
 * Attempt to indent current line according to KNF rules.
 */
int
cc_indent(int f, int n)
{
	int pi, mi;			/* Previous indents (mi is ignored) */
	int ci;				/* current indent */
	struct line *lp;
	int ret;

	if (n < 0)
		return (FALSE);

	undo_boundary_enable(FFRAND, 0);
	if (cc_strip_trailp)
		deltrailwhite(FFRAND, 1);

	/*
	 * Search backwards for a non-blank, non-preprocessor,
	 * non-comment line
	 */

	lp = findnonblank(curwp->w_dotp);

	pi = getindent(lp, &mi);

	/* Strip leading space on current line */
	delleadwhite(FFRAND, 1);
	/* current indent is computed only to current position */
	(void)getindent(curwp->w_dotp, &ci);

	if (pi + ci < 0)
		ret = indent(FFOTHARG, 0);
	else
		ret = indent(FFOTHARG, pi + ci);

	undo_boundary_enable(FFRAND, 1);

	return (ret);
}

/*
 * Indent-and-newline (technically, newline then indent)
 */
int
cc_lfindent(int f, int n)
{
	if (n < 0)
		return (FALSE);
	if (enewline(FFRAND, 1) == FALSE)
		return (FALSE);
	return (cc_indent(FFRAND, n));
}

/*
 * Get the level of indention after line lp is processed
 * Note getindent has two returns:
 * curi = value if indenting current line.
 * return value = value affecting subsequent lines.
 */
static int
getindent(const struct line *lp, int *curi)
{
	int lo, co;		/* leading space,  current offset*/
	int nicol = 0;		/* position count */
	int c = '\0';		/* current char */
	int newind = 0;		/* new index value */
	int stringp = FALSE;	/* in string? */
	int escp = FALSE;	/* Escape char? */
	int lastc = '\0';	/* Last matched string delimeter */
	int nparen = 0;		/* paren count */
	int obrace = 0;		/* open brace count */
	int cbrace = 0;		/* close brace count */
	int firstnwsp = FALSE;	/* First nonspace encountered? */
	int colonp = FALSE;	/* Did we see a colon? */
	int questionp = FALSE;	/* Did we see a question mark? */
	int slashp = FALSE;	/* Slash? */
	int astp = FALSE;	/* Asterisk? */
	int cpos = -1;		/* comment position */
	int cppp  = FALSE;	/* Preprocessor command? */

	*curi = 0;

	/* Compute leading space */
	for (lo = 0; lo < llength(lp); lo++) {
		if (!isspace(c = lgetc(lp, lo)))
			break;
		if (c == '\t'
#ifdef NOTAB
		    && !(curbp->b_flag & BFNOTAB)
#endif /* NOTAB */
		    ) {
			nicol |= 0x07;
		}
		nicol++;
	}

	/* If last line was blank, choose 0 */
	if (lo == llength(lp))
		nicol = 0;

	newind = 0;
	/* Compute modifiers */
	for (co = lo; co < llength(lp); co++) {
		c = lgetc(lp, co);
		/* We have a non-whitespace char */
		if (!firstnwsp && !isspace(c)) {
			if (c == '#')
				cppp = TRUE;
			firstnwsp = TRUE; 
		}
		if (c == '\\')
			escp = !escp;
		else if (stringp) {
			if (!escp && (c == '"' || c == '\'')) {
				/* unescaped string char */
				if (getmatch(c, lastc))
					stringp = FALSE;
			}
		} else if (c == '"' || c == '\'') {
			stringp = TRUE;
			lastc = c;
		} else if (c == '(') {
			nparen++;
		} else if (c == ')') {
			nparen--;
		} else if (c == '{') {
			obrace++;
			firstnwsp = FALSE;
		} else if (c == '}') {
			cbrace++;
		} else if (c == '?') {
			questionp = TRUE;
		} else if (c == ':') {
			/* ignore (foo ? bar : baz) construct */
			if (!questionp)
				colonp = TRUE;
		} else if (c == '/') {
			/* first nonwhitespace? -> indent */
			if (firstnwsp) {
				/* If previous char asterisk -> close */
				if (astp)
					cpos = -1;
				else
					slashp = TRUE;
			}
		} else if (c == '*') {
			/* If previous char slash -> open */
			if (slashp)
				cpos = co;
			else
				astp = TRUE;
		} else if (firstnwsp) {
			firstnwsp = FALSE;
		}

		/* Reset matches that apply to next character only */
		if (c != '\\')
			escp = FALSE;
		if (c != '*')
			astp = FALSE;
		if (c != '/')
			slashp = FALSE;
	}
	/*
	 * If not terminated with a semicolon, and brace or paren open.
	 * we continue
	 */
	if (colonp) {
		*curi += cc_colon_indent;
		newind -= cc_colon_indent;
	}

	*curi -= (cbrace) * cc_basic_indent;
	newind += obrace * cc_basic_indent;

	if (nparen < 0)
		newind -= cc_cont_indent;
	else if (nparen > 0)
		newind += cc_cont_indent;

	*curi += nicol;

	/* Ignore preprocessor. Otherwise, add current column */
	if (cppp) {
		newind = nicol;
		*curi = 0;
	} else {
		newind += nicol;
	}

	if (cpos != -1)
		newind = findcolpos(curbp, lp, cpos);

	return (newind);
}

/*
 * Given a delimeter and its purported mate, tell us if they
 * match.
 */
static int
getmatch(int c, int mc)
{
	int match = FALSE;

	switch (c) {
	case '"':
		match = (mc == '"');
		break;
	case '\'':
		match = (mc == '\'');
		break;
	case '(':
		match = (mc == ')');
		break;
	case '[':
		match = (mc == ']');
		break;
	case '{':
		match = (mc == '}');
		break;
	}

	return (match);
}

static int
in_whitespace(struct line *lp, int len)
{
	int lo;
	int inwhitep = FALSE;

	for (lo = 0; lo < len; lo++) {
		if (!isspace(lgetc(lp, lo)))
			break;
		if (lo == len - 1)
			inwhitep = TRUE;
	}

	return (inwhitep);
}


/* convert a line/offset pair to a column position (for indenting) */
static int
findcolpos(const struct buffer *bp, const struct line *lp, int lo)
{
	int	col, i, c;
	char tmp[5];

	/* determine column */
	col = 0;

	for (i = 0; i < lo; ++i) {
		c = lgetc(lp, i);
		if (c == '\t'
#ifdef NOTAB
		    && !(bp->b_flag & BFNOTAB)
#endif /* NOTAB */
			) {
			col |= 0x07;
			col++;
		} else if (ISCTRL(c) != FALSE)
			col += 2;
		else if (isprint(c)) {
			col++;
		} else {
			col += snprintf(tmp, sizeof(tmp), "\\%o", c);
		}

	}
	return (col);
}

/*
 * Find a non-blank line, searching backwards from the supplied line pointer.
 * For C, nonblank is non-preprocessor, non C++, and accounts
 * for complete C-style comments.
 */
static struct line *
findnonblank(struct line *lp)
{
	int lo;
	int nonblankp = FALSE;
	int commentp = FALSE;
	int slashp;
	int astp;
	int c;

	while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) {
		lp = lback(lp);
		slashp = FALSE;
		astp = FALSE;

		/* Potential nonblank? */
		nonblankp = isnonblank(lp, llength(lp));

		/*
		 * Search from end, removing complete C-style
		 * comments. If one is found, ignore it and
		 * test for nonblankness from where it starts.
		 */
		for (lo = llength(lp) - 1; lo >= 0; lo--) {
			if (!isspace(c = lgetc(lp, lo))) {
				if (commentp) { /* find comment "open" */
					if (c == '*')
						astp = TRUE;
					else if (astp && c == '/') {
						commentp = FALSE;
						/* whitespace to here? */
						nonblankp = isnonblank(lp, lo);
					}
				} else { /* find comment "close" */
					if (c == '/')
						slashp = TRUE;
					else if (slashp && c == '*')
						/* found a comment */
						commentp = TRUE;
				}
			}
		}
	}

	/* Rewound to start of file? */
	if (lback(lp) == curbp->b_headp && !nonblankp)
		return (curbp->b_headp);

	return (lp);
}

/*
 * Given a line, scan forward to 'omax' and determine if we
 * are all C whitespace.
 * Note that preprocessor directives and C++-style comments
 * count as whitespace. C-style comments do not, and must
 * be handled elsewhere.
 */
static int
isnonblank(const struct line *lp, int omax)
{
	int nonblankp = FALSE;		/* Return value */
	int slashp = FALSE;		/* Encountered slash */
	int lo;				/* Loop index */
	int c;				/* char being read */

	/* Scan from front for preprocessor, C++ comments */
	for (lo = 0; lo < omax; lo++) {
		if (!isspace(c = lgetc(lp, lo))) {
			/* Possible nonblank line */
			nonblankp = TRUE;
			/* skip // and # starts */
			if (c == '#' || (slashp && c == '/')) {
				nonblankp = FALSE;
				break;
			} else if (!slashp && c == '/') {
				slashp = TRUE;
				continue;
			}
		}
		slashp = FALSE;
	}
	return (nonblankp);
}