shithub: scc

ref: db7877e7b5317f8c3fef848096fbffd498f4eb69
dir: /src/cmd/cc/cc1/lex.c/

View raw version
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <scc/cstd.h>
#include <scc/scc.h>
#include "cc1.h"

int yytoken;
struct yystype yylval;
char yytext[STRINGSIZ+3];
unsigned short yylen;
int lexmode = CCMODE;
unsigned lineno;
char filenam[FILENAME_MAX];

int namespace = NS_IDEN;
static int safe;
Input *input;

void
setloc(char *fname, unsigned line)
{
	size_t len;

	if (fname) {
		if ((len = strlen(fname)) >= FILENAME_MAX)
			die("cc1: %s: file name too long", fname);
		memmove(filenam, fname, len);
		filenam[len] = '\0';

		/*
		 * There are cases where we want to call setloc()
		 * with the data in input, and then we have t be
		 * careful about freeing input->filenam
		 */
		if (fname != input->filenam) {
			free(input->filenam);
			input->filenam = xstrdup(fname);
		}
	}

	lineno = input->lineno = line;
}

static void
hide(Symbol *sym)
{
	assert(sym->hide == 0);
	sym->hide = 1;
	DBG("SYM: hidding symbol %s %d", sym->name, sym->hide);
}

static void
unhide(Symbol *sym)
{
	assert(sym->hide == 1);
	DBG("SYM: unhidding symbol %s %d", sym->name, sym->hide);
	sym->hide = 0;
}

int
addinput(int type, void *arg, int fail)
{
	FILE *fp;
	char *extp, *fname, *buffer, *infile;;
	int infileln;
	Macro *mp;
	Symbol *sym;
	Input *newip, *curip = input;

	if (curip)
		curip->lineno = lineno;

	switch (type) {
	case IMACRO:
		fp = NULL;
		mp = arg;
		sym = mp->sym;
		fname = mp->fname;
		buffer = mp->buffer;
		hide(sym);
		DBG("INPUT: macro %s expanded to '%s'", sym->name, buffer);
		break;
	case IPARAM:
		fp = NULL;
		mp = NULL;
		buffer = arg;
		fname = filenam;
		DBG("INPUT: macro parameter '%s'", buffer);
		break;
	case IFILE:
		fname = arg;
		mp = NULL;
		buffer = NULL;

		if ((fp = fopen(fname, "r")) == NULL) {
			if (!fail)
				return 0;
			die("cc1: %s: %s", fname, strerror(errno));
		}
		if (curip && onlyheader) {
			infile = curip->filenam;
			infileln = strlen(infile);
			if (extp = strrchr(infile, '.'))
				infileln -= strlen(extp);
			/* TODO: is this C99? */
			printf("%.*s.o: %s %s\n",
			       infileln, infile, infile, fname);
		}
		lineno = 0;
		DBG("INPUT: file input '%s'", fname);
		break;
	case ISTDIN:
		fp = stdin;
		mp = NULL;
		fname = "<stdin>";
		buffer = NULL;
		lineno = 0;
		DBG("INPUT: file input 'stdin'");
		break;
	default:
		abort();
	}

	if (!buffer) {
		buffer = xmalloc(INPUTSIZ);
		buffer[0] = '\0';
	} else {
		buffer = xstrdup(buffer);
	}

	newip = xmalloc(sizeof(*newip));
	newip->next = curip;
	newip->macro = mp;
	newip->p = newip->begin = newip->line = buffer;
	newip->filenam = NULL;
	newip->lineno = 0;
	newip->fp = fp;
	newip->flags = type;
	input = newip;

	setloc(fname, lineno);
	return 1;
}

