#include <sys/cdefs.h>
__RCSID("$NetBSD: lexi.c,v 1.242 2023/12/03 21:44:42 rillig Exp $");
#include <stdlib.h>
#include <string.h>
#include "indent.h"
static const struct keyword {
const char name[12];
lexer_symbol lsym;
} keywords[] = {
{"_Bool", lsym_type},
{"_Complex", lsym_modifier},
{"_Imaginary", lsym_modifier},
{"auto", lsym_modifier},
{"bool", lsym_type},
{"break", lsym_word},
{"case", lsym_case},
{"char", lsym_type},
{"complex", lsym_modifier},
{"const", lsym_modifier},
{"continue", lsym_word},
{"default", lsym_default},
{"do", lsym_do},
{"double", lsym_type},
{"else", lsym_else},
{"enum", lsym_tag},
{"extern", lsym_modifier},
{"float", lsym_type},
{"for", lsym_for},
{"goto", lsym_word},
{"if", lsym_if},
{"imaginary", lsym_modifier},
{"inline", lsym_modifier},
{"int", lsym_type},
{"long", lsym_type},
{"offsetof", lsym_offsetof},
{"register", lsym_modifier},
{"restrict", lsym_word},
{"return", lsym_return},
{"short", lsym_type},
{"signed", lsym_type},
{"sizeof", lsym_sizeof},
{"static", lsym_modifier},
{"struct", lsym_tag},
{"switch", lsym_switch},
{"typedef", lsym_typedef},
{"union", lsym_tag},
{"unsigned", lsym_type},
{"void", lsym_type},
{"volatile", lsym_modifier},
{"while", lsym_while}
};
static struct {
const char **items;
unsigned int len;
unsigned int cap;
} typenames;
static const unsigned char lex_number_state[][26] = {
[0] = "uuiifuufiuuiiuiiiiiuiuuuuu",
[1] = "CEIDEHHHIJQ U Q VUVVZZZ",
[2] = "DEIDEHHHIJQ U Q VUVVZZZ",
[3] = "DEIDEHHHIJ U VUVVZZZ",
[4] = "DEJDEHHHJJ U VUVVZZZ",
[5] = " U VUVV ",
[6] = " K U VUVV ",
[7] = " FFF FF U VUVV ",
[8] = " f f U VUVV f",
[9] = " LLf fL PR Li L f",
[10] = " OOf fO S P O i O f",
[11] = " FFX ",
[12] = " MM M i iiM M ",
[13] = " N ",
[14] = " G Y ",
[15] = "B EE EE T W ",
};
static const unsigned char lex_number_row[] = {
['0'] = 1,
['1'] = 2,
['2'] = 3, ['3'] = 3, ['4'] = 3, ['5'] = 3, ['6'] = 3, ['7'] = 3,
['8'] = 4, ['9'] = 4,
['A'] = 5, ['a'] = 5, ['C'] = 5, ['c'] = 5, ['D'] = 5, ['d'] = 5,
['B'] = 6, ['b'] = 6,
['E'] = 7, ['e'] = 7,
['F'] = 8, ['f'] = 8,
['L'] = 9,
['l'] = 10,
['P'] = 11, ['p'] = 11,
['U'] = 12, ['u'] = 12,
['X'] = 13, ['x'] = 13,
['+'] = 14, ['-'] = 14,
['.'] = 15,
};
static bool
is_identifier_start(char ch)
{
return ch_isalpha(ch) || ch == '_' || ch == '$';
}
static bool
is_identifier_part(char ch)
{
return ch_isalnum(ch) || ch == '_' || ch == '$';
}
static void
token_add_char(char ch)
{
buf_add_char(&token, ch);
}
static bool
skip_line_continuation(void)
{
if (in.p[0] == '\\' && in.p[1] == '\n') {
in.p++;
inp_skip();
in.token_end_line++;
return true;
}
return false;
}
static void
lex_number(void)
{
for (unsigned char s = 'A'; s != 'f' && s != 'i' && s != 'u';) {
unsigned char ch = (unsigned char)*in.p;
if (skip_line_continuation())
continue;
if (ch >= array_length(lex_number_row)
|| lex_number_row[ch] == 0)
break;
unsigned char row = lex_number_row[ch];
if (lex_number_state[row][s - 'A'] == ' ') {
return;
}
s = lex_number_state[row][s - 'A'];
token_add_char(inp_next());
}
}
static void
lex_word(void)
{
for (;;) {
if (is_identifier_part(*in.p))
token_add_char(*in.p++);
else if (skip_line_continuation())
continue;
else
return;
}
}
static void
lex_char_or_string(void)
{
for (char delim = token.s[token.len - 1];;) {
if (*in.p == '\n') {
diag(1, "Unterminated literal");
return;
}
token_add_char(*in.p++);
if (token.s[token.len - 1] == delim)
return;
if (token.s[token.len - 1] == '\\') {
if (*in.p == '\n')
in.token_end_line++;
token_add_char(inp_next());
}
}
}
static bool
probably_typename(void)
{
if (ps.prev_lsym == lsym_modifier)
return true;
if (ps.in_init)
return false;
if (ps.in_stmt_or_decl)
return false;
if (ps.prev_lsym == lsym_semicolon
|| ps.prev_lsym == lsym_lbrace
|| ps.prev_lsym == lsym_rbrace) {
if (in.p[0] == '*' && in.p[1] != '=')
return true;
if (ch_isalpha(in.p[0]))
return true;
}
return false;
}
static int
bsearch_typenames(const char *key)
{
const char **arr = typenames.items;
unsigned lo = 0;
unsigned hi = typenames.len;
while (lo < hi) {
unsigned mid = (lo + hi) / 2;
int cmp = strcmp(arr[mid], key);
if (cmp < 0)
lo = mid + 1;
else if (cmp > 0)
hi = mid;
else
return (int)mid;
}
return -1 - (int)lo;
}
static bool
is_typename(void)
{
if (ps.prev_lsym == lsym_tag)
return true;
if (opt.auto_typedefs &&
token.len >= 2 && memcmp(token.s + token.len - 2, "_t", 2) == 0)
return true;
return bsearch_typenames(token.s) >= 0;
}
void
register_typename(const char *name)
{
if (typenames.len >= typenames.cap) {
typenames.cap = 16 + 2 * typenames.cap;
typenames.items = nonnull(realloc(typenames.items,
sizeof(typenames.items[0]) * typenames.cap));
}
int pos = bsearch_typenames(name);
if (pos >= 0)
return;
pos = -1 - pos;
memmove(typenames.items + pos + 1, typenames.items + pos,
sizeof(typenames.items[0]) * (typenames.len++ - (unsigned)pos));
typenames.items[pos] = nonnull(strdup(name));
}
static int
cmp_keyword_by_name(const void *key, const void *elem)
{
return strcmp(key, ((const struct keyword *)elem)->name);
}
static bool
probably_function_definition(const char *p)
{
int paren_level = 0;
for (; *p != '\n'; p++) {
if (*p == '(')
paren_level++;
if (*p == ')' && --paren_level == 0) {
p++;
while (*p != '\n'
&& (ch_isspace(*p) || is_identifier_part(*p)))
p++;
if (*p == '\n')
break;
if (*p == ';')
return false;
if (*p == ',')
return false;
if (*p == '(')
paren_level++;
else
break;
}
if (paren_level == 1 && p[0] == '*' && p[1] == ',')
return false;
}
return true;
}
static lexer_symbol
lexi_alnum(void)
{
if (ch_isdigit(in.p[0]) ||
(in.p[0] == '.' && ch_isdigit(in.p[1]))) {
lex_number();
} else if (is_identifier_start(in.p[0])) {
lex_word();
if (token.len == 1 && token.s[0] == 'L' &&
(in.p[0] == '"' || in.p[0] == '\'')) {
token_add_char(*in.p++);
lex_char_or_string();
ps.next_unary = false;
return lsym_word;
}
} else
return lsym_eof;
while (ch_isblank(*in.p))
in.p++;
ps.next_unary = ps.prev_lsym == lsym_tag
|| ps.prev_lsym == lsym_typedef
|| (ps.prev_lsym == lsym_modifier && *in.p == '*');
if (ps.prev_lsym == lsym_tag && ps.paren.len == 0)
return lsym_type;
if (ps.spaced_expr_psym == psym_for_exprs
&& ps.prev_lsym == lsym_lparen && ps.paren.len == 1
&& *in.p == '*') {
ps.next_unary = true;
return lsym_type;
}
token_add_char('\0');
token.len--;
const struct keyword *kw = bsearch(token.s, keywords,
array_length(keywords), sizeof(keywords[0]), cmp_keyword_by_name);
lexer_symbol lsym = lsym_word;
if (kw != NULL) {
lsym = kw->lsym;
ps.next_unary = true;
if (lsym == lsym_tag || lsym == lsym_type)
goto found_typename;
return lsym;
}
if (is_typename()) {
lsym = lsym_type;
ps.next_unary = true;
found_typename:
if (ps.prev_lsym != lsym_period
&& ps.prev_lsym != lsym_unary_op) {
if (lsym == lsym_tag)
return lsym_tag;
if (ps.paren.len == 0)
return lsym_type;
}
}
const char *p = in.p;
if (*p == ')')
p++;
if (*p == '(' && ps.psyms.len < 3 && ps.ind_level == 0 &&
!ps.in_func_def_params && !ps.in_init) {
bool maybe_function_definition = *in.p == ')'
? ps.paren.len == 1 && ps.prev_lsym != lsym_unary_op
: ps.paren.len == 0;
if (maybe_function_definition
&& probably_function_definition(p)) {
ps.line_has_func_def = true;
if (ps.in_decl)
ps.in_func_def_params = true;
return lsym_funcname;
}
} else if (ps.paren.len == 0 && probably_typename()) {
ps.next_unary = true;
return lsym_type;
}
return lsym;
}
static void
check_parenthesized_function_definition(void)
{
const char *p = in.p;
while (ch_isblank(*p))
p++;
if (is_identifier_start(*p))
while (is_identifier_part(*p))
p++;
while (ch_isblank(*p))
p++;
if (*p == ')') {
p++;
while (ch_isblank(*p))
p++;
if (*p == '(' && probably_function_definition(p))
ps.line_has_func_def = true;
}
}
static bool
is_asterisk_unary(void)
{
const char *p = in.p;
while (*p == '*' || ch_isblank(*p))
p++;
if (*p == ')')
return true;
if (ps.next_unary || ps.in_func_def_params)
return true;
if (ps.prev_lsym == lsym_word ||
ps.prev_lsym == lsym_rparen ||
ps.prev_lsym == lsym_rbracket)
return false;
return ps.in_decl && ps.paren.len > 0;
}
static bool
probably_in_function_definition(void)
{
for (const char *p = in.p; *p != '\n';) {
if (ch_isspace(*p))
p++;
else if (is_identifier_start(*p)) {
p++;
while (is_identifier_part(*p))
p++;
} else
return *p == '(';
}
return false;
}
static void
lex_asterisk_unary(void)
{
while (*in.p == '*' || ch_isspace(*in.p)) {
if (*in.p == '*')
token_add_char('*');
if (*in.p == '\n')
in.token_end_line++;
inp_skip();
}
if (ps.in_decl && probably_in_function_definition())
ps.line_has_func_def = true;
}
static bool
skip(const char **pp, const char *s)
{
size_t len = strlen(s);
while (ch_isblank(**pp))
(*pp)++;
if (strncmp(*pp, s, len) == 0) {
*pp += len;
return true;
}
return false;
}
static void
lex_indent_comment(void)
{
const char *p = in.line.s;
if (skip(&p, "/*") && skip(&p, "INDENT")) {
enum indent_enabled enabled;
if (skip(&p, "ON") || *p == '*')
enabled = indent_last_off_line;
else if (skip(&p, "OFF"))
enabled = indent_off;
else
return;
if (skip(&p, "*/\n")) {
if (lab.len > 0 || code.len > 0 || com.len > 0)
output_line();
indent_enabled = enabled;
}
}
}
lexer_symbol
lexi(void)
{
buf_clear(&token);
for (;;) {
if (ch_isblank(*in.p))
in.p++;
else if (skip_line_continuation())
continue;
else
break;
}
in.token_start_line = in.token_end_line;
lexer_symbol alnum_lsym = lexi_alnum();
if (alnum_lsym != lsym_eof)
return alnum_lsym;
token_add_char(inp_next());
lexer_symbol lsym;
bool next_unary;
switch (token.s[token.len - 1]) {
case '#':
lsym = lsym_preprocessing;
next_unary = ps.next_unary;
break;
case '\n':
lsym = had_eof ? lsym_eof : lsym_newline;
next_unary = ps.next_unary;
break;
case ')': lsym = lsym_rparen; next_unary = false; break;
case '[': lsym = lsym_lbracket; next_unary = true; break;
case ']': lsym = lsym_rbracket; next_unary = false; break;
case '{': lsym = lsym_lbrace; next_unary = true; break;
case '}': lsym = lsym_rbrace; next_unary = true; break;
case '.': lsym = lsym_period; next_unary = false; break;
case '?': lsym = lsym_question; next_unary = true; break;
case ',': lsym = lsym_comma; next_unary = true; break;
case ';': lsym = lsym_semicolon; next_unary = true; break;
case '(':
if (in.p == in.line.s + 1)
check_parenthesized_function_definition();
lsym = lsym_lparen;
next_unary = true;
break;
case '+':
case '-':
lsym = ps.next_unary ? lsym_unary_op : lsym_binary_op;
next_unary = true;
if (*in.p == token.s[token.len - 1]) {
token_add_char(*in.p++);
if (ps.prev_lsym == lsym_word ||
ps.prev_lsym == lsym_rparen ||
ps.prev_lsym == lsym_rbracket) {
lsym = ps.next_unary
? lsym_unary_op : lsym_postfix_op;
next_unary = false;
}
} else if (*in.p == '=') {
token_add_char(*in.p++);
} else if (*in.p == '>') {
token_add_char(*in.p++);
lsym = lsym_unary_op;
next_unary = false;
ps.want_blank = false;
}
break;
case ':':
lsym = ps.quest_level > 0
? (ps.quest_level--, lsym_question_colon)
: ps.in_var_decl ? lsym_other_colon : lsym_label_colon;
next_unary = true;
break;
case '*':
if (*in.p == '=') {
token_add_char(*in.p++);
lsym = lsym_binary_op;
} else if (is_asterisk_unary()) {
lex_asterisk_unary();
lsym = lsym_unary_op;
} else
lsym = lsym_binary_op;
next_unary = true;
break;
case '=':
if (ps.in_var_decl)
ps.in_init = true;
if (*in.p == '=')
token_add_char(*in.p++);
lsym = lsym_binary_op;
next_unary = true;
break;
case '>':
case '<':
case '!':
if (*in.p == '>' || *in.p == '<' || *in.p == '=')
token_add_char(*in.p++);
if (*in.p == '=')
token_add_char(*in.p++);
lsym = ps.next_unary ? lsym_unary_op : lsym_binary_op;
next_unary = true;
break;
case '\'':
case '"':
lex_char_or_string();
lsym = lsym_word;
next_unary = false;
break;
default:
if (token.s[token.len - 1] == '/'
&& (*in.p == '*' || *in.p == '/')) {
enum indent_enabled prev = indent_enabled;
lex_indent_comment();
if (prev == indent_on && indent_enabled == indent_off)
buf_clear(&out.indent_off_text);
token_add_char(*in.p++);
lsym = lsym_comment;
next_unary = ps.next_unary;
break;
}
lsym = ps.next_unary ? lsym_unary_op : lsym_binary_op;
if (*in.p == token.s[token.len - 1])
token_add_char(*in.p++), lsym = lsym_binary_op;
if (*in.p == '=')
token_add_char(*in.p++), lsym = lsym_binary_op;
next_unary = true;
}
ps.next_unary = next_unary;
return lsym;
}