evil-qemacs/qescript.c
Charlie Gordon c5e4d9d3dc Improve file completion, sundries
- 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
2020-11-14 09:57:48 +01:00

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);