void
delinput(void)
{
	Input *ip = input;

	switch (ip->flags & ITYPE) {
	case IFILE:
		DBG("INPUT: file finished '%s'", ip->filenam);
		if (fclose(ip->fp))
			die("cc1: %s: %s", ip->filenam, strerror(errno));
		break;
	case IMACRO:
		DBG("INPUT: macro %s finished", ip->macro->sym->name);
		unhide(ip->macro->sym);
		break;
	case IPARAM:
		DBG("INPUT: macro param finished");
		break;
	case ISTDIN:
		DBG("INPUT: stdin finished");
		break;
	default:
		abort();
	}

	input = ip->next;
	delmacro(ip->macro);
	free(ip->filenam);
	free(ip->line);
	free(ip);
	if (input)
		setloc(input->filenam, input->lineno);
}

static void
newline(void)
{
	if (++lineno == 0)
		die("cc1: %s: file too long", filenam);
}

/*
 * Read the next character from the input file, counting number of lines
 * and joining lines escaped with \
 */
static int
readchar(void)
{
	FILE *fp = input->fp;
	int c;

repeat:
	switch (c = getc(fp)) {
	case '\\':
		if ((c = getc(fp)) == '\n') {
			newline();
			goto repeat;
		}
		ungetc(c, fp);
		c = '\\';
		break;
	case '\n':
		newline();
		break;
	default:
		if (!isprint(c) && !ispunct(c) && !isspace(c))
			warn("invalid input character. The shame of UB is yours");
		break;
	}

	return c;
}

/*
 * discard a C comment. This function is only called from readline
 * because it is impossible to have a comment in a macro, because
 * comments are always discarded before processing any cpp directive
 */
static void
comment(int type)
{
	int c;

repeat:
	while ((c = readchar()) != EOF && c != type)
		;

	if (c == EOF) {
		errorp("unterminated comment");
		return;
	}

	if (type == '*' && (c = readchar()) != '/')
		goto repeat;
}

/*
 * readline is used to read a full logic line from a file.
 * It discards comments and check that the line fits in
 * the input buffer
 */
static int
readline(void)
{
	char *bp, *lim;
	int c, peekc = 0, delim = 0;

	if (feof(input->fp)) {
		input->flags |= IEOF;
		*input->p = '\0';
		return 0;
	}

	*input->line = '\0';
	lim = &input->line[INPUTSIZ-1];
	for (bp = input->line; bp < lim-1; *bp++ = c) {
		c = (peekc) ? peekc : readchar();
		peekc = 0;
		if (c == '\n' || c == EOF)
			break;
		if (c == '\\') {
			peekc = readchar();
			if (peekc == '\n' || peekc == EOF)
				continue;
			if (bp == lim-2)
				break;
			*bp++ = c;
			c = peekc;
			peekc = 0;
			continue;
		}

		if (delim && c == delim)
			delim = 0;
		else if (!delim && (c == '"' || c == '\''))
			delim = c;
		if (c != '/' || delim)
			continue;

		/* check for /* or // */
		peekc = readchar();
		if (peekc != '*' && peekc != '/')
			continue;

		if (peekc == '/') {
			comment('\n');
			break;
		} else {
			comment('*');
			c = ' ';
		}
		peekc = 0;
	}

	input->begin = input->p = input->line;
	if (bp == lim-1) {
		errorp("line too long");
		--bp;
	}
	*bp++ = '\n';
	*bp = '\0';

	return 1;
}

/*
 * moreinput gets more bytes to be passed to the lexer.
 * It can take more bytes from macro expansions or
 * directly reading from files. When a cpp directive
 * is processed the line is discarded because it must not
 * be passed to the lexer
 */
static int
moreinput(void)
{
	int wasexpand = 0;

repeat:
	if (!input)
		return 0;

	if (*input->p == '\0') {
		int t = input->flags & ITYPE;
		if (t == IPARAM) {
			input->flags |= IEOF;
			return 0;
		}
		if (t == IMACRO) {
			wasexpand = 1;
			input->flags |= IEOF;
		}
		if (input->flags & IEOF) {
			delinput();
			goto repeat;
		}
		if (!readline()) {
			*input->p = '\0';
			goto repeat;
		}
		if (cpp()) {
			*input->p = '\0';
			goto repeat;
		}
	}

	if (onlycpp && !wasexpand)
		ppragmaln();
	return 1;
}

