- fix bogus test for . and .. in find_file_next() - use strequal() instead of !strcmp() - add some emacs-lisp defined symbols - fixed awk mode for `#` comments
1244 lines
34 KiB
C
1244 lines
34 KiB
C
/*
|
|
* QEmacs, tiny but powerful multimode editor
|
|
*
|
|
* Copyright (c) 2000-2002 Fabrice Bellard.
|
|
* Copyright (c) 2000-2020 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"
|
|
#include "variables.h"
|
|
|
|
/* qscript: config file parsing */
|
|
|
|
/* CG: error messages should go to the *error* buffer.
|
|
* displayed as a popup upon start.
|
|
*/
|
|
|
|
typedef struct QEValue {
|
|
int type; // value type
|
|
int len; // string length
|
|
union {
|
|
long long value; // number value
|
|
char *str; // string value
|
|
} u;
|
|
} QEValue;
|
|
|
|
typedef struct QEmacsDataSource {
|
|
EditState *s;
|
|
char *allocated_buf;
|
|
const char *buf;
|
|
const char *p;
|
|
const char *start_p;
|
|
const char *filename; // source filename
|
|
int line_num; // source line number
|
|
int tok; // token type
|
|
int len; // length of TOK_STRING and TOK_ID string
|
|
QEValue *sp_max;
|
|
char str[256]; // token source (XXX: should use source code)
|
|
QEValue stack[16];
|
|
} QEmacsDataSource;
|
|
|
|
enum {
|
|
TOK_EOF = -1, TOK_ERR = -2, TOK_VOID = 0, TOK_ALLOC = 1,
|
|
TOK_NUMBER = 128, TOK_STRING, TOK_CHAR, TOK_ID, TOK_IF, TOK_ELSE,
|
|
TOK_MUL_EQ, TOK_DIV_EQ, TOK_MOD_EQ, TOK_ADD_EQ, TOK_SUB_EQ, TOK_SHL_EQ, TOK_SHR_EQ,
|
|
TOK_AND_EQ, TOK_XOR_EQ, TOK_OR_EQ,
|
|
TOK_EQ, TOK_NE, TOK_SHL, TOK_SHR, TOK_LE, TOK_GE, TOK_INC, TOK_DEC, TOK_LOR, TOK_LAND,
|
|
};
|
|
|
|
enum {
|
|
PREC_SUFFIX = 14, PREC_COMMA = 1, PREC_ASSIGN = 2, PREC_QUESTION = 3,
|
|
};
|
|
|
|
static const char ops2[] = "*= /= %= += -= <<= >>= &= ^= |= == != << >> <= >= ++ -- || && ";
|
|
static const char ops1[] = "=<>?:|^&+-*/%,;.!~()[]{}";
|
|
|
|
static const char prec[] = {
|
|
'(', '[', '.', TOK_INC, TOK_DEC, 14,
|
|
'*', '/', '%', 13,
|
|
'+', '-', 12,
|
|
TOK_SHL, TOK_SHR, 11,
|
|
'<', '>', TOK_LE, TOK_GE, 10,
|
|
TOK_EQ, TOK_NE, 9,
|
|
'&', 8,
|
|
'^', 7,
|
|
'|', 6,
|
|
TOK_LAND, 5,
|
|
TOK_LOR, 4,
|
|
'?', ':', 3,
|
|
TOK_MUL_EQ, TOK_DIV_EQ, TOK_MOD_EQ, TOK_ADD_EQ, TOK_SUB_EQ, TOK_SHL_EQ, TOK_SHR_EQ,
|
|
TOK_AND_EQ, TOK_OR_EQ, TOK_XOR_EQ, '=', 2,
|
|
',', 1,
|
|
')', ']', '{', '}', ';', '!', '~', 0,
|
|
};
|
|
|
|
static inline void qe_cfg_set_void(QEValue *sp) {
|
|
if (sp->type & TOK_ALLOC)
|
|
qe_free(&sp->u.str);
|
|
sp->type = TOK_VOID;
|
|
}
|
|
|
|
static inline void qe_cfg_set_num(QEValue *sp, long long value) {
|
|
if (sp->type & TOK_ALLOC)
|
|
qe_free(&sp->u.str);
|
|
sp->u.value = value;
|
|
sp->type = TOK_NUMBER;
|
|
}
|
|
|
|
static inline void qe_cfg_set_char(QEValue *sp, int c) {
|
|
if (sp->type & TOK_ALLOC)
|
|
qe_free(&sp->u.str);
|
|
sp->u.value = c;
|
|
sp->type = TOK_CHAR;
|
|
}
|
|
|
|
static inline void qe_cfg_set_str(QEValue *sp, const char *str, int len) {
|
|
if (sp->type & TOK_ALLOC)
|
|
qe_free(&sp->u.str);
|
|
sp->u.str = qe_malloc_array(char, len + 1);
|
|
memcpy(sp->u.str, str, len);
|
|
sp->u.str[len] = '\0';
|
|
sp->len = len;
|
|
sp->type = TOK_STRING;
|
|
}
|
|
|
|
static inline void qe_cfg_set_pstr(QEValue *sp, char *str, int len) {
|
|
if (sp->type & TOK_ALLOC)
|
|
qe_free(&sp->u.str);
|
|
sp->u.str = str;
|
|
sp->len = len;
|
|
sp->type = TOK_STRING;
|
|
}
|
|
|
|
static void qe_cfg_init(QEmacsDataSource *ds) {
|
|
memset(ds, 0, sizeof(*ds));
|
|
ds->sp_max = ds->stack;
|
|
}
|
|
|
|
static void qe_cfg_release(QEmacsDataSource *ds) {
|
|
QEValue *sp;
|
|
for (sp = ds->stack; sp < ds->sp_max; sp++)
|
|
qe_cfg_set_void(sp);
|
|
}
|
|
|
|
static int qe_cfg_get_prec(int tok) {
|
|
char *p = strchr(prec, tok);
|
|
if (p) {
|
|
while (*++p >= ' ')
|
|
continue;
|
|
return *p;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_parse_string(EditState *s, const char **pp, int delim,
|
|
char *dest, int size, int *plen)
|
|
{
|
|
char cbuf[8];
|
|
const char *p = *pp;
|
|
int res = 0;
|
|
int pos = 0;
|
|
int end = size - 1;
|
|
int i, len;
|
|
|
|
for (;;) {
|
|
int c = *p;
|
|
if (c == '\n' || c == '\0') {
|
|
put_status(s, "unterminated string");
|
|
res = -1;
|
|
break;
|
|
}
|
|
p++;
|
|
if (c == delim)
|
|
break;
|
|
if (c == '\\') {
|
|
int maxc = -1;
|
|
c = *p++;
|
|
switch (c) {
|
|
case 'a': c = '\a'; break;
|
|
case 'b': c = '\b'; break;
|
|
case 'e': c = '\e'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
case 'v': c = '\v'; break;
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
c -= '0';
|
|
if (qe_isoctdigit(*p)) {
|
|
c = (c << 3) | (*p++ - '0');
|
|
if (c < 040 && qe_isoctdigit(*p)) {
|
|
c = (c << 3) | (*p++ - '0');
|
|
}
|
|
}
|
|
break;
|
|
case 'U':
|
|
maxc += 4;
|
|
case 'u':
|
|
maxc += 5;
|
|
case 'x':
|
|
for (c = 0; qe_isxdigit(*p) && maxc-- != 0; p++) {
|
|
c = (c << 4) | qe_digit_value(*p);
|
|
}
|
|
len = utf8_encode(cbuf, c);
|
|
for (i = 0; i < len && pos < end; i++)
|
|
dest[pos++] = cbuf[i];
|
|
continue;
|
|
}
|
|
}
|
|
/* XXX: silently truncate overlong string constants */
|
|
if (pos < end)
|
|
dest[pos++] = c;
|
|
}
|
|
if (pos <= end)
|
|
dest[pos] = '\0';
|
|
*pp = p;
|
|
*plen = pos;
|
|
return res;
|
|
}
|
|
|
|
static int qe_cfg_next_token(QEmacsDataSource *ds)
|
|
{
|
|
for (;;) {
|
|
int tok, len;
|
|
char c;
|
|
const char *op;
|
|
|
|
ds->start_p = ds->p;
|
|
c = *ds->p;
|
|
if (c == '\0')
|
|
return ds->tok = TOK_EOF;
|
|
ds->p++;
|
|
if (c == '\n') {
|
|
ds->s->qe_state->ec.lineno = ++ds->line_num;
|
|
continue;
|
|
}
|
|
if (qe_isspace(c))
|
|
continue;
|
|
if (c == '/') {
|
|
if (*ds->p == '/') { /* line comment */
|
|
while ((c = *ds->p) != '\0' && c != '\n') {
|
|
ds->p++;
|
|
}
|
|
continue;
|
|
}
|
|
if (*ds->p == '*') { /* multiline comment */
|
|
ds->p++;
|
|
while ((c = *ds->p) != '\0') {
|
|
if (c == '*' && ds->p[1] == '/') {
|
|
ds->p += 2;
|
|
break;
|
|
}
|
|
if (c == '\n')
|
|
ds->s->qe_state->ec.lineno = ++ds->line_num;
|
|
ds->p++;
|
|
}
|
|
continue;
|
|
}
|
|
return ds->tok = '/';
|
|
}
|
|
if (qe_isalpha_(c)) {
|
|
len = 0;
|
|
ds->str[len++] = c;
|
|
while (qe_isalnum_(c = *ds->p) || (c == '-' && qe_isalpha(ds->p[1]))) {
|
|
if (c == '_')
|
|
c = '-';
|
|
if (len < (int)sizeof(ds->str) - 1)
|
|
ds->str[len++] = c;
|
|
ds->p++;
|
|
}
|
|
ds->str[len] = '\0';
|
|
ds->len = len;
|
|
if (strequal(ds->str, "if"))
|
|
return ds->tok = TOK_IF;
|
|
if (strequal(ds->str, "else"))
|
|
return ds->tok = TOK_ELSE;
|
|
return ds->tok = TOK_ID;
|
|
}
|
|
if (qe_isdigit(c)) {
|
|
strtoll(ds->start_p, (char **)&ds->p, 0);
|
|
if (qe_isalnum_(*ds->p)) {
|
|
put_status(ds->s, "invalid number");
|
|
return ds->tok = TOK_ERR;
|
|
}
|
|
return ds->tok = TOK_NUMBER;
|
|
}
|
|
if (c == '\'' || c == '\"') {
|
|
if (qe_cfg_parse_string(ds->s, &ds->p, c, ds->str, sizeof(ds->str), &ds->len))
|
|
return ds->tok = TOK_ERR;
|
|
if (c == '\'') {
|
|
return ds->tok = TOK_CHAR;
|
|
}
|
|
return ds->tok = TOK_STRING;
|
|
}
|
|
for (tok = TOK_MUL_EQ, op = ops2; *op; tok++) {
|
|
if (*op++ == c) {
|
|
const char *p1 = ds->p;
|
|
for (;;) {
|
|
if (*op == ' ') {
|
|
ds->p = p1;
|
|
return ds->tok = tok;
|
|
}
|
|
if (*p1++ != *op++)
|
|
break;
|
|
}
|
|
}
|
|
while (*op++ != ' ')
|
|
continue;
|
|
}
|
|
if (strchr(ops1, c)) {
|
|
return ds->tok = c;
|
|
}
|
|
put_status(ds->s, "unsupported operator: %c", c);
|
|
return ds->tok = TOK_ERR;
|
|
}
|
|
}
|
|
|
|
static int expect_token(QEmacsDataSource *ds, int tok)
|
|
{
|
|
if (ds->tok == tok) {
|
|
qe_cfg_next_token(ds);
|
|
return 1;
|
|
} else {
|
|
/* XXX: pretty print token name */
|
|
put_status(ds->s, "'%c' expected", tok);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int qe_cfg_getvalue(QEmacsDataSource *ds, QEValue *sp) {
|
|
#ifndef CONFIG_TINY
|
|
if (sp->type == TOK_ID) {
|
|
char buf[256];
|
|
int num;
|
|
switch (qe_get_variable(ds->s, ds->str, buf, sizeof(buf), &num, 0)) {
|
|
case VAR_CHARS:
|
|
case VAR_STRING:
|
|
qe_cfg_set_str(sp, buf, strlen(buf));
|
|
break;
|
|
case VAR_NUMBER:
|
|
qe_cfg_set_num(sp, num);
|
|
break;
|
|
default:
|
|
case VAR_UNKNOWN:
|
|
put_status(ds->s, "no variable %s", ds->str);
|
|
qe_cfg_set_void(sp);
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_tonum(QEmacsDataSource *ds, QEValue *sp) {
|
|
if (qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
switch (sp->type) {
|
|
case TOK_NUMBER:
|
|
return 0;
|
|
case TOK_STRING:
|
|
qe_cfg_set_num(sp, strtoll(sp->u.str, NULL, 0));
|
|
break;
|
|
case TOK_CHAR:
|
|
sp->type = TOK_NUMBER;
|
|
break;
|
|
default:
|
|
sp->u.value = 0;
|
|
sp->type = TOK_NUMBER;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_tostr(QEmacsDataSource *ds, QEValue *sp) {
|
|
char buf[64];
|
|
int len;
|
|
|
|
if (qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
switch (sp->type) {
|
|
case TOK_STRING:
|
|
return 0;
|
|
case TOK_NUMBER:
|
|
len = snprintf(buf, sizeof buf, "%lld", sp->u.value);
|
|
qe_cfg_set_str(sp, buf, len);
|
|
break;
|
|
case TOK_CHAR:
|
|
len = utf8_encode(buf, (int)sp->u.value);
|
|
qe_cfg_set_str(sp, buf, len);
|
|
break;
|
|
default:
|
|
qe_cfg_set_str(sp, "", 0);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_tochar(QEmacsDataSource *ds, QEValue *sp) {
|
|
const char *p;
|
|
|
|
if (qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
switch (sp->type) {
|
|
case TOK_STRING:
|
|
p = sp->u.str;
|
|
qe_cfg_set_num(sp, utf8_decode(&p));
|
|
break;
|
|
case TOK_NUMBER:
|
|
case TOK_CHAR:
|
|
sp->type = TOK_CHAR;
|
|
break;
|
|
default:
|
|
qe_cfg_set_num(sp, 0);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_append(QEmacsDataSource *ds, QEValue *sp, const char *p, size_t len) {
|
|
char *new_p;
|
|
int new_len;
|
|
|
|
if (qe_cfg_tostr(ds, sp))
|
|
return 1;
|
|
/* XXX: should cap length and check for malloc failure */
|
|
new_len = sp->len + len;
|
|
new_p = qe_malloc_array(char, new_len + 1);
|
|
memcpy(new_p, sp->u.str, sp->len);
|
|
memcpy(new_p + sp->len, p, len);
|
|
new_p[new_len] = '\0';
|
|
qe_cfg_set_pstr(sp, new_p, new_len);
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_format(QEmacsDataSource *ds, QEValue *sp) {
|
|
char buf[256];
|
|
char fmt[16];
|
|
/* prevent warning on variable format */
|
|
int (*fun)(char *buf, size_t size, const char *fmt, ...) = snprintf;
|
|
char *start, *p;
|
|
int c, len;
|
|
|
|
if (qe_cfg_tostr(ds, sp))
|
|
return 1;
|
|
len = 0;
|
|
|
|
/* XXX: should use buf_xxx */
|
|
for (start = p = sp->u.str;;) {
|
|
p += strcspn(p, "%");
|
|
len += strlen(pstrncpy(buf + len, sizeof(buf) - len, start, p - start));
|
|
if (*p == '\0')
|
|
break;
|
|
start = p++;
|
|
if (*p == '%') {
|
|
start++;
|
|
p++;
|
|
} else {
|
|
p += strspn(p, "0123456789+- #.");
|
|
c = *p++;
|
|
if (strchr("diouxX", c)) {
|
|
if (qe_cfg_tonum(ds, sp + 1))
|
|
return 1;
|
|
snprintf(fmt, sizeof fmt, "%.*sll%c", (int)(p - 1 - start), start, c);
|
|
(*fun)(buf + len, sizeof(buf) - len, fmt, sp[1].u.value);
|
|
len += strlen(buf + len);
|
|
start = p;
|
|
} else
|
|
if (c == 'c') {
|
|
if (qe_cfg_tochar(ds, sp + 1))
|
|
return 1;
|
|
goto hasstr;
|
|
} else
|
|
if (c == 's') {
|
|
hasstr:
|
|
if (qe_cfg_tostr(ds, sp + 1))
|
|
return 1;
|
|
snprintf(fmt, sizeof fmt, "%.*ss", (int)(p - start), start);
|
|
(*fun)(buf + len, sizeof(buf) - len, sp[1].u.str);
|
|
len += strlen(buf + len);
|
|
start = p;
|
|
}
|
|
}
|
|
}
|
|
qe_cfg_set_str(sp, buf, len);
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_call(QEmacsDataSource *ds, QEValue *sp, CmdDef *d);
|
|
static int qe_cfg_assign(QEmacsDataSource *ds, QEValue *sp, int op);
|
|
static int qe_cfg_op(QEmacsDataSource *ds, QEValue *sp, int op);
|
|
|
|
static int qe_cfg_expr(QEmacsDataSource *ds, QEValue *sp, int prec0) {
|
|
int tok = ds->tok;
|
|
|
|
if (sp >= ds->sp_max) {
|
|
if (sp >= ds->stack + countof(ds->stack)) {
|
|
put_status(ds->s, "stack overflow");
|
|
return 1;
|
|
}
|
|
ds->sp_max = sp + 1;
|
|
}
|
|
/* handle prefix operators */
|
|
switch (tok) {
|
|
case '(':
|
|
qe_cfg_next_token(ds);
|
|
if (qe_cfg_expr(ds, sp, PREC_COMMA) || !expect_token(ds, ')'))
|
|
return 1;
|
|
break;
|
|
case '+':
|
|
qe_cfg_next_token(ds);
|
|
if (qe_cfg_expr(ds, sp, PREC_SUFFIX) || qe_cfg_tonum(ds, sp))
|
|
return 1;
|
|
break;
|
|
case '-':
|
|
qe_cfg_next_token(ds);
|
|
if (qe_cfg_expr(ds, sp, PREC_SUFFIX) || qe_cfg_tonum(ds, sp))
|
|
return 1;
|
|
sp->u.value = -sp->u.value;
|
|
break;
|
|
case '~':
|
|
qe_cfg_next_token(ds);
|
|
if (qe_cfg_expr(ds, sp, PREC_SUFFIX) || qe_cfg_tonum(ds, sp))
|
|
return 1;
|
|
sp->u.value = ~sp->u.value;
|
|
break;
|
|
case '!':
|
|
qe_cfg_next_token(ds);
|
|
if (qe_cfg_expr(ds, sp, PREC_SUFFIX) || qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
qe_cfg_set_num(sp, (sp->type == TOK_STRING) ? 0 : !sp->u.value);
|
|
break;
|
|
case TOK_INC:
|
|
case TOK_DEC:
|
|
qe_cfg_next_token(ds);
|
|
if (qe_cfg_expr(ds, sp, PREC_SUFFIX))
|
|
return 1;
|
|
if (sp->type != TOK_ID) {
|
|
put_status(ds->s, "invalid increment");
|
|
return 1;
|
|
}
|
|
qe_cfg_set_num(sp + 1, 1);
|
|
if (qe_cfg_assign(ds, sp, tok))
|
|
return 1;
|
|
break;
|
|
// case TOK_SIZEOF:
|
|
case TOK_NUMBER:
|
|
qe_cfg_set_num(sp, strtoll(ds->start_p, NULL, 0));
|
|
qe_cfg_next_token(ds);
|
|
break;
|
|
case TOK_STRING:
|
|
#ifndef CONFIG_TINY
|
|
case TOK_ID:
|
|
#endif
|
|
qe_cfg_set_str(sp, ds->str, ds->len); /* XXX: should parse string */
|
|
sp->type = tok;
|
|
qe_cfg_next_token(ds);
|
|
break;
|
|
case TOK_CHAR: {
|
|
const char *p = ds->str;
|
|
int c = utf8_decode(&p); // XXX: should check for extra characters
|
|
qe_cfg_set_char(sp, c);
|
|
qe_cfg_next_token(ds);
|
|
break;
|
|
}
|
|
default:
|
|
qe_cfg_set_void(sp);
|
|
put_status(ds->s, "invalid expression");
|
|
return 1;
|
|
}
|
|
|
|
for (;;) {
|
|
int op = ds->tok;
|
|
int prec = qe_cfg_get_prec(op);
|
|
|
|
if (prec < prec0)
|
|
return 0;
|
|
qe_cfg_next_token(ds);
|
|
if (prec == PREC_SUFFIX) {
|
|
switch (op) {
|
|
case '(': /* function call */
|
|
if (sp->type == TOK_ID) {
|
|
CmdDef *d = qe_find_cmd(sp->u.str);
|
|
if (!d) {
|
|
put_status(ds->s, "unknown command '%s'", sp->u.str);
|
|
return 1;
|
|
}
|
|
if (qe_cfg_call(ds, sp, d))
|
|
return 1;
|
|
continue;
|
|
}
|
|
put_status(ds->s, "invalid function call");
|
|
return 1;
|
|
case TOK_INC: /* post increment */
|
|
case TOK_DEC: /* post decrement */
|
|
qe_cfg_set_num(sp + 1, 1);
|
|
if (qe_cfg_assign(ds, sp, op))
|
|
return 1;
|
|
sp->u.value -= (op == TOK_INC) ? 1 : -1;
|
|
continue;
|
|
case '[': /* subscripting */
|
|
if (qe_cfg_expr(ds, sp + 1, PREC_COMMA) || !expect_token(ds, ']'))
|
|
return 1;
|
|
if (qe_cfg_op(ds, sp, op))
|
|
return 1;
|
|
continue;
|
|
case '.': /* property / method accessor */
|
|
if (ds->tok != TOK_ID) {
|
|
put_status(ds->s, "expected property name");
|
|
return 1;
|
|
}
|
|
if (qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
if (sp->type == TOK_STRING && strequal(ds->str, "length")) {
|
|
qe_cfg_set_num(sp, strlen(sp->u.str)); // utf8?
|
|
qe_cfg_next_token(ds);
|
|
continue;
|
|
}
|
|
put_status(ds->s, "no such property '%s'", ds->str);
|
|
return 1;
|
|
default:
|
|
put_status(ds->s, "unsupported operator '%c'", op);
|
|
return 1;
|
|
}
|
|
}
|
|
if (prec == PREC_ASSIGN) {
|
|
if (qe_cfg_expr(ds, sp + 1, prec))
|
|
return 1;
|
|
return qe_cfg_assign(ds, sp, op);
|
|
}
|
|
if (qe_cfg_expr(ds, sp + 1, prec + 1))
|
|
return 1;
|
|
if (qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
if (qe_cfg_op(ds, sp, op))
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int qe_cfg_op(QEmacsDataSource *ds, QEValue *sp, int op) {
|
|
if (sp->type == TOK_STRING) {
|
|
switch (op) {
|
|
case '<':
|
|
case '>':
|
|
case TOK_LE:
|
|
case TOK_GE:
|
|
case TOK_EQ:
|
|
case TOK_NE:
|
|
if (qe_cfg_tostr(ds, sp + 1))
|
|
return 1;
|
|
qe_cfg_set_num(sp, strcmp(sp->u.str, sp[1].u.str));
|
|
qe_cfg_set_num(sp + 1, 0);
|
|
goto num;
|
|
case '+':
|
|
case TOK_ADD_EQ:
|
|
if (qe_cfg_tostr(ds, sp + 1))
|
|
return 1;
|
|
if (qe_cfg_append(ds, sp, sp[1].u.str, sp[1].len))
|
|
return 1;
|
|
break;
|
|
case '[':
|
|
if (qe_cfg_tonum(ds, sp + 1))
|
|
return 1;
|
|
if (sp[1].u.value >= 0 && sp[1].u.value < sp->len) {
|
|
qe_cfg_set_char(sp, sp->u.str[sp[1].u.value]); // XXX: utf-8 ?
|
|
} else {
|
|
qe_cfg_set_void(sp);
|
|
}
|
|
break;
|
|
case '%':
|
|
if (qe_cfg_format(ds, sp))
|
|
return 1;
|
|
break;
|
|
default:
|
|
put_status(ds->s, "invalid string operator '%c'", op);
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (qe_cfg_tonum(ds, sp) || qe_cfg_tonum(ds, sp + 1))
|
|
return 1;
|
|
num:
|
|
switch (op) {
|
|
case '*':
|
|
case TOK_MUL_EQ:
|
|
sp->u.value *= sp[1].u.value;
|
|
break;
|
|
case '/':
|
|
case '%':
|
|
case TOK_DIV_EQ:
|
|
case TOK_MOD_EQ:
|
|
if (sp[1].u.value == 0 || (sp->u.value == LLONG_MIN && sp[1].u.value == -1)) {
|
|
put_status(ds->s, "'%c': division overflow", op);
|
|
return 1;
|
|
}
|
|
if (op == '/' || op == TOK_DIV_EQ)
|
|
sp->u.value /= sp[1].u.value;
|
|
else
|
|
sp->u.value %= sp[1].u.value;
|
|
break;
|
|
case '+':
|
|
case TOK_ADD_EQ:
|
|
case TOK_INC:
|
|
sp->u.value += sp[1].u.value;
|
|
break;
|
|
case '-':
|
|
case TOK_SUB_EQ:
|
|
case TOK_DEC:
|
|
sp->u.value -= sp[1].u.value;
|
|
break;
|
|
case TOK_SHL:
|
|
case TOK_SHL_EQ:
|
|
sp->u.value <<= sp[1].u.value;
|
|
break;
|
|
case TOK_SHR:
|
|
case TOK_SHR_EQ:
|
|
sp->u.value >>= sp[1].u.value;
|
|
break;
|
|
case '<':
|
|
sp->u.value = sp->u.value < sp[1].u.value;
|
|
break;
|
|
case '>':
|
|
sp->u.value = sp->u.value > sp[1].u.value;
|
|
break;
|
|
case TOK_LE:
|
|
sp->u.value = sp->u.value <= sp[1].u.value;
|
|
break;
|
|
case TOK_GE:
|
|
sp->u.value = sp->u.value >= sp[1].u.value;
|
|
break;
|
|
case TOK_EQ:
|
|
sp->u.value = sp->u.value == sp[1].u.value;
|
|
break;
|
|
case TOK_NE:
|
|
sp->u.value = sp->u.value != sp[1].u.value;
|
|
break;
|
|
case '&':
|
|
case TOK_AND_EQ:
|
|
sp->u.value &= sp[1].u.value;
|
|
break;
|
|
case '^':
|
|
case TOK_XOR_EQ:
|
|
sp->u.value ^= sp[1].u.value;
|
|
break;
|
|
case '|':
|
|
case TOK_OR_EQ:
|
|
sp->u.value |= sp[1].u.value;
|
|
break;
|
|
case TOK_LAND:
|
|
sp->u.value = sp->u.value && sp[1].u.value;
|
|
break;
|
|
case TOK_LOR:
|
|
sp->u.value = sp->u.value || sp[1].u.value;
|
|
break;
|
|
//case '?':
|
|
case ',':
|
|
sp->u.value = sp[1].u.value;
|
|
break;
|
|
default:
|
|
put_status(ds->s, "invalid numeric operator '%c'", op);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_assign(QEmacsDataSource *ds, QEValue *sp, int op) {
|
|
if (sp->type != TOK_ID) {
|
|
put_status(ds->s, "invalid assignment");
|
|
return 1;
|
|
}
|
|
#ifndef CONFIG_TINY
|
|
{
|
|
char cmd[128];
|
|
pstrcpy(cmd, sizeof cmd, sp->u.str);
|
|
if (qe_cfg_getvalue(ds, sp + 1))
|
|
return 1;
|
|
if (op == '=') {
|
|
qe_cfg_set_void(sp);
|
|
sp[0] = sp[1];
|
|
qe_cfg_set_void(sp + 1);
|
|
} else {
|
|
if (qe_cfg_getvalue(ds, sp))
|
|
return 1;
|
|
if (qe_cfg_op(ds, sp, op))
|
|
return 1;
|
|
}
|
|
if (sp->type == TOK_STRING) {
|
|
qe_set_variable(ds->s, cmd, sp->u.str, 0);
|
|
} else {
|
|
qe_set_variable(ds->s, cmd, NULL, sp->u.value);
|
|
}
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_skip(QEmacsDataSource *ds) {
|
|
int level = 0;
|
|
|
|
for (;;) {
|
|
switch (ds->tok) {
|
|
case TOK_EOF:
|
|
return ds->tok;
|
|
case ';':
|
|
if (!level)
|
|
goto done;
|
|
break;
|
|
case '{':
|
|
case '[':
|
|
case '(':
|
|
level++;
|
|
break;
|
|
case '}':
|
|
case ']':
|
|
case ')':
|
|
if (!--level)
|
|
goto done;
|
|
break;
|
|
}
|
|
qe_cfg_next_token(ds);
|
|
}
|
|
done:
|
|
return qe_cfg_next_token(ds);
|
|
}
|
|
|
|
static int qe_cfg_call(QEmacsDataSource *ds, QEValue *sp, CmdDef *d) {
|
|
char str[1024];
|
|
char *strp;
|
|
const char *r;
|
|
int nb_args, sep, i, ret;
|
|
CmdArg args[MAX_CMD_ARGS];
|
|
unsigned char args_type[MAX_CMD_ARGS];
|
|
EditState *s = ds->s;
|
|
QEmacsState *qs = s->qe_state;
|
|
CmdArgSpec cas;
|
|
|
|
nb_args = 0;
|
|
|
|
/* construct argument type list */
|
|
r = d->spec;
|
|
if (*r == '*') {
|
|
r++;
|
|
if (s->b->flags & BF_READONLY) {
|
|
put_status(s, "Buffer is read only");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* first argument is always the window */
|
|
args_type[nb_args++] = CMD_ARG_WINDOW;
|
|
|
|
while ((ret = parse_arg(&r, &cas)) != 0) {
|
|
if (ret < 0 || nb_args >= MAX_CMD_ARGS) {
|
|
put_status(s, "Badly defined command '%s'", d->name);
|
|
return -1;
|
|
}
|
|
args[nb_args].p = NULL;
|
|
args_type[nb_args++] = cas.arg_type;
|
|
}
|
|
|
|
sep = '\0';
|
|
strp = str;
|
|
|
|
for (i = 0; i < nb_args; i++) {
|
|
/* pseudo arguments: skip them */
|
|
switch (args_type[i]) {
|
|
case CMD_ARG_WINDOW:
|
|
args[i].s = s;
|
|
continue;
|
|
case CMD_ARG_INTVAL:
|
|
args[i].n = (int)(intptr_t)d->val;
|
|
continue;
|
|
case CMD_ARG_STRINGVAL:
|
|
/* CG: kludge for xxx-mode functions and named kbd macros,
|
|
must be last argument */
|
|
args[i].p = cas.prompt;
|
|
continue;
|
|
case CMD_ARG_INT | CMD_ARG_USE_ARGVAL:
|
|
if (ds->tok == ')') {
|
|
args[i].n = NO_ARG;
|
|
continue;
|
|
}
|
|
break;
|
|
case CMD_ARG_INT | CMD_ARG_MUL_ARGVAL:
|
|
if (ds->tok == ')') {
|
|
args[i].n = d->val;
|
|
continue;
|
|
}
|
|
break;
|
|
case CMD_ARG_INT | CMD_ARG_USE_MARK:
|
|
if (ds->tok == ')') {
|
|
args[i].n = s->b->mark;
|
|
continue;
|
|
}
|
|
break;
|
|
case CMD_ARG_INT | CMD_ARG_USE_POINT:
|
|
if (ds->tok == ')') {
|
|
args[i].n = s->offset;
|
|
continue;
|
|
}
|
|
break;
|
|
case CMD_ARG_INT | CMD_ARG_USE_ZERO:
|
|
if (ds->tok == ')') {
|
|
args[i].n = 0;
|
|
continue;
|
|
}
|
|
break;
|
|
case CMD_ARG_INT | CMD_ARG_USE_BSIZE:
|
|
if (ds->tok == ')') {
|
|
args[i].n = s->b->total_size;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (sep) {
|
|
/* CG: Should test for arg list too short. */
|
|
/* CG: Could supply default arguments. */
|
|
if (ds->tok != sep) {
|
|
put_status(s, "missing arguments for %s", d->name);
|
|
return -1;
|
|
}
|
|
qe_cfg_next_token(ds);
|
|
}
|
|
sep = ',';
|
|
|
|
if (qe_cfg_expr(ds, sp, PREC_ASSIGN)) {
|
|
put_status(s, "missing arguments for %s", d->name);
|
|
return -1;
|
|
}
|
|
|
|
switch (args_type[i] & CMD_ARG_TYPE_MASK) {
|
|
case CMD_ARG_INT:
|
|
qe_cfg_tonum(ds, sp); // XXX: should complain about type mismatch?
|
|
args[i].n = sp->u.value;
|
|
if (args_type[i] == (CMD_ARG_INT | CMD_ARG_MUL_ARGVAL))
|
|
args[i].n *= d->val;
|
|
break;
|
|
case CMD_ARG_STRING:
|
|
qe_cfg_tostr(ds, sp); // XXX: should complain about type mismatch?
|
|
pstrcpy(strp, str + countof(str) - strp, sp->u.str);
|
|
args[i].p = strp;
|
|
if (strp < str + countof(str) - 1)
|
|
strp += strlen(strp) + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (ds->tok != ')') {
|
|
put_status(s, "too many arguments for %s", d->name);
|
|
return -1;
|
|
}
|
|
qe_cfg_next_token(ds);
|
|
|
|
qs->this_cmd_func = d->action.func;
|
|
qs->ec.function = d->name;
|
|
call_func(d->sig, d->action, nb_args, args, args_type);
|
|
qs->last_cmd_func = qs->this_cmd_func;
|
|
if (qs->active_window)
|
|
s = qs->active_window;
|
|
check_window(&s);
|
|
ds->s = s;
|
|
sp->type = TOK_VOID;
|
|
return 0;
|
|
}
|
|
|
|
static int qe_cfg_stmt(QEmacsDataSource *ds, QEValue *sp) {
|
|
const char *start = ds->p;
|
|
int res = 0;
|
|
|
|
if (ds->tok == '{') {
|
|
qe_cfg_next_token(ds);
|
|
while (ds->tok != '}') {
|
|
if (ds->tok == TOK_EOF) {
|
|
put_status(ds->s, "missing '}'");
|
|
return 1;
|
|
}
|
|
res |= qe_cfg_stmt(ds, sp);
|
|
}
|
|
qe_cfg_next_token(ds);
|
|
return res;
|
|
}
|
|
|
|
if (ds->tok == TOK_IF) {
|
|
int skip;
|
|
QEValue *sp = &ds->stack[0];
|
|
|
|
qe_cfg_next_token(ds);
|
|
sp->type = TOK_VOID;
|
|
if (qe_cfg_expr(ds, sp, PREC_COMMA) || qe_cfg_getvalue(ds, sp))
|
|
goto fail;
|
|
skip = (sp->type == TOK_STRING) ? 0 : sp->u.value == 0;
|
|
if (skip) {
|
|
qe_cfg_skip(ds);
|
|
} else {
|
|
qe_cfg_stmt(ds, sp);
|
|
}
|
|
if (ds->tok == TOK_ELSE) {
|
|
if (!skip) {
|
|
qe_cfg_skip(ds);
|
|
} else {
|
|
qe_cfg_stmt(ds, sp);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
if (qe_cfg_expr(ds, sp, PREC_COMMA) || qe_cfg_getvalue(ds, sp))
|
|
goto fail;
|
|
if (ds->tok == ';') {
|
|
qe_cfg_next_token(ds);
|
|
}
|
|
if (ds->tok != '}' && ds->tok != TOK_EOF) {
|
|
// XXX: should check for implicit semicolon at end of line
|
|
}
|
|
return 0;
|
|
|
|
fail:
|
|
ds->p = start;
|
|
qe_cfg_skip(ds);
|
|
return 1;
|
|
}
|
|
|
|
static int qe_parse_script(EditState *s, QEmacsDataSource *ds)
|
|
{
|
|
QEmacsState *qs = s->qe_state;
|
|
QErrorContext ec = qs->ec;
|
|
QEValue *sp = &ds->stack[0];
|
|
|
|
ds->s = s;
|
|
ds->p = ds->buf;
|
|
sp->type = TOK_VOID;
|
|
|
|
qs->ec.filename = ds->filename;
|
|
qs->ec.function = NULL;
|
|
qs->ec.lineno = ds->line_num = 1;
|
|
|
|
qe_cfg_next_token(ds);
|
|
while (ds->tok != TOK_EOF && ds->tok != TOK_ERR) {
|
|
if (qe_cfg_stmt(ds, sp))
|
|
sp->type = TOK_VOID;
|
|
}
|
|
qe_free(&ds->allocated_buf);
|
|
qs->ec = ec;
|
|
return sp->type;
|
|
}
|
|
|
|
void do_eval_expression(EditState *s, const char *expression, int argval)
|
|
{
|
|
QEmacsDataSource ds;
|
|
QEValue *sp = &ds.stack[0];
|
|
char buf[64];
|
|
int len;
|
|
|
|
if (argval != NO_ARG && (s->b->flags & BF_READONLY))
|
|
return;
|
|
|
|
qe_cfg_init(&ds);
|
|
ds.buf = expression;
|
|
ds.filename = "<string>";
|
|
switch (qe_parse_script(s, &ds)) {
|
|
case TOK_VOID:
|
|
break;
|
|
case TOK_NUMBER:
|
|
len = snprintf(buf, sizeof buf, "%lld", sp->u.value);
|
|
if (argval == NO_ARG) {
|
|
put_status(s, "-> %s", buf);
|
|
} else {
|
|
s->offset += eb_insert_utf8_buf(s->b, s->offset, buf, len);
|
|
}
|
|
break;
|
|
case TOK_STRING:
|
|
if (argval == NO_ARG) {
|
|
/* XXX: should unparse string */
|
|
put_status(s, "-> \"%s\"", sp->u.str);
|
|
} else {
|
|
s->offset += eb_insert_utf8_buf(s->b, s->offset, sp->u.str, sp->len);
|
|
}
|
|
break;
|
|
case TOK_CHAR:
|
|
len = utf8_encode(buf, (int)sp->u.value);
|
|
if (argval == NO_ARG) {
|
|
/* XXX: should unparse character */
|
|
put_status(s, "-> '%.*s'", len, buf);
|
|
} else {
|
|
s->offset += eb_insert_utf8_buf(s->b, s->offset, buf, len);
|
|
}
|
|
break;
|
|
default:
|
|
put_status(s, "unexpected value type: %d", sp->type);
|
|
break;
|
|
}
|
|
qe_cfg_release(&ds);
|
|
do_refresh(s);
|
|
}
|
|
|
|
#define MAX_SCRIPT_LENGTH (128 * 1024 - 1)
|
|
|
|
static int do_eval_buffer_region(EditState *s, int start, int stop)
|
|
{
|
|
QEmacsDataSource ds;
|
|
char *buf;
|
|
int length, res;
|
|
|
|
qe_cfg_init(&ds);
|
|
|
|
if (stop < start) {
|
|
int tmp = start;
|
|
start = stop;
|
|
stop = tmp;
|
|
}
|
|
if (start < 0)
|
|
start = 0;
|
|
if (stop < start)
|
|
stop = start;
|
|
if (stop > s->b->total_size)
|
|
stop = s->b->total_size;
|
|
length = stop - start;
|
|
if (length > MAX_SCRIPT_LENGTH || !(buf = qe_malloc_array(char, length + 1))) {
|
|
put_status(s, "Buffer too large");
|
|
return -1;
|
|
}
|
|
length = eb_read(s->b, start, buf, length);
|
|
buf[length] = '\0';
|
|
ds.buf = ds.allocated_buf = buf;
|
|
ds.filename = s->b->name;
|
|
res = qe_parse_script(s, &ds);
|
|
do_refresh(s);
|
|
qe_cfg_release(&ds);
|
|
return res;
|
|
}
|
|
|
|
void do_eval_region(EditState *s)
|
|
{
|
|
s->region_style = 0; /* deactivate region hilite */
|
|
|
|
do_eval_buffer_region(s, s->b->mark, s->offset);
|
|
}
|
|
|
|
void do_eval_buffer(EditState *s)
|
|
{
|
|
do_eval_buffer_region(s, 0, s->b->total_size);
|
|
}
|
|
|
|
int parse_config_file(EditState *s, const char *filename)
|
|
{
|
|
QEmacsDataSource ds;
|
|
FILE *fp;
|
|
char *buf;
|
|
long length;
|
|
int res;
|
|
|
|
qe_cfg_init(&ds);
|
|
|
|
fp = fopen(filename, "r");
|
|
if (!fp)
|
|
return -1;
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
length = ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
if (length > MAX_SCRIPT_LENGTH || !(buf = qe_malloc_array(char, length + 1))) {
|
|
fclose(fp);
|
|
put_status(s, "File too large");
|
|
return -1;
|
|
}
|
|
|
|
length = fread(buf, 1, length, fp);
|
|
fclose(fp);
|
|
buf[length] = '\0';
|
|
ds.buf = ds.allocated_buf = buf;
|
|
ds.filename = filename;
|
|
res = qe_parse_script(s, &ds);
|
|
qe_cfg_release(&ds);
|
|
return res;
|
|
}
|
|
|
|
#ifndef CONFIG_TINY
|
|
int qe_load_session(EditState *s)
|
|
{
|
|
return parse_config_file(s, ".qesession");
|
|
}
|
|
|
|
void do_save_session(EditState *s, int popup)
|
|
{
|
|
EditBuffer *b = eb_scratch("*session*", BF_UTF8);
|
|
time_t now;
|
|
|
|
eb_printf(b, "// qemacs version: %s\n", QE_VERSION);
|
|
now = time(NULL);
|
|
eb_printf(b, "// session saved: %s\n", ctime(&now));
|
|
|
|
qe_save_variables(s, b);
|
|
qe_save_macros(s, b);
|
|
qe_save_open_files(s, b);
|
|
qe_save_window_layout(s, b);
|
|
|
|
if (popup) {
|
|
b->offset = 0;
|
|
b->flags |= BF_READONLY;
|
|
show_popup(s, b, "QEmacs session");
|
|
} else {
|
|
eb_write_buffer(b, 0, b->total_size, ".qesession");
|
|
eb_free(&b);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void symbol_complete(CompleteState *cp) {
|
|
command_complete(cp);
|
|
variable_complete(cp);
|
|
}
|
|
|
|
static int symbol_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
|
CmdDef *d = qe_find_cmd(name);
|
|
if (d) {
|
|
return command_print_entry(cp, s, name);
|
|
} else {
|
|
return variable_print_entry(cp, s, name);
|
|
}
|
|
}
|
|
|
|
static CompletionDef symbol_completion = {
|
|
"symbol", symbol_complete, symbol_print_entry, command_get_entry,
|
|
CF_SPACE_OK | CF_NO_AUTO_SUBMIT
|
|
};
|
|
|
|
static CmdDef parser_commands[] = {
|
|
|
|
CMD2( KEY_META(':'), KEY_NONE,
|
|
"eval-expression", do_eval_expression, ESsi,
|
|
"s{Eval: }[.symbol]|expression|"
|
|
"p", "")
|
|
CMD0( KEY_NONE, KEY_NONE,
|
|
"eval-region", do_eval_region, "")
|
|
CMD0( KEY_NONE, KEY_NONE,
|
|
"eval-buffer", do_eval_buffer, "")
|
|
#ifndef CONFIG_TINY
|
|
CMD1( KEY_NONE, KEY_NONE,
|
|
"save-session", do_save_session, 1, "")
|
|
#endif
|
|
CMD_DEF_END,
|
|
};
|
|
|
|
static int parser_init(void)
|
|
{
|
|
qe_register_cmd_table(parser_commands, NULL);
|
|
qe_register_completion(&symbol_completion);
|
|
return 0;
|
|
}
|
|
|
|
qe_module_init(parser_init);
|