evil-qemacs/clang.c
2005-05-05 14:07:50 +00:00

563 lines
16 KiB
C

/*
* C mode for QEmacs.
* Copyright (c) 2001, 2002 Fabrice Bellard.
*
* 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"
static const char c_keywords[] =
"|auto|break|case|const|continue|do|else|enum|extern|for|goto|"
"if|register|return|static|struct|switch|typedef|union|volatile|while|";
/* NOTE: 'var' is added for javascript */
static const char c_types[] =
"|char|double|float|int|long|unsigned|short|signed|void|var|";
static int get_c_keyword(char *buf, int buf_size, unsigned int **pp)
{
unsigned int *p, c;
char *q;
p = *pp;
c = *p;
q = buf;
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c == '_')) {
do {
if ((q - buf) < buf_size - 1)
*q++ = c;
p++;
c = *p;
} while ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c == '_') ||
(c >= '0' && c <= '9'));
}
*q = '\0';
*pp = p;
return q - buf;
}
/* colorization states */
enum {
C_COMMENT = 1,
C_STRING,
C_STRING_Q,
C_PREPROCESS,
};
void c_colorize_line(unsigned int *buf, int len,
int *colorize_state_ptr, int state_only)
{
int c, state, l, type_decl;
unsigned int *p, *p_start, *p1;
char kbuf[32];
state = *colorize_state_ptr;
p = buf;
p_start = p;
type_decl = 0;
c = 0; /* turn off stupid egcs-2.91.66 warning */
/* if already in a state, go directly in the code parsing it */
switch(state) {
case C_COMMENT:
goto parse_comment;
case C_STRING:
case C_STRING_Q:
goto parse_string;
case C_PREPROCESS:
goto parse_preprocessor;
default:
break;
}
for(;;) {
p_start = p;
c = *p;
switch(c) {
case '\n':
goto the_end;
case '/':
p++;
if (*p == '*') {
/* normal comment */
p++;
state = C_COMMENT;
parse_comment:
while (*p != '\n') {
if (p[0] == '*' && p[1] == '/') {
p += 2;
state = 0;
break;
} else {
p++;
}
}
set_color(p_start, p - p_start, QE_STYLE_COMMENT);
} else if (*p == '/') {
/* line comment */
while (*p != '\n')
p++;
set_color(p_start, p - p_start, QE_STYLE_COMMENT);
}
break;
case '#':
/* preprocessor */
parse_preprocessor:
p = buf + len;
set_color(p_start, p - p_start, QE_STYLE_PREPROCESS);
if (p > buf && (p[-1] & CHAR_MASK) == '\\')
state = C_PREPROCESS;
else
state = 0;
goto the_end;
case '\'':
state = C_STRING_Q;
goto string;
case '\"':
/* strings/chars */
state = C_STRING;
string:
p++;
parse_string:
while (*p != '\n') {
if (*p == '\\') {
p++;
if (*p == '\n')
break;
p++;
} else if ((*p == '\'' && state == C_STRING_Q) ||
(*p == '\"' && state == C_STRING)) {
p++;
state = 0;
break;
} else {
p++;
}
}
set_color(p_start, p - p_start, QE_STYLE_STRING);
break;
case '=':
p++;
/* exit type declaration */
type_decl = 0;
break;
default:
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c == '_')) {
l = get_c_keyword(kbuf, sizeof(kbuf), &p);
p1 = p;
if (strfind(c_keywords, kbuf, 0)) {
set_color(p_start, p1 - p_start, QE_STYLE_KEYWORD);
} else
if (strfind(c_types, kbuf, 0)) {
/* c type */
while (*p == ' ' || *p == '\t')
p++;
/* if not cast, assume type declaration */
if (*p != ')') {
type_decl = 1;
}
set_color(p_start, p1 - p_start, QE_STYLE_TYPE);
} else {
/* assume typedef if starting at first column */
if (p_start == buf)
type_decl = 1;
if (type_decl) {
while (*p == ' ' || *p == '\t')
p++;
if (*p == '(') {
/* function definition case */
set_color(p_start, p1 - p_start, QE_STYLE_FUNCTION);
type_decl = 1;
} else if (p_start == buf) {
/* assume type if first column */
set_color(p_start, p1 - p_start, QE_STYLE_TYPE);
} else {
set_color(p_start, p1 - p_start, QE_STYLE_VARIABLE);
}
}
}
} else {
p++;
}
break;
}
}
the_end:
*colorize_state_ptr = state;
}
#define MAX_BUF_SIZE 512
#define MAX_STACK_SIZE 64
/* gives the position of the first non while 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->tab_size - (pos % s->tab_size);
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->tab_size - (pos % s->tab_size);
else
pos++;
}
return pos;
}
enum {
INDENT_NORM,
INDENT_FIND_EQ,
};
/* insert n spaces at *offset_ptr. Update offset_ptr to point just
after. Tabs are inserted if s->indent_tabs_mode is true. */
static void insert_spaces(EditState *s, int *offset_ptr, int i)
{
int offset, size;
char buf1[64];
offset = *offset_ptr;
/* insert tabs */
if (s->indent_tabs_mode) {
while (i >= s->tab_size) {
buf1[0] = '\t';
eb_insert(s->b, offset, buf1, 1);
offset++;
i -= s->tab_size;
}
}
/* insert needed spaces */
while (i > 0) {
size = i;
if (size > (int)sizeof(buf1))
size = (int)sizeof(buf1);
memset(buf1, ' ', size);
eb_insert(s->b, offset, buf1, size);
i -= size;
offset += size;
}
*offset_ptr = offset;
}
static void do_c_indent(EditState *s)
{
int offset, offset1, offset2, offsetl, c, pos, size, 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, s->offset);
line_num1 = line_num;
offset = s->offset;
offset = eb_goto_bol(s->b, offset);
/* 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--;
eb_prevc(s->b, offsetl, &offsetl);
offsetl = eb_goto_bol(s->b, offsetl);
len = get_colorized_line(s, buf, MAX_BUF_SIZE - 1, offsetl, 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 {
/* XXX: syntax check ? */
stack_ptr--;
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 {
/* XXX: syntax check ? */
stack_ptr--;
}
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 + sizeof(buf1) - 1 && p2 <= p1) {
*q++ = *p2++ & CHAR_MASK;
}
*q = '\0';
if (!eoi_found &&
(!strcmp(buf1, "if") ||
!strcmp(buf1, "for") ||
!strcmp(buf1, "while"))) {
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 */
len = get_colorized_line(s, buf, MAX_BUF_SIZE - 1, offset, 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' */
/* suppress leading spaces */
offset1 = offset;
for(;;) {
c = eb_nextc(s->b, offset1, &offset2);
if (c != ' ' && c != '\t')
break;
offset1 = offset2;
}
size = offset1 - offset;
if (size > 0) {
eb_delete(s->b, offset, size);
s->offset -= size;
if (s->offset < offset)
s->offset = offset;
}
/* insert needed spaces */
insert_spaces(s, &offset, pos);
s->offset = offset;
}
void do_c_indent_region(EditState *s)
{
int col_num, p1, p2, tmp;
/* we do it with lines to avoid offset variations during indenting */
eb_get_pos(s->b, &p1, &col_num, s->offset);
eb_get_pos(s->b, &p2, &col_num, s->b->mark);
if (p1 > p2) {
tmp = p1;
p1 = p2;
p2 = tmp;
}
for(;p1<=p2;p1++) {
s->offset = eb_goto_pos(s->b, p1, 0);
do_c_indent(s);
}
}
void do_c_electric(EditState *s, int key)
{
do_char(s, key);
do_c_indent(s);
}
static int c_mode_probe(ModeProbeData *p)
{
const char *r;
/* currently, only use the file extension */
r = extension(p->filename);
if (*r) {
if (strfind("|c|e|h|js|cs|jav|java|cxx|cpp|", r + 1, 1))
return 100;
}
return 0;
}
int c_mode_init(EditState *s, ModeSavedData *saved_data)
{
int ret;
ret = text_mode_init(s, saved_data);
if (ret)
return ret;
set_colorize_func(s, c_colorize_line);
return ret;
}
/* specific C commands */
static CmdDef c_commands[] = {
CMD0( KEY_CTRL('i'), KEY_NONE, "c-indent-command", do_c_indent)
CMD0( KEY_NONE, KEY_NONE, "c-indent-region", do_c_indent_region)
CMD1( ';', KEY_NONE, "c-electric-semi&comma", do_c_electric, ';')
CMD1( ':', KEY_NONE, "c-electric-colon", do_c_electric, ':')
CMD1( '{', KEY_NONE, "c-electric-obrace", do_c_electric, '{')
CMD1( '}', KEY_NONE, "c-electric-cbrace", do_c_electric, '}')
CMD_DEF_END,
};
static ModeDef c_mode;
int c_init(void)
{
/* 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.mode_probe = c_mode_probe;
c_mode.mode_init = c_mode_init;
qe_register_mode(&c_mode);
qe_register_cmd_table(c_commands, "C");
return 0;
}
qe_module_init(c_init);