static void
tok2str(void)
{
	if ((yylen = input->p - input->begin) > INTIDENTSIZ)
		error("token too big");
	memcpy(yytext, input->begin, yylen);
	yytext[yylen] = '\0';
	input->begin = input->p;
}

static Symbol *
readint(char *s, int base, int sign, Symbol *sym)
{
	Type *tp = sym->type;
	struct limits *lim;
	TUINT u, val, max;
	int c;

	lim = getlimits(tp);
	max = lim->max.i;
	if (*s == '0')
		++s;
	if (toupper(*s) == 'X')
		++s;

	for (u = 0; isxdigit(c = *s++); u = u*base + val) {
		static char letters[] = "0123456789ABCDEF";
		val = strchr(letters, toupper(c)) - letters;
	repeat:
		if (u <= max/base && u*base <= max - val)
			continue;
		if (tp->prop & TSIGNED) {
			if (tp == inttype)
				tp = (base==10) ? longtype : uinttype;
			else if (tp == longtype)
				tp = (base==10) ? llongtype : ulongtype;
			else
				goto overflow;
		} else {
			if (tp == uinttype)
				tp = (sign==UNSIGNED) ? ulongtype : longtype;
			else if (tp == ulongtype)
				tp = (sign==UNSIGNED) ? ullongtype : llongtype;
			else
				goto overflow;
		}
		sym->type = tp;
		lim = getlimits(tp);
		max = lim->max.i;
		goto repeat;
	}

	if (tp->prop & TSIGNED)
		sym->u.i = u;
	else
		sym->u.u = u;

	return sym;

overflow:
	errorp("overflow in integer constant");
	return sym;
}

static int
integer(char *s, int base)
{
	Type *tp;
	Symbol *sym;
	unsigned size, sign;

	for (size = sign = 0; ; ++input->p) {
		switch (toupper(*input->p)) {
		case 'L':
			if (size == LLONG)
				goto wrong_type;
			size = (size == LONG) ? LLONG : LONG;
			continue;
		case 'U':
			if (sign == UNSIGNED)
				goto wrong_type;
			sign = UNSIGNED;
			continue;
		default:
			goto convert;
		wrong_type:
			error("invalid suffix in integer constant");
		}
	}

convert:
	tp = ctype(INT, sign, size);
	sym = newsym(NS_IDEN, NULL);
	sym->type = tp;
	sym->flags |= SCONSTANT;
	yylval.sym = readint(s, base, sign, sym);
	return CONSTANT;
}

static char *
digits(int base)
{
	char *p;
	int c;

	for (p = input->p; c = *p; ++p) {
		switch (base) {
		case 8:
			if (!strchr("01234567", c))
				goto end;
			break;
		case 10:
			if (!isdigit(c))
				goto end;
			break;
		case 16:
			if (!isxdigit(c))
				goto end;
			break;
		}
	}
end:
	input->p = p;
	tok2str();
	return yytext;
}

static int
number(void)
{
	int base;

	if (*input->p != '0') {
		base = 10;
	} else {
		if (toupper(*++input->p) == 'X') {
			++input->p;
			base = 16;
		} else {
			base = 8;
		}
	}

	return integer(digits(base), base);
}

static int
escape(void)
{
	int c, base;

	switch (*++input->p) {
	case 'a':
		return '\a';
	case 'f':
		return '\f';
	case 'n':
		return '\n';
	case 'r':
		return '\r';
	case 't':
		return '\t';
	case 'v':
		return '\v';
	case '"':
		return '"';
	case '\'':
		return '\'';
	case '\\':
		return '\\';
	case '\?':
		return '\?';
	case 'u':
		/*
		 * FIXME: universal constants are not correctly handled
		 */
		if (!isdigit(*++input->p))
			warn("incorrect digit for numerical character constant");
		base = 10;
		break;
	case 'x':
		if (!isxdigit(*++input->p))
			warn("\\x used with no following hex digits");
		base = 16;
		break;
	case '0':
		if (!strchr("01234567", *++input->p))
			warn("\\0 used with no following octal digits");
		base = 8;
		break;
	default:
		warn("unknown escape sequence");
		return ' ';
	}
	errno = 0;
	/* FIXME: We don't check that there is an actual number */
	c = strtoul(input->p, &input->p, base);
	if (errno || c > 255)
		warn("character constant out of range");
	--input->p;
	return c;
}

