evil-qemacs/clang.c
Charlie Gordon e2beb7c910 improve C mode
* improve do_c_indent to prevent buffer modification if indentation is correct
* simplify do_c_indent_region accordingly
* added c-newline to remove indentation on blank lines
2014-01-31 14:47:06 +00:00

712 lines
21 KiB
C

/*
* C mode for QEmacs.
*
* Copyright (c) 2001-2002 Fabrice Bellard.
* Copyright (c) 2002-2014 Charlie Gordon.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "qe.h"
#if 0
static const char cc_keywords[] =
"asm|catch|class|delete|friend|inline|new|operator|"
"private|protected|public|template|try|this|virtual|throw|";
static const char java_keywords[] =
"abstract|boolean|byte|catch|class|extends|false|final|"
"finally|function|implements|import|in|instanceof|"
"interface|native|new|null|package|private|protected|"
"public|super|synchronized|this|throw|throws|transient|"
"true|try|var|with|";
#endif
static const char *c_mode_keywords =
"auto|break|case|const|continue|default|do|else|enum|extern|for|goto|"
"if|inline|register|restrict|return|sizeof|static|struct|switch|"
"typedef|union|volatile|while|";
/* NOTE: 'var' is added for javascript */
static const char *c_mode_types =
"char|double|float|int|long|unsigned|short|signed|void|var|"
"_Bool|_Complex|_Imaginary|";
static const char c_mode_extensions[] =
"c|h|y|l|lex|e|qe|cs|idl|jav|java|js|json|"
"ec|ecp|" /* Informix embedded C */
"pgc|" /* Postgres embedded C */
"pcc|" /* Oracle C++ */
"cc|hh|cpp|hpp|cxx|hxx|C|H|CPP|CC|c++"; /* C++ extensions */
#if 0
static int get_c_identifier(char *buf, int buf_size, unsigned int *p)
{
unsigned int c;
char *q;
c = *p;
q = buf;
if (qe_isalpha_(c)) {
do {
if ((q - buf) < buf_size - 1)
*q++ = c;
p++;
c = *p;
} while (qe_isalnum_(c));
}
*q = '\0';
return q - buf;
}
#endif
/* c-mode colorization states */
enum {
C_COMMENT = 1, /* multiline comment pending */
C_COMMENT1 = 2, /* single line comment with \ at EOL */
C_STRING = 4, /* double quoted string spanning multiple lines */
C_STRING_Q = 8, /* single quoted string spanning multiple lines */
C_PREPROCESS = 16, /* preprocessor directive with \ at EOL */
};
void c_colorize_line(unsigned int *buf, int len,
int *colorize_state_ptr, __unused__ int state_only)
{
int c, state, style, style1, type_decl, klen, delim;
unsigned int *p, *p_start, *p_end, *p1, *p2;
char kbuf[32];
state = *colorize_state_ptr;
p = buf;
p_start = p;
p_end = p + len;
type_decl = 0;
if (p >= p_end)
goto the_end;
c = 0; /* turn off stupid egcs-2.91.66 warning */
style = 0;
if (state) {
/* if already in a state, go directly in the code parsing it */
if (state & C_PREPROCESS)
style = QE_STYLE_PREPROCESS;
if (state & C_COMMENT)
goto parse_comment;
if (state & C_COMMENT1)
goto parse_comment1;
if (state & C_STRING)
goto parse_string;
if (state & C_STRING_Q)
goto parse_string_q;
}
while (p < p_end) {
p_start = p;
c = *p++;
switch (c) {
case '/':
if (*p == '*') {
/* normal comment */
p++;
parse_comment:
state |= C_COMMENT;
while (p < p_end) {
if (p[0] == '*' && p[1] == '/') {
p += 2;
state &= ~C_COMMENT;
break;
} else {
p++;
}
}
set_color(p_start, p, QE_STYLE_COMMENT);
continue;
} else
if (*p == '/') {
/* line comment */
parse_comment1:
state |= C_COMMENT1;
p = p_end;
set_color(p_start, p, QE_STYLE_COMMENT);
goto the_end;
}
break;
case '#': /* preprocessor */
state = C_PREPROCESS;
style = QE_STYLE_PREPROCESS;
break;
case 'L': /* wide character and string literals */
if (*p == '\'') {
p++;
goto parse_string_q;
}
if (*p == '\"') {
p++;
goto parse_string;
}
goto normal;
case '\'': /* character constant */
parse_string_q:
state |= C_STRING_Q;
style1 = QE_STYLE_STRING_Q;
delim = '\'';
goto string;
case '\"': /* string literal */
parse_string:
state |= C_STRING;
style1 = QE_STYLE_STRING;
delim = '\"';
string:
while (p < p_end) {
if (*p == '\\') {
p++;
if (p >= p_end)
break;
p++;
} else
if ((int)*p == delim) {
p++;
state &= ~(C_STRING | C_STRING_Q);
break;
} else {
p++;
}
}
if (state & C_PREPROCESS)
style1 = QE_STYLE_PREPROCESS;
set_color(p_start, p, style1);
continue;
case '=':
/* exit type declaration */
/* does not handle this: int i = 1, j = 2; */
type_decl = 0;
break;
case '<': /* JavaScript extension */
if (*p == '!' && p[1] == '-' && p[2] == '-')
goto parse_comment1;
break;
default:
normal:
if (state & C_PREPROCESS)
break;
if (qe_isdigit(c)) {
while (qe_isalnum(*p) || *p == '.') {
p++;
}
set_color(p_start, p, QE_STYLE_NUMBER);
continue;
}
if (qe_isalpha_(c)) {
/* XXX: should support :: and $ */
klen = 0;
p--;
do {
if (klen < countof(kbuf) - 1)
kbuf[klen++] = c;
p++;
c = *p;
} while (qe_isalnum_(c));
kbuf[klen] = '\0';
if (strfind(c_mode_keywords, kbuf)) {
set_color(p_start, p, QE_STYLE_KEYWORD);
continue;
}
p1 = p;
while (qe_isblank(*p1))
p1++;
p2 = p1;
while (*p2 == '*' || qe_isblank(*p2))
p2++;
if (strfind(c_mode_types, kbuf)
|| (klen > 2 && kbuf[klen - 2] == '_' && kbuf[klen - 1] == 't')) {
/* c type */
/* if not cast, assume type declaration */
if (*p2 != ')') {
type_decl = 1;
}
set_color(p_start, p, QE_STYLE_TYPE);
continue;
}
if (*p == '(') {
/* function call */
/* XXX: different styles for call and definition */
set_color(p_start, p, QE_STYLE_FUNCTION);
continue;
}
/* assume typedef if starting at first column */
if (p_start == buf)
type_decl = 1;
if (type_decl) {
if (p_start == buf) {
/* assume type if first column */
set_color(p_start, p, QE_STYLE_TYPE);
} else {
set_color(p_start, p, QE_STYLE_VARIABLE);
}
}
continue;
}
break;
}
set_color1(p_start, style);
}
the_end:
/* strip state if not overflowing from a comment */
if (!(state & C_COMMENT) && p > buf && ((p[-1] & CHAR_MASK) != '\\'))
state &= ~(C_COMMENT1 | C_PREPROCESS);
*colorize_state_ptr = state;
}
#define MAX_BUF_SIZE 512
#define MAX_STACK_SIZE 64
/* gives the position of the first non white space character in
buf. TABs are counted correctly */
static int find_indent1(EditState *s, unsigned int *buf)
{
unsigned int *p;
int pos, c;
p = buf;
pos = 0;
for (;;) {
c = *p++ & CHAR_MASK;
if (c == '\t')
pos += s->b->tab_width - (pos % s->b->tab_width);
else if (c == ' ')
pos++;
else
break;
}
return pos;
}
static int find_pos(EditState *s, unsigned int *buf, int size)
{
int pos, c, i;
pos = 0;
for (i = 0; i < size; i++) {
c = buf[i] & CHAR_MASK;
if (c == '\t')
pos += s->b->tab_width - (pos % s->b->tab_width);
else
pos++;
}
return pos;
}
enum {
INDENT_NORM,
INDENT_FIND_EQ,
};
/* Check if indentation is already what it should be */
static int check_indent(EditState *s, int offset, int i, int *offset_ptr)
{
int offset1;
/* check tabs */
if (s->indent_tabs_mode) {
while (i >= s->b->tab_width) {
if (eb_nextc(s->b, offset, &offset) != '\t')
return 0;
i -= s->b->tab_width;
}
}
/* check needed spaces */
while (i > 0) {
if (eb_nextc(s->b, offset, &offset) != ' ')
return 0;
i--;
}
if (!qe_isblank(eb_nextc(s->b, offset, &offset1))) {
*offset_ptr = offset;
}
return 1;
}
static void remove_indent(EditState *s, int offset)
{
int offset1 = offset, offset2;
/* suppress leading spaces */
for (;;) {
int c = eb_nextc(s->b, offset1, &offset2);
if (c != ' ' && c != '\t')
break;
offset1 = offset2;
}
if (offset1 > offset)
eb_delete_range(s->b, offset, offset1);
}
/* Insert n spaces at beginning of line at <offset>.
* Stop new offset after indentation to <*offset_ptr>.
* Tabs are inserted if s->indent_tabs_mode is true. */
static void insert_indent(EditState *s, int offset, int i, int *offset_ptr)
{
/* insert tabs */
if (s->indent_tabs_mode) {
while (i >= s->b->tab_width) {
offset += eb_insert_uchar(s->b, offset, '\t');
i -= s->b->tab_width;
}
}
/* insert needed spaces */
while (i > 0) {
char buf[64];
int size = i;
if (size > ssizeof(buf))
size = ssizeof(buf);
memset(buf, ' ', size);
offset += eb_insert_utf8_buf(s->b, offset, buf, size);
i -= size;
}
*offset_ptr = offset;
}
/* indent a line of C code starting at <offset>,
* store indentation in <*offset_ptr>
*/
static void c_indent_line(EditState *s, int offset0)
{
int offset, offset1, offsetl, c, pos, line_num, col_num;
int i, eoi_found, len, pos1, lpos, style, line_num1, state;
unsigned int buf[MAX_BUF_SIZE], *p;
unsigned char stack[MAX_STACK_SIZE];
char buf1[64], *q;
int stack_ptr;
/* find start of line */
eb_get_pos(s->b, &line_num, &col_num, offset0);
line_num1 = line_num;
offset = eb_goto_bol(s->b, offset0);
/* now find previous lines and compute indent */
pos = 0;
lpos = -1; /* position of the last instruction start */
offsetl = offset;
eoi_found = 0;
stack_ptr = 0;
state = INDENT_NORM;
for (;;) {
if (offsetl == 0)
break;
line_num--;
offsetl = eb_prev_line(s->b, offsetl);
offset1 = offsetl;
len = s->get_colorized_line(s, buf, countof(buf), &offset1, line_num);
/* store indent position */
pos1 = find_indent1(s, buf);
p = buf + len;
while (p > buf) {
p--;
c = *p;
/* skip strings or comments */
style = c >> STYLE_SHIFT;
if (style == QE_STYLE_COMMENT ||
style == QE_STYLE_STRING ||
style == QE_STYLE_PREPROCESS)
continue;
c = c & CHAR_MASK;
if (state == INDENT_FIND_EQ) {
/* special case to search '=' or ; before { to know if
we are in data definition */
if (c == '=') {
/* data definition case */
pos = lpos;
goto end_parse;
} else if (c == ';') {
/* normal instruction case */
goto check_instr;
}
} else {
switch (c) {
case '}':
if (stack_ptr >= MAX_STACK_SIZE)
return;
stack[stack_ptr++] = c;
goto check_instr;
case '{':
if (stack_ptr == 0) {
if (lpos == -1) {
pos = pos1 + s->indent_size;
eoi_found = 1;
goto end_parse;
} else {
state = INDENT_FIND_EQ;
}
} else {
if (stack[--stack_ptr] != '}') {
/* XXX: syntax check ? */
goto check_instr;
}
goto check_instr;
}
break;
case ')':
case ']':
if (stack_ptr >= MAX_STACK_SIZE)
return;
stack[stack_ptr++] = c;
break;
case '(':
case '[':
if (stack_ptr == 0) {
pos = find_pos(s, buf, p - buf) + 1;
goto end_parse;
} else {
if (stack[--stack_ptr] != (c == '(' ? ')' : ']')) {
/* XXX: syntax check ? */
}
}
break;
case ' ':
case '\t':
case '\n':
break;
case ';':
/* level test needed for 'for(;;)' */
if (stack_ptr == 0) {
/* ; { or } are found before an instruction */
check_instr:
if (lpos >= 0) {
/* start of instruction already found */
pos = lpos;
if (!eoi_found)
pos += s->indent_size;
goto end_parse;
}
eoi_found = 1;
}
break;
case ':':
/* a label line is ignored */
/* XXX: incorrect */
goto prev_line;
default:
if (stack_ptr == 0) {
if ((c >> STYLE_SHIFT) == QE_STYLE_KEYWORD) {
unsigned int *p1, *p2;
/* special case for if/for/while */
p1 = p;
while (p > buf &&
(p[-1] >> STYLE_SHIFT) == QE_STYLE_KEYWORD)
p--;
p2 = p;
q = buf1;
while (q < buf1 + countof(buf1) - 1 && p2 <= p1) {
*q++ = *p2++ & CHAR_MASK;
}
*q = '\0';
if (!eoi_found && strfind("if|for|while", buf1)) {
pos = pos1 + s->indent_size;
goto end_parse;
}
}
lpos = pos1;
}
break;
}
}
}
prev_line: ;
}
end_parse:
/* compute special cases which depend on the chars on the current line */
offset1 = offset;
len = s->get_colorized_line(s, buf, countof(buf), &offset1, line_num1);
if (stack_ptr == 0) {
if (!pos && lpos >= 0) {
/* start of instruction already found */
pos = lpos;
if (!eoi_found)
pos += s->indent_size;
}
}
for (i = 0; i < len; i++) {
c = buf[i];
style = c >> STYLE_SHIFT;
/* if preprocess, no indent */
if (style == QE_STYLE_PREPROCESS) {
pos = 0;
break;
}
/* NOTE: strings & comments are correctly ignored there */
if (c == '}' || c == ':') {
pos -= s->indent_size;
if (pos < 0)
pos = 0;
break;
}
if (c == '{' && pos == s->indent_size && !eoi_found) {
pos = 0;
break;
}
}
/* the number of needed spaces is in 'pos' */
/* if on a blank line, reset indent to 0 unless point is on it */
if (eb_is_blank_line(s->b, offset, &offset1)
&& (s->offset < offset || s->offset >= offset1)) {
pos = 0;
}
/* Do not modify buffer if indentation in correct */
if (!check_indent(s, offset, pos, &offset1)) {
/* simple approach to normalization of indentation */
remove_indent(s, offset);
insert_indent(s, offset, pos, &offset1);
}
if (s->offset >= offset && s->offset < offset1) {
/* move to the indentation if point was in indent space */
s->offset = offset1;
}
}
static void do_c_indent(EditState *s)
{
c_indent_line(s, s->offset);
}
static void do_c_indent_region(EditState *s)
{
int col_num, line1, line2;
/* deactivate region hilite */
s->region_style = 0;
/* Swap point and mark so mark <= point */
if (s->offset < s->b->mark) {
int tmp = s->b->mark;
s->b->mark = s->offset;
s->offset = tmp;
}
/* We do it with lines to avoid offset variations during indenting */
eb_get_pos(s->b, &line1, &col_num, s->b->mark);
eb_get_pos(s->b, &line2, &col_num, s->offset);
if (col_num == 0)
line2--;
/* Iterate over all lines inside block */
for (; line1 <= line2; line1++) {
c_indent_line(s, eb_goto_pos(s->b, line1, 0));
}
}
static void do_c_electric(EditState *s, int key)
{
int offset = s->offset;
do_char(s, key, 1);
c_indent_line(s, offset);
}
static void do_c_return(EditState *s)
{
int offset = s->offset;
do_return(s, 1);
/* reindent line to remove indent on blank line */
c_indent_line(s, offset);
}
/* forward / backward preprocessor */
static void do_c_forward_preprocessor(EditState *s, int dir)
{
}
static int c_mode_probe(ModeDef *mode, ModeProbeData *p)
{
/* trust the file extension */
if (match_extension(p->filename, mode->extensions))
return 80;
/* weaker match on C comment start */
if (p->buf[0] == '/' && p->buf[1] == '*')
return 60;
/* even weaker match on C++ comment start */
if (p->buf[0] == '/' && p->buf[1] == '/')
return 50;
if (p->buf[0] == '#') {
/* same for file starting with '#include' */
if (strstart(cs8(p->buf), "#include", NULL))
return 50;
/* same for file starting with '#pragma' as in #pragma once */
if (strstart(cs8(p->buf), "#pragma", NULL))
return 50;
}
return 0;
}
/* C mode specific commands */
static CmdDef c_commands[] = {
CMD2( KEY_CTRL('i'), KEY_NONE,
"c-indent-command", do_c_indent, ES, "*")
CMD2( KEY_META(KEY_CTRL('\\')), KEY_NONE,
"c-indent-region", do_c_indent_region, ES, "*")
/* should map to KEY_META + KEY_CTRL_LEFT ? */
CMD3( KEY_META('['), KEY_NONE,
"c-backward-preprocessor", do_c_forward_preprocessor, ESi, -1, "*v")
CMD3( KEY_META(']'), KEY_NONE,
"c-forward-preprocessor", do_c_forward_preprocessor, ESi, 1, "*v")
CMD2( '{', '}',
"c-electric-key", do_c_electric, ESi, "*ki")
CMD2( KEY_RET, KEY_NONE,
"c-newline", do_c_return, ES, "*v")
CMD_DEF_END,
};
static ModeDef c_mode;
static int c_init(void)
{
const char *p;
/* C mode is almost like the text mode, so we copy and patch it */
memcpy(&c_mode, &text_mode, sizeof(ModeDef));
c_mode.name = "C";
c_mode.extensions = c_mode_extensions;
c_mode.mode_probe = c_mode_probe;
c_mode.colorize_func = c_colorize_line;
qe_register_mode(&c_mode);
qe_register_cmd_table(c_commands, &c_mode);
for (p = ";:#&|"; *p; p++) {
qe_register_binding(*p, "c-electric-key", &c_mode);
}
return 0;
}
qe_module_init(c_init);