/* * QEmacs, tiny but powerful multimode editor * * Copyright (c) 2000-2002 Fabrice Bellard. * Copyright (c) 2000-2026 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #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 QEmacsDataSource { EditState *s; const char *filename; // source filename char *allocated_buf; const char *buf; // start of source block const char *p; // pointer past current token const char *start_p; // start of token in source block int line_num; // source line number at ds->p int start_line; // the source line number of ds->start_p int newline_seen; // current token is first on line int tok; // token type int prec; // operator precedence int len; // length of TOK_STRING and TOK_ID string int str_size; char *str; QEValue *sp_max; QEValue stack[16]; char str_buf[256]; // token string (XXX: should use local buffer?) } QEmacsDataSource; enum { TOK_EOF = -1, TOK_ERR = -2, TOK_VOID__ = 0, TOK_NUMBER__ = 128, TOK_STRING__, TOK_CHAR__, TOK_ID__, TOK_IF, TOK_ELSE, #ifndef CONFIG_TINY TOK_FOR, TOK_WHILE, TOK_BREAK, TOK_CONTINUE, TOK_SWITCH, TOK_CASE, TOK_DEFAULT, TOK_VAR, TOK_FUNCTION, TOK_RETURN, 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, #endif }; enum { PREC_NONE, PREC_EXPRESSION, PREC_ASSIGNMENT, PREC_CONDITIONAL, PREC_LOGICAL_OR, PREC_LOGICAL_AND, PREC_BITOR, PREC_BITXOR, PREC_BITAND, PREC_EQUALITY, PREC_RELATIONAL, PREC_SHIFT, PREC_ADDITIVE, PREC_MULTIPLICATIVE, PREC_POSTFIX, }; struct opdef { char str[4]; u8 op, prec; }; #define op(str, op, prec) { str, op, prec }, #ifdef CONFIG_TINY #define OP(str, op, prec) #else #define OP(str, op, prec) { str, op, prec }, #endif /* This table must be sorted in lexicographical order */ static const struct opdef ops[] = { OP( "!", '!', PREC_NONE ) OP( "!=", TOK_NE, PREC_EQUALITY ) OP( "%", '%', PREC_MULTIPLICATIVE ) OP( "%=", TOK_MOD_EQ, PREC_ASSIGNMENT ) OP( "&", '&', PREC_BITAND ) OP( "&&", TOK_LAND, PREC_LOGICAL_AND ) OP( "&=", TOK_AND_EQ, PREC_ASSIGNMENT ) op( "(", '(', PREC_POSTFIX ) op( ")", ')', PREC_NONE ) OP( "*", '*', PREC_MULTIPLICATIVE ) OP( "*=", TOK_MUL_EQ, PREC_ASSIGNMENT ) OP( "+", '+', PREC_ADDITIVE ) OP( "++", TOK_INC, PREC_POSTFIX ) OP( "+=", TOK_ADD_EQ, PREC_ASSIGNMENT ) op( ",", ',', PREC_EXPRESSION ) OP( "-", '-', PREC_ADDITIVE ) OP( "--", TOK_DEC, PREC_POSTFIX ) OP( "-=", TOK_SUB_EQ, PREC_ASSIGNMENT ) OP( ".", '.', PREC_POSTFIX ) OP( "/", '/', PREC_MULTIPLICATIVE ) OP( "/=", TOK_DIV_EQ, PREC_ASSIGNMENT ) OP( ":", ':', PREC_NONE ) op( ";", ';', PREC_NONE ) OP( "<", '<', PREC_SHIFT ) OP( "<<", TOK_SHL, PREC_SHIFT ) OP( "<<=", TOK_SHL_EQ, PREC_ASSIGNMENT ) OP( "<=", TOK_LE, PREC_RELATIONAL ) op( "=", '=', PREC_ASSIGNMENT ) OP( "==", TOK_EQ, PREC_EQUALITY ) OP( ">", '>', PREC_SHIFT ) OP( ">=", TOK_GE, PREC_RELATIONAL ) OP( ">>", TOK_SHR, PREC_SHIFT ) OP( ">>=", TOK_SHR_EQ, PREC_ASSIGNMENT ) OP( "?", '?', PREC_CONDITIONAL ) OP( "[", '[', PREC_POSTFIX ) OP( "]", ']', PREC_NONE ) OP( "^", '^', PREC_BITXOR ) OP( "^=", TOK_XOR_EQ, PREC_ASSIGNMENT ) op( "{", '{', PREC_NONE ) OP( "|", '|', PREC_BITOR ) OP( "|=", TOK_OR_EQ, PREC_ASSIGNMENT ) OP( "||", TOK_LOR, PREC_LOGICAL_OR ) op( "}", '}', PREC_NONE ) OP( "~", '~', PREC_NONE ) }; #undef OP #undef op static void qe_cfg_init(QEmacsDataSource *ds) { memset(ds, 0, sizeof(*ds)); ds->sp_max = ds->stack; ds->str = ds->str_buf; ds->str_size = sizeof(ds->str_buf); } static void qe_cfg_release(QEmacsDataSource *ds) { // XXX: should free ds->allocated_buf ? QEValue *sp; for (sp = ds->stack; sp < ds->sp_max; sp++) qe_cfg_set_void(sp); } #ifndef CONFIG_TINY static int qe_cfg_append_str_char32(QEmacsDataSource *ds, char32_t ch) { if (ds->len + MAX_CHAR_BYTES + 1 > ds->str_size) { int size = ds->str_size + (ds->str_size >> 1); char *str = qe_malloc_array(char, size); if (str == NULL) return 0; memcpy(str, ds->str, ds->len); if (ds->str != ds->str_buf) qe_free(&ds->str); ds->str = str; ds->str_size = size; } return ds->len += utf8_encode(ds->str + ds->len, ch); } #endif static int qe_cfg_append_str_byte(QEmacsDataSource *ds, unsigned char c) { if (ds->len + 1 + 1 > ds->str_size) { int size = ds->str_size + (ds->str_size >> 1); char *str = qe_malloc_array(char, size); if (str == NULL) return 0; memcpy(str, ds->str, ds->len); if (ds->str != ds->str_buf) qe_free(&ds->str); ds->str = str; ds->str_size = size; } ds->str[ds->len++] = c; return ds->len; } static void qe_cfg_error(QEmacsDataSource *ds, const char *fmt, ...) qe__attr_printf(2,3); static void qe_cfg_error(QEmacsDataSource *ds, const char *fmt, ...) { char buf[MAX_SCREEN_WIDTH]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); put_status(ds->s, "!\007\006script error: %s", buf); } // XXX: Should use strunquote parser from util.c // XXX: Should remove string length limitation using allocation static int qe_cfg_parse_string(QEmacsDataSource *ds, const char **pp, int delim) { const char *p = *pp; int triple = 0; int res = 0; #ifndef CONFIG_TINY char32_t ch; #endif /* check for triple delimiter */ if (p[0] == delim && p[1] == delim) { triple = 1; p += 2; if (*p == '\n') { /* ignore newline after """ */ // XXX: should ignore leading white space? p++; } } ds->len = 0; for (;;) { /* encoding issues deliberately ignored */ unsigned char c = *p; if (c == '\0') { qe_cfg_error(ds, "unterminated string"); res = -1; break; } if (c == '\n') { if (!triple) { qe_cfg_error(ds, "newline in simple string"); res = -1; break; } ds->s->qs->ec.lineno = ++ds->line_num; } p++; if (c == delim) { if (!triple) break; if (p[0] == delim && p[1] == delim) { p += 2; break; } } if (c == '\\') { #ifndef CONFIG_TINY int maxc = -1; #endif if (*p == '\0') { qe_cfg_error(ds, "unterminated string"); res = -1; break; } c = *p++; switch (c) { case '\n': // escaped newlines contribute no character to the string // XXX: should ignore leading white space? ds->s->qs->ec.lineno = ++ds->line_num; continue; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; #ifndef CONFIG_TINY /* ignore other escapes, including octal and hex */ case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'e': c = '\033'; break; case 'f': c = '\f'; 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; /* maxc will be 8 */ FALLTHROUGH; case 'u': maxc += 5; /* maxc will be 4 */ FALLTHROUGH; case 'x': for (ch = 0; qe_isxdigit(*p) && maxc-- != 0; p++) { ch = (ch << 4) | qe_digit_value(*p); } qe_cfg_append_str_char32(ds, ch); continue; #endif } } qe_cfg_append_str_byte(ds, c); } if (ds->len < ds->str_size) ds->str[ds->len] = '\0'; *pp = p; return res; } static int qe_cfg_next_token(QEmacsDataSource *ds) { const u8 *p = (const u8 *)ds->p; ds->newline_seen = 0; for (;;) { int len; u8 c; const struct opdef *op; ds->start_p = cs8(p); ds->start_line = ds->line_num; ds->prec = PREC_NONE; c = *p; if (c == '\0') { ds->p = cs8(p); return ds->tok = TOK_EOF; } p++; if (c == '\n') { /* set has_seen_newline for automatic semicolon insertion */ ds->newline_seen = 1; ds->s->qs->ec.lineno = ++ds->line_num; continue; } if (qe_isspace(c)) continue; if (c == '/') { if (*p == '/') { /* line comment */ while ((c = *p) != '\0' && c != '\n') p++; continue; } if (*p == '*') { /* multiline comment */ for (;;) { if ((c = *++p) == '\0') { // XXX: should complain about unfinished comment break; } if (c == '*' && p[1] == '/') { p += 2; break; } if (c == '\n') ds->s->qs->ec.lineno = ++ds->line_num; } continue; } } if (ds->str != ds->str_buf) { qe_free(&ds->str); ds->str = ds->str_buf; ds->str_size = sizeof(ds->str_buf); } if (qe_isalpha_(c)) { /* parse an identifier */ // XXX: should have a list of symbols with command and // variable names with transparent dash translation // and use a hashtable in qecore to register symbol // bindings commands, global variables, local variables // and qscript keywords. len = 0; ds->str[len++] = c; while (qe_isalnum_(c = *p) || (c == '-' && qe_isalpha(p[1]))) { if (c == '_') c = '-'; /* silently truncate overlong identifiers */ if (len < ssizeof(ds->str_buf) - 1) ds->str[len++] = c; p++; } ds->str[len] = '\0'; ds->len = len; ds->p = cs8(p); #ifndef CONFIG_TINY // XXX: should use hash table switch (len) { case 2: if (!memcmp(ds->start_p, "if", 2)) return ds->tok = TOK_IF; break; case 3: if (!memcmp(ds->start_p, "for", 3)) return ds->tok = TOK_FOR; if (!memcmp(ds->start_p, "fun", 3)) return ds->tok = TOK_FUNCTION; if (!memcmp(ds->start_p, "def", 3)) return ds->tok = TOK_FUNCTION; if (!memcmp(ds->start_p, "var", 3)) return ds->tok = TOK_VAR; break; case 4: if (!memcmp(ds->start_p, "else", 4)) return ds->tok = TOK_ELSE; if (!memcmp(ds->start_p, "case", 4)) return ds->tok = TOK_CASE; break; case 5: if (!memcmp(ds->start_p, "break", 5)) return ds->tok = TOK_BREAK; if (!memcmp(ds->start_p, "while", 5)) return ds->tok = TOK_WHILE; break; case 6: if (!memcmp(ds->start_p, "switch", 6)) return ds->tok = TOK_SWITCH; if (!memcmp(ds->start_p, "return", 6)) return ds->tok = TOK_RETURN; break; case 7: if (!memcmp(ds->start_p, "default", 7)) return ds->tok = TOK_DEFAULT; break; case 8: if (!memcmp(ds->start_p, "continue", 8)) return ds->tok = TOK_CONTINUE; if (!memcmp(ds->start_p, "function", 8)) return ds->tok = TOK_FUNCTION; break; } #else if (len == 2 && !memcmp(ds->start_p, "if", 2)) return ds->tok = TOK_IF; if (len == 4 && !memcmp(ds->start_p, "else", 4)) return ds->tok = TOK_ELSE; #endif return ds->tok = TOK_ID; } if (qe_isdigit(c)) { /* parse a number */ ds->p = cs8(p); // XXX: should handle floating point and _ and ' digit separators strtoll_c(ds->start_p, &ds->p, 0); if (qe_isalnum_(*ds->p)) { /* type suffixes not supported */ qe_cfg_error(ds, "invalid number"); return ds->tok = TOK_ERR; } return ds->tok = TOK_NUMBER; } if (c == '\'' || c == '\"') { ds->len = 0; ds->p = cs8(p); if (qe_cfg_parse_string(ds, &ds->p, c) < 0) { return ds->tok = TOK_ERR; } return ds->tok = (c == '\'') ? TOK_CHAR : TOK_STRING; } // XXX: use binary search? op = ops + countof(ops); while (op --> ops) { for (len = 0; p[len - 1] == op->str[len]; len++) { if (op->str[len + 1] == '\0') { ds->p = cs8(p + len); ds->prec = op->prec; return ds->tok = op->op; } } } ds->p = cs8(p); qe_cfg_error(ds, "unsupported operator: %c", c); return ds->tok = c; } } static int has_token(QEmacsDataSource *ds, int tok) { if (ds->tok == tok) { qe_cfg_next_token(ds); return 1; } else { return 0; } } static int expect_token(QEmacsDataSource *ds, int tok) { if (has_token(ds, tok)) { return 1; } else { /* tok is a single byte token, no need to pretty print */ qe_cfg_error(ds, "'%c' expected", tok); return 0; } } static int qe_cfg_getvalue(QEmacsDataSource *ds, QEValue *sp) { if (sp->type == TOK_ID) { #ifndef CONFIG_TINY char buf[2048]; int num; // XXX: qe_get_variable should populate a QEValue switch (qe_get_variable(ds->s, sp->u.str, buf, sizeof(buf), &num, 0)) { case VAR_CHARS: case VAR_STRING: qe_cfg_set_str(sp, buf, strlen(buf)); break; case VAR_NUMBER: case VAR_STYLE: qe_cfg_set_num(sp, num); break; default: case VAR_UNKNOWN: qe_cfg_error(ds, "no variable %s", sp->u.str); qe_cfg_set_void(sp); return 1; } #else qe_cfg_error(ds, "no variable %s", sp->u.str); #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, (char32_t)sp->u.value); qe_cfg_set_str(sp, buf, len); break; default: qe_cfg_set_str(sp, "", 0); break; } return 0; } #ifndef CONFIG_TINY 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? */ new_len = sp->len + len; new_p = qe_malloc_array(char, new_len + 1); if (new_p == NULL) return 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, 1); return 0; } static int qe_cfg_format(QEmacsDataSource *ds, QEValue *sp) { char buf[2048]; char fmt[16]; /* Use function pointer and cast to prevent warning on variable format */ int (*fun)(char *buf, size_t size, const char *fmt, ...) = (int (*)(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_get_args(QEmacsDataSource *ds, QEValue *sp, int n1, int n2); static int qe_cfg_op(QEmacsDataSource *ds, QEValue *sp, int op); #endif static int qe_cfg_call(QEmacsDataSource *ds, QEValue *sp, const CmdDef *d); static int qe_cfg_assign(QEmacsDataSource *ds, QEValue *sp, int op); static int qe_cfg_skip_expr(QEmacsDataSource *ds); static int qe_cfg_check_lvalue(QEmacsDataSource *ds, QEValue *sp) { if (sp->type != TOK_ID) { qe_cfg_error(ds, "not a variable"); return 1; } return 0; } static int qe_cfg_expr(QEmacsDataSource *ds, QEValue *sp, int prec0, int skip) { /* Parse and evaluate an expression upto and including operators with precedence prec0. */ /* in CONFIG_TINY, only support function calls and setting variables */ const char *start_p = ds->start_p; int start_line = ds->line_num; int tok = ds->tok; if (skip) return qe_cfg_skip_expr(ds); if (sp >= ds->sp_max) { if (sp >= ds->stack + countof(ds->stack)) { qe_cfg_error(ds, "stack overflow"); return qe_cfg_skip_expr(ds); } ds->sp_max = sp + 1; } again: /* handle prefix operators (ignoring precedence) */ switch (tok) { case '(': /* parenthesized expression, including if expression */ qe_cfg_next_token(ds); if (qe_cfg_expr(ds, sp, PREC_EXPRESSION, 0) || !expect_token(ds, ')')) goto fail; break; case '-': qe_cfg_next_token(ds); if (qe_cfg_expr(ds, sp, PREC_POSTFIX, 0) || qe_cfg_tonum(ds, sp)) goto fail; sp->u.value = -sp->u.value; break; #ifndef CONFIG_TINY case '+': qe_cfg_next_token(ds); if (qe_cfg_expr(ds, sp, PREC_POSTFIX, 0) || qe_cfg_tonum(ds, sp)) goto fail; break; case '~': qe_cfg_next_token(ds); if (qe_cfg_expr(ds, sp, PREC_POSTFIX, 0) || qe_cfg_tonum(ds, sp)) goto fail; sp->u.value = ~sp->u.value; break; case '!': qe_cfg_next_token(ds); if (qe_cfg_expr(ds, sp, PREC_POSTFIX, 0) || qe_cfg_getvalue(ds, sp)) goto fail; qe_cfg_set_num(sp, (sp->type == TOK_STRING) ? 0 : !sp->u.value); break; case TOK_INC: /* convert to x += 1 */ case TOK_DEC: /* convert to x -= 1 */ qe_cfg_next_token(ds); if (qe_cfg_expr(ds, sp, PREC_POSTFIX, 0)) goto fail; if (qe_cfg_check_lvalue(ds, sp)) goto fail; qe_cfg_set_num(sp + 1, 1); if (qe_cfg_assign(ds, sp, tok)) goto fail; if (qe_cfg_getvalue(ds, sp)) goto fail; break; // case TOK_SIZEOF: #endif case TOK_NUMBER: qe_cfg_set_num(sp, strtoll(ds->start_p, NULL, 0)); qe_cfg_next_token(ds); break; case TOK_STRING: case TOK_ID: /* XXX: could either parse here or delay parse till qe_cfg_getvalue() */ qe_cfg_set_str(sp, ds->str, ds->len); sp->type = tok; qe_cfg_next_token(ds); break; case TOK_CHAR: { const char *p = ds->str; char32_t 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); qe_cfg_error(ds, "invalid expression"); goto fail; } for (;;) { int op = ds->tok; int prec = ds->prec; if (prec < prec0) return 0; qe_cfg_next_token(ds); if (op == ',') goto again; #ifndef CONFIG_TINY if (op == '?') { int truth; if (qe_cfg_getvalue(ds, sp)) goto again; truth = (sp->type == TOK_STRING) || (sp->u.value != 0); if (qe_cfg_expr(ds, sp, PREC_EXPRESSION, !truth) != !truth) goto again; if (!has_token(ds, ':')) goto again; if (qe_cfg_expr(ds, sp, PREC_CONDITIONAL, truth) != truth) goto again; continue; } #endif if (prec == PREC_POSTFIX) { switch (op) { case '(': /* function call */ /* XXX: should move this code to qe_cfg_call() */ if (sp->type == TOK_ID) { const CmdDef *d = qe_find_cmd(ds->s->qs, sp->u.str); if (!d) { #ifndef CONFIG_TINY if (strequal(sp->u.str, "char")) { if (qe_cfg_get_args(ds, sp, 1, 1) < 0) goto fail; qe_cfg_tochar(ds, sp); continue; } else if (strequal(sp->u.str, "int")) { if (qe_cfg_get_args(ds, sp, 1, 1) < 0) goto fail; qe_cfg_tonum(ds, sp); continue; } else if (strequal(sp->u.str, "string")) { if (qe_cfg_get_args(ds, sp, 1, 1) < 0) goto fail; qe_cfg_tostr(ds, sp); continue; } else #endif { qe_cfg_error(ds, "unknown command '%s'", sp->u.str); goto fail; } } if (qe_cfg_call(ds, sp, d)) goto fail; continue; } qe_cfg_error(ds, "invalid function call"); goto fail; #ifndef CONFIG_TINY case TOK_INC: /* post increment: convert to first(x, x += 1) */ case TOK_DEC: /* post decrement: convert to first(x, x -= 1) */ if (qe_cfg_check_lvalue(ds, sp)) goto fail; qe_cfg_set_void(sp + 1); sp[1] = sp[0]; sp->alloc = 0; if (qe_cfg_getvalue(ds, sp)) goto fail; qe_cfg_set_num(sp + 2, 1); if (qe_cfg_assign(ds, sp + 1, op)) goto fail; continue; case '[': /* subscripting */ if (qe_cfg_expr(ds, sp + 1, PREC_EXPRESSION, 0) || !expect_token(ds, ']')) goto fail; if (qe_cfg_op(ds, sp, op)) return 1; continue; case '.': /* property / method accessor */ if (ds->tok != TOK_ID) { qe_cfg_error(ds, "expected property name"); goto fail; } if (qe_cfg_getvalue(ds, sp)) return 1; if (sp->type == TOK_STRING && strequal(ds->str, "length")) { // XXX: use sp->len? qe_cfg_set_num(sp, strlen(sp->u.str)); // utf8? qe_cfg_next_token(ds); continue; } qe_cfg_error(ds, "no such property '%s'", ds->str); goto fail; #endif default: qe_cfg_error(ds, "unsupported operator '%c'", op); goto fail; } //continue; // never reached } if (prec == PREC_ASSIGNMENT) { /* assignments are right associative */ if (qe_cfg_expr(ds, sp + 1, PREC_ASSIGNMENT, 0)) goto fail; if (qe_cfg_assign(ds, sp, op)) goto fail; continue; } #ifndef CONFIG_TINY // XXX: should implement shortcut evaluation for || and && /* other operators are left associative */ if (qe_cfg_expr(ds, sp + 1, prec + 1, 0)) goto fail; // XXX: may need to delay for qe_cfg_op() to decide if qe_cfg_getvalue is OK if (qe_cfg_getvalue(ds, sp)) goto fail; if (qe_cfg_op(ds, sp, op)) goto fail; #else qe_cfg_error(ds, "unsupported operator '%c'", op); goto fail; #endif } fail: ds->p = start_p; ds->line_num = start_line; qe_cfg_next_token(ds); return qe_cfg_skip_expr(ds); } #ifndef CONFIG_TINY 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 '%': // XXX: should pass format a tuple? if (qe_cfg_format(ds, sp)) return 1; break; default: qe_cfg_error(ds, "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)) { // XXX: should pretty print op for `/=` and `%=` qe_cfg_error(ds, "'%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: // XXX: should use shortcut evaluation sp->u.value = sp->u.value && sp[1].u.value; break; case TOK_LOR: // XXX: should use shortcut evaluation sp->u.value = sp->u.value || sp[1].u.value; break; case '?': // Should not get here case ',': // Should not get here sp->u.value = sp[1].u.value; break; default: qe_cfg_error(ds, "invalid numeric operator '%c'", op); return 1; } } return 0; } #endif static int qe_cfg_assign(QEmacsDataSource *ds, QEValue *sp, int op) { if (qe_cfg_check_lvalue(ds, sp)) return 1; if (qe_cfg_getvalue(ds, sp + 1)) return 1; if (op != '=') { #ifndef CONFIG_TINY QEValue val = *sp; sp->alloc = 0; if (qe_cfg_getvalue(ds, sp) || qe_cfg_op(ds, sp, op)) { qe_cfg_set_void(&val); return 1; } qe_cfg_move(sp + 1, sp); *sp = val; #else qe_cfg_error(ds, "unsupported operator %c", op); return 1; #endif } // XXX: should pass QEValue pointer to qe_set_variable() #ifndef CONFIG_TINY if (sp[1].type == TOK_STRING) { qe_set_variable(ds->s, sp->u.str, sp[1].u.str, 0); } else { qe_set_variable(ds->s, sp->u.str, NULL, sp[1].u.value); } // XXX: should detect and report read-only variables and invalid assignments #else qe_cfg_tonum(ds, sp + 1); if (strequal(sp->u.str, "tab-width")) { ds->s->b->tab_width = sp[1].u.value; } else if (strequal(sp->u.str, "default-tab-width")) { ds->s->qs->default_tab_width = sp[1].u.value; } else if (strequal(sp->u.str, "indent-tabs-mode")) { ds->s->indent_tabs_mode = sp[1].u.value; } else if (strequal(sp->u.str, "indent-width")) { ds->s->indent_width = sp[1].u.value; } else { /* ignore other variables without a warning */ qe_cfg_error(ds, "unsupported variable %s", sp->u.str); return 1; } qe_cfg_swap(sp, sp + 1); /* do not reload value */ #endif return 0; } static int qe_cfg_skip_expr(QEmacsDataSource *ds) { /* skip an expression: consume all tokens until prec <= 0. parentheses are skipped in pairs but not balanced. */ int level = 0; // XXX: should match bracket types for (;;) { switch (ds->tok) { case TOK_EOF: // XXX: should potentially complain about missing )]} */ return 1; case '?': case '{': case '[': case '(': level++; break; case ':': case '}': case ']': case ')': if (!level) return 1; level--; break; case ';': if (!level) return 1; break; } qe_cfg_next_token(ds); } } #ifndef CONFIG_TINY static int qe_cfg_get_args(QEmacsDataSource *ds, QEValue *sp, int n1, int n2) { int nargs = 0; int sep = 0; for (nargs = 0; !has_token(ds, ')'); nargs++) { if (sep && !expect_token(ds, sep)) return -1; sep = ','; if (qe_cfg_expr(ds, sp + nargs, PREC_ASSIGNMENT, 0)) { qe_cfg_error(ds, "invalid argument"); // need function name return -1; } } if (nargs < n1) { qe_cfg_error(ds, "missing arguments"); // need function name return -1; } if (nargs > n2) { qe_cfg_error(ds, "extra arguments"); // need function name return -1; } return nargs; } #endif static void qe_cfg_free_args(QEmacsDataSource *ds, int nb_args, CmdArg *args, unsigned char *args_type) { int i; for (i = 0; i < nb_args; i++) { if (args_type[i] == CMD_ARG_STRING) qe_free(&args[i].p); } } static int qe_cfg_call(QEmacsDataSource *ds, QEValue *sp, const CmdDef *d) { EditState *s = ds->s; QEmacsState *qs = s->qs; const char *r; int nb_args, sep, i, ret; CmdArgSpec cas; CmdArg args[MAX_CMD_ARGS]; unsigned char args_type[MAX_CMD_ARGS]; nb_args = 0; /* construct argument type list */ r = d->spec; if (*r == '*') { r++; if (check_read_only(s)) return -1; } /* This 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) { qe_cfg_error(ds, "invalid command definition '%s'", d->name); return -1; } args[nb_args].p = NULL; args_type[nb_args++] = cas.arg_type; } sep = '\0'; 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 = d->val; continue; case CMD_ARG_STRINGVAL: /* kludge for xxx-mode functions and named kbd macros, must be the last argument */ args[i].p = cas.prompt; continue; } if (ds->tok == ')') { /* no more arguments: handle default values */ switch (args_type[i]) { case CMD_ARG_INT | CMD_ARG_RAW_ARGVAL: args[i].n = NO_ARG; continue; case CMD_ARG_INT | CMD_ARG_NUM_ARGVAL: args[i].n = 1; continue; case CMD_ARG_INT | CMD_ARG_NEG_ARGVAL: args[i].n = -1; continue; case CMD_ARG_INT | CMD_ARG_USE_MARK: args[i].n = s->b->mark; continue; case CMD_ARG_INT | CMD_ARG_USE_POINT: args[i].n = s->offset; continue; case CMD_ARG_INT | CMD_ARG_USE_ZERO: args[i].n = 0; continue; case CMD_ARG_INT | CMD_ARG_USE_BSIZE: args[i].n = s->b->total_size; continue; } /* CG: Could supply default arguments. */ /* source stays in front of the ')'. */ /* Let the expression parser complain about the missing argument */ } else { if (sep && !expect_token(ds, sep)) { qe_cfg_free_args(ds, i, args, args_type); return -1; } sep = ','; } /* XXX: should parse and evaluate all arguments and then match actual command arguments */ if (qe_cfg_expr(ds, sp, PREC_ASSIGNMENT, 0)) { qe_cfg_error(ds, "missing arguments for %s", d->name); qe_cfg_free_args(ds, i, args, args_type); 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_NEG_ARGVAL)) args[i].n *= -1; break; case CMD_ARG_STRING: qe_cfg_tostr(ds, sp); // XXX: should complain about type mismatch? /* use allocated string pointer or duplicate it */ if (sp->alloc) { args[i].p = sp->u.str; sp->alloc = 0; sp->type = TOK_VOID; } else { args[i].p = qe_strdup(sp->u.str); } break; } } if (!has_token(ds, ')')) { qe_cfg_error(ds, "too many arguments for %s", d->name); qe_cfg_free_args(ds, nb_args, args, args_type); return -1; } qs->this_cmd_func = d->action.func; qs->ec.function = d->name; call_func(d->sig, d->action, nb_args, args, args_type); qs->ec.function = NULL; qs->last_cmd_func = qs->this_cmd_func; if (qs->active_window) s = qs->active_window; qe_check_window(qs, &s); ds->s = s; sp->type = TOK_VOID; qe_cfg_free_args(ds, nb_args, args, args_type); return 0; } static int qe_cfg_stmt(QEmacsDataSource *ds, QEValue *sp, int skip) { int res = 0; if (has_token(ds, '{')) { /* handle blocks */ while (!has_token(ds, '}')) { if (ds->tok == TOK_EOF) { qe_cfg_error(ds, "missing '}'"); return 1; } res |= qe_cfg_stmt(ds, sp, skip); } return res; } // XXX: should also parse do / while? if (has_token(ds, TOK_IF)) { int truth = 0; if (qe_cfg_expr(ds, sp, PREC_EXPRESSION, skip) || qe_cfg_getvalue(ds, sp)) { res = skip = 1; } else { truth = (sp->type == TOK_STRING) || (sp->u.value != 0); } res |= qe_cfg_stmt(ds, sp, skip | !truth); if (has_token(ds, TOK_ELSE)) res |= qe_cfg_stmt(ds, sp, skip | truth); return res; } if (ds->tok != ';') { /* test for empty statement */ /* accept comma expressions */ if (qe_cfg_expr(ds, sp, PREC_EXPRESSION, skip) || qe_cfg_getvalue(ds, sp)) res = 1; } /* consume `;` if any or is current token first on line */ if (!has_token(ds, ';') && ds->tok != TOK_EOF && ds->tok != '}' && !ds->newline_seen) { //if (!res) qe_cfg_error(ds, "missing ';'"); } return res; } static int qe_parse_script(EditState *s, QEmacsDataSource *ds) { QEmacsState *qs = s->qs; QErrorContext ec_save = 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, 0)) sp->type = TOK_VOID; } if (ds->allocated_buf) { qe_free(&ds->allocated_buf); ds->p = ds->buf = NULL; } qs->ec = ec_save; return sp->type; } static void qe_cfg_postprocess(EditState *s, QEmacsDataSource *ds, int argval) { #ifndef CONFIG_TINY QEValue *sp = &ds->stack[0]; char buf[64]; int len; if (argval == NO_ARG) argval = 0; if (argval > 0 && check_read_only(s)) return; if (!qe_cfg_getvalue(ds, sp)) { switch (sp->type) { case TOK_VOID: break; case TOK_NUMBER: if (argval <= 0) { len = snprintf(buf, sizeof buf, "-> %lld 0x%llx", sp->u.value, sp->u.uvalue); if (sp->u.value >= 32 && sp->u.value < 128) len += snprintf(buf + len, sizeof(buf) - len, " '%c'", (int)sp->u.value); put_status(s, "%s", buf); } else { if (argval == 16) len = snprintf(buf, sizeof buf, "0x%llx", sp->u.uvalue); else len = snprintf(buf, sizeof buf, "%lld", sp->u.value); s->offset += eb_insert_utf8_buf(s->b, s->offset, buf, len); } break; case TOK_STRING: if (argval <= 0) { /* XXX: should optionally 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, (char32_t)sp->u.value); if (argval <= 0) { /* XXX: should optionally unparse character */ put_status(s, "-> '%.*s'", len, buf); } else { s->offset += eb_insert_utf8_buf(s->b, s->offset, buf, len); } break; default: qe_cfg_error(ds, "unexpected value type: %d", sp->type); break; } } #endif } void do_eval_expression(EditState *s, const char *expression, int argval) { QEmacsDataSource ds; qe_cfg_init(&ds); ds.buf = expression; ds.filename = ""; if (qe_parse_script(s, &ds) == TOK_ERR) { // error already reported } else { qe_cfg_postprocess(s, &ds, argval); } qe_cfg_release(&ds); } #define MAX_SCRIPT_LENGTH (128 * 1024 - 1) static int do_eval_buffer_region(EditState *s, int start, int stop, int argval) { QEmacsDataSource ds; char *buf; int length, res = 0; qe_cfg_init(&ds); if (stop < start) { int tmp = start; start = stop; stop = tmp; } /* extract region as UTF-8 with a size limit */ length = eb_get_region_content_size(s->b, start, stop); if (length > MAX_SCRIPT_LENGTH || !(buf = qe_malloc_array(char, length + 1))) { put_error(s, "Buffer region too large"); return -1; } length = eb_get_region_contents(s->b, start, stop, buf, length + 1, 0); ds.buf = ds.allocated_buf = buf; ds.filename = s->b->name; if (qe_parse_script(s, &ds) == TOK_ERR) { // error already reported res = 1; } else { if (argval != NO_ARG && !check_read_only(s)) { // replace region with script result // assuming script did not move point, nor modify buffer eb_delete_range(s->b, start, stop); } qe_cfg_postprocess(s, &ds, argval); } qe_cfg_release(&ds); //do_refresh(s); return res; } void do_eval_region(EditState *s, int argval) { s->region_style = 0; /* deactivate region hilite */ do_eval_buffer_region(s, s->b->mark, s->offset, argval); } void do_eval_buffer(EditState *s, int argval) { do_eval_buffer_region(s, 0, s->b->total_size, argval); } int parse_config_file(EditState *s, const char *filename) { QEmacsDataSource ds; int res; qe_cfg_init(&ds); ds.allocated_buf = file_load(filename, MAX_SCRIPT_LENGTH + 1, NULL); if (!ds.allocated_buf) { if (errno == ERANGE || errno == ENOMEM) { put_error(s, "File too large"); } return -1; } ds.filename = filename; ds.buf = ds.allocated_buf; res = qe_parse_script(s, &ds); qe_cfg_release(&ds); return res; } #ifndef CONFIG_TINY static void symbol_complete(CompleteState *cp, CompleteFunc enumerate) { command_complete(cp, enumerate); variable_complete(cp, enumerate); } static int symbol_print_entry(CompleteState *cp, EditState *s, const char *name) { const CmdDef *d = qe_find_cmd(s->qs, name); if (d) { return command_print_entry(cp, s, name); } else { return variable_print_entry(cp, s, name); } } static CompletionDef symbol_completion = { .name = "symbol", .enumerate = symbol_complete, .print_entry = symbol_print_entry, .get_entry = command_get_entry, .flags = CF_SPACE_OK | CF_NO_AUTO_SUBMIT, }; #endif static const CmdDef parser_commands[] = { CMD2( "eval-expression", "M-:", "Evaluate a qemacs expression", do_eval_expression, ESsi, "s{Eval: }[.symbol]|expression|" "P") /* XXX: should take region as argument, implicit from keyboard */ CMD2( "eval-region", "M-C-z", "Evaluate qemacs expressions in a region", do_eval_region, ESi, "P") CMD2( "eval-buffer", "", "Evaluate qemacs expressions in the buffer", do_eval_buffer, ESi, "P") }; static int parser_init(QEmacsState *qs) { qe_register_commands(qs, NULL, parser_commands, countof(parser_commands)); #ifndef CONFIG_TINY qe_register_completion(qs, &symbol_completion); #endif return 0; } qe_module_init(parser_init);