static int
character(void)
{
	int c;
	Symbol *sym;

	if ((c = *++input->p) == '\\')
		c = escape();
	else
		c = *input->p;
	++input->p;
	if (*input->p != '\'')
		errorp("invalid character constant");
	else
		++input->p;

	sym = newsym(NS_IDEN, NULL);
	sym->u.i = c;
	sym->type = inttype;
	yylval.sym = sym;
	tok2str();
	return CONSTANT;
}

/*
 * string() parses a constant string, and convert all the
 * escape sequences into single characters. This behaviour
 * is correct except when we parse a #define, where we want
 * to preserve the literal content of the string. In that
 * case cpp.c:/^define( sets the variable disescape to
 * disable converting escape sequences into characters.
 */
static int
string(void)
{
	char *bp = yytext;
	int c, esc;

	*bp++ = '"';
	esc = 0;
	for (++input->p; ; ++input->p) {
		c = *input->p;

		if (c == '"' && !esc)
			break;

		if (c == '\0') {
			errorp("missing terminating '\"' character");
			break;
		}

		if (c == '\\' && !esc && disescape)
			esc = 1;
		else
			esc = 0;

		if (c == '\\' && !esc)
			c = escape();

		if (bp == &yytext[STRINGSIZ+1]) {
			/* too long, ignore everything until next quote */
			for (++input->p; *input->p != '"'; ++input->p) {
				if (*input->p == '\\')
					++input->p;
				if (*input->p == '\0')
					break;
			}
			--bp;
			errorp("string too long");
			break;
		}
		*bp++ = c;
	}

	input->begin = ++input->p;
	*bp = '\0';

	yylen = bp - yytext + 1;
	yylval.sym = newstring(yytext+1, yylen-1);
	*bp++ = '"';
	*bp = '\0';
	return STRING;
}

static int
iden(void)
{
	Symbol *sym;
	char *p, *begin;

	begin = input->p;
	for (p = begin; isalnum(*p) || *p == '_'; ++p)
		;
	input->p = p;
	tok2str();
	if ((sym = lookup(NS_CPP, yytext, NOALLOC)) != NULL) {
		if (expand(sym))
			return next();
	}
	sym = lookup(namespace, yytext, ALLOC);
	yylval.sym = sym;
	if (sym->flags & SCONSTANT)
		return CONSTANT;
	if (sym->token != IDEN)
		yylval.token = sym->u.token;
	return sym->token;
}

static int
follow(int expect, int ifyes, int ifno)
{
	if (*input->p++ == expect)
		return ifyes;
	--input->p;
	return ifno;
}

static int
minus(void)
{
	switch (*input->p++) {
	case '-':
		return DEC;
	case '>':
		return INDIR;
	case '=':
		return SUB_EQ;
	default:
		--input->p;
		return '-';
	}
}

static int
plus(void)
{
	switch (*input->p++) {
	case '+':
		return INC;
	case '=':
		return ADD_EQ;
	default:
		--input->p;
		return '+';
	}
}

static int
relational(int op, int equal, int shift, int assig)
{
	int c;

	if ((c = *input->p++) == '=')
		return equal;
	if (c == op)
		return follow('=', assig, shift);
	--input->p;
	return op;
}

static int
logic(int op, int equal, int logic)
{
	int c;

	if ((c = *input->p++) == '=')
		return equal;
	if (c == op)
		return logic;
	--input->p;
	return op;
}

static int
dot(void)
{
	int c;

	if ((c = *input->p) != '.')
		return '.';
	if ((c = *++input->p) != '.')
		error("incorrect token '..'");
	++input->p;
	return ELLIPSIS;
}

static int
operator(void)
{
	int t;

	switch (t = *input->p++) {
	case '<':
		t = relational('<', LE, SHL, SHL_EQ);
		break;
	case '>':
		t = relational('>', GE, SHR, SHR_EQ);
		break;
	case '&':
		t = logic('&', AND_EQ, AND);
		break;
	case '|':
		t = logic('|', OR_EQ, OR);
		break;
	case '=':
		t = follow('=', EQ, '=');
		break;
	case '^':
		t = follow('=', XOR_EQ, '^');
		break;
	case '*':
		t = follow('=', MUL_EQ, '*');
		break;
	case '/':
		t = follow('=', DIV_EQ, '/');
		break;
	case '%':
		t = follow('=', MOD_EQ, '%');
		break;
	case '!':
		t = follow('=', NE, '!');
		break;
	case '#':
		t = follow('#', CONCAT, STRINGIZE);
		break;
	case '-':
		t = minus();
		break;
	case '+':
		t = plus();
		break;
	case '.':
		t = dot();
		break;
	}
	tok2str();
	return t;
}

/* TODO: Ensure that namespace is NS_IDEN after a recovery */

/*
 * skip all the spaces until the next token. When we are in
 * CPPMODE \n is not considered a whitespace
 */
static int
skipspaces(void)
{
	int c;

	for (;;) {
		switch (c = *input->p) {
		case '\n':
			if (lexmode == CPPMODE)
				goto return_byte;
			++input->p;
		case '\0':
			if (!moreinput())
				return EOF;
			break;
		case ' ':
		case '\t':
		case '\v':
		case '\r':
		case '\f':
			++input->p;
			break;
		default:
			goto return_byte;
		}
	}

return_byte:
	input->begin = input->p;
	return c;
}

int
next(void)
{
	int c;

	if ((c = skipspaces()) == EOF)
		yytoken = EOFTOK;
	else if (isalpha(c) || c == '_')
		yytoken = iden();
	else if (isdigit(c))
		yytoken = number();
	else if (c == '"')
		yytoken = string();
	else if (c == '\'')
		yytoken = character();
	else
		yytoken = operator();

	if (yytoken == EOFTOK) {
		strcpy(yytext, "<EOF>");
		if (cppctx)
			errorp("#endif expected");
	}

	DBG("TOKEN %s", yytext);
	return yytoken;
}

void
expect(int tok)
{
	if (yytoken != tok) {
		if (isgraph(tok))
			errorp("expected '%c' before '%s'", tok, yytext);
		else
			errorp("unexpected '%s'", yytext);
	} else {
		next();
	}
}

int
ahead(void)
{
	skipspaces();
	return *input->begin;
}

void
setsafe(int type)
{
	safe = type;
}

void
discard(void)
{
	extern jmp_buf recover;
	int c;

	if (!input)
		return;

	for (c = yytoken; ; c = *input->p++) {
		switch (safe) {
		case END_COMP:
			if (c == '}')
				goto jump;
			goto semicolon;
		case END_COND:
			if (c == ')')
				goto jump;
			break;
		case END_LDECL:
			if (c == ',')
				goto jump;
		case END_DECL:
		semicolon:
			if (c == ';')
				goto jump;
			break;
		}
		if (c == '\0' && !moreinput())
			exit(1);
	}
jump:
	input->begin = input->p;
	yytoken = c;
	yytext[0] = c;
	yytext[1] = '\0';
	exit(EXIT_FAILURE);

	/*
	 * FIXME: We don't have a proper recover mechanism at this moment
	 * and we don't set the recover point ever, so executing this
	 * longjmp will generate surely a segmentation fault, so it does
	 * not make sense to do it. We just exit until we can find time
	 * to solve this problem.
	 */
	longjmp(recover, 1);
}