/* * Copyright (C) 2020 Kevin J. McCarthy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H # include "config.h" #endif #include "mutt.h" #include "mutt_lisp.h" #include #include static int read_backticks (BUFFER *dest, BUFFER *line) { char ch; int rc = -1, matched = 0; if (*line->dptr != '`') return -1; mutt_buffer_addch (dest, *line->dptr++); while (*line->dptr && !matched) { ch = *line->dptr; if (ch == '`') matched = 1; else if (ch == '\\') { mutt_buffer_addch (dest, *line->dptr++); if (!*line->dptr) break; } mutt_buffer_addch (dest, *line->dptr++); } if (matched) rc = 0; else { /* L10N: Printed when backticks in MuttLisp are not matched. */ mutt_error (_("MuttLisp: unclosed backticks: %s"), mutt_b2s (line)); } return rc; } static int read_list (BUFFER *list, BUFFER *line) { int rc = -1, level = 0; char ch, quotechar = 0; mutt_buffer_clear (list); if (!line->dptr || !*line->dptr) return 0; SKIPWS (line->dptr); if (*line->dptr != '(') return -1; mutt_buffer_addch (list, *line->dptr++); level = 1; while (*line->dptr && level) { ch = *line->dptr; if (ch == quotechar) quotechar = 0; else if (ch == '\\' && quotechar != '\'') { mutt_buffer_addch (list, *line->dptr++); if (!*line->dptr) break; } else if (ch == '`' && (!quotechar || quotechar == '"')) { read_backticks (list, line); continue; } else if (!quotechar) { if (ch == '"' || ch == '\'') quotechar = ch; else if (ch == '(') level++; else if (ch == ')') level--; } mutt_buffer_addch (list, *line->dptr++); } if (level == 0) rc = 0; else { /* L10N: Printed when a MuttLisp list is missing a matching closing parenthesis. For example: (a */ mutt_error (_("MuttLisp: unclosed list: %s"), mutt_b2s (line)); } return rc; } static int read_atom (BUFFER *atom, BUFFER *line) { char ch, quotechar = 0; int rc = 0; mutt_buffer_clear (atom); if (!line->dptr || !*line->dptr) return 0; SKIPWS (line->dptr); while (*line->dptr) { ch = *line->dptr; if (ch == quotechar) quotechar = 0; else if (ch == '\\' && quotechar != '\'') { mutt_buffer_addch (atom, *line->dptr++); if (!*line->dptr) break; } else if (ch == '`' && (!quotechar || quotechar == '"')) { rc = read_backticks (atom, line); continue; } else if (!quotechar) { if (ISSPACE (ch) || ch == '(' || ch == ')') break; if (ch == '"' || ch == '\'') quotechar = ch; } mutt_buffer_addch (atom, *line->dptr++); } return rc; } /* returns -1 on an illegal sexp * 0 end of line (denoted by an empty sexp) * 1 valid sexp read in */ static int read_sexp (BUFFER *sexp, BUFFER *line) { int rc = 0; mutt_buffer_clear (sexp); if (!line->dptr || !*line->dptr) return 0; SKIPWS (line->dptr); if (*line->dptr == '(') rc = read_list (sexp, line); else if (*line->dptr) rc = read_atom (sexp, line); if (rc == 0 && mutt_buffer_len (sexp)) rc = 1; return rc; } /* note that a valid sexp might have been read in, but expand to an empty one. * * returns -1 on an illegal sexp * 0 end of line (denoted by an empty sexp) * 1 valid sexp read in */ static int read_eval_sexp (BUFFER *sexp, BUFFER *line) { int rc = -1; BUFFER *temp_sexp = NULL; mutt_buffer_clear (sexp); if (!line->dptr || !*line->dptr) return 0; temp_sexp = mutt_buffer_new (); mutt_buffer_increase_size (temp_sexp, mutt_buffer_len (line)); rc = read_sexp (temp_sexp, line); if (rc <= 0) goto cleanup; mutt_buffer_rewind (temp_sexp); /* We send evaluation back through the muttrc parser. * Variables/expressions will be handled there, while list evaluation * will be done by calling mutt_lisp_eval_list(). * * If we ever decide to add types (e.g. lists or variables), we * could instead directly handle their evaluation internally. The * external mutt_lisp_eval_list() would always return the string * form, while the internal version could handle more. */ if (mutt_extract_token (sexp, temp_sexp, MUTT_TOKEN_LISP | MUTT_TOKEN_COMMENT | MUTT_TOKEN_SEMICOLON) != 0) { rc = -1; goto cleanup; } /* It's possible mutt_extract_token() did not process the entire * temp_sexp. For instance backticks might have generated multiple * words of output that weren't all processed. Prepend that to line * so it is processed later. */ if (*temp_sexp->dptr) { BUFFER *extra = mutt_buffer_pool_get (); mutt_buffer_strcpy (extra, temp_sexp->dptr); mutt_buffer_addstr (extra, line->dptr); mutt_buffer_strcpy (line, mutt_b2s (extra)); mutt_buffer_rewind (line); mutt_buffer_pool_release (&extra); } rc = 1; cleanup: mutt_buffer_free (&temp_sexp); return rc; } static int lisp_concat (BUFFER *result, BUFFER *list) { BUFFER *arg = NULL; int rc; mutt_buffer_clear (result); arg = mutt_buffer_new (); rc = read_eval_sexp (arg, list); while (rc > 0) { mutt_buffer_addstr (result, mutt_b2s (arg)); rc = read_eval_sexp (arg, list); } mutt_buffer_free (&arg); return rc; } static int lisp_quote (BUFFER *result, BUFFER *list) { mutt_buffer_strcpy (result, list->dptr); return 0; } static int lisp_equal (BUFFER *result, BUFFER *list) { BUFFER *first_arg = NULL, *arg = NULL; int rc; mutt_buffer_clear (result); mutt_buffer_addch (result, 't'); first_arg = mutt_buffer_new (); rc = read_eval_sexp (first_arg, list); if (rc <= 0) goto cleanup; arg = mutt_buffer_new (); rc = read_eval_sexp (arg, list); while (rc > 0) { if (mutt_strcmp (mutt_b2s (first_arg), mutt_b2s (arg))) { mutt_buffer_clear (result); break; } rc = read_eval_sexp (arg, list); } cleanup: mutt_buffer_free (&first_arg); mutt_buffer_free (&arg); return rc; } static int lisp_not (BUFFER *result, BUFFER *list) { BUFFER *arg = NULL; int rc; mutt_buffer_clear (result); arg = mutt_buffer_new (); rc = read_eval_sexp (arg, list); if (rc > 0 && !mutt_buffer_len (arg)) mutt_buffer_addstr (result, "t"); mutt_buffer_free (&arg); return rc; } static int lisp_and (BUFFER *result, BUFFER *list) { BUFFER *arg = NULL; int rc; mutt_buffer_clear (result); mutt_buffer_addch (result, 't'); arg = mutt_buffer_new (); rc = read_eval_sexp (arg, list); while (rc > 0) { mutt_buffer_strcpy (result, mutt_b2s (arg)); if (!mutt_buffer_len (result)) break; rc = read_eval_sexp (arg, list); } mutt_buffer_free (&arg); return rc; } static int lisp_or (BUFFER *result, BUFFER *list) { BUFFER *arg = NULL; int rc; mutt_buffer_clear (result); arg = mutt_buffer_new (); rc = read_eval_sexp (arg, list); while (rc > 0) { mutt_buffer_strcpy (result, mutt_b2s (arg)); if (mutt_buffer_len (result)) break; rc = read_eval_sexp (arg, list); } mutt_buffer_free (&arg); return rc; } static int lisp_if (BUFFER *result, BUFFER *list) { BUFFER *cond = NULL; int rc; mutt_buffer_clear (result); cond = mutt_buffer_new (); rc = read_eval_sexp (cond, list); if (rc <= 0) { /* L10N: An error printed for the 'if' function if the condition is missing. For example (if) by itself. */ mutt_error (_("MuttLisp: missing if condition: %s"), mutt_b2s (list)); goto cleanup; } if (!mutt_buffer_len (cond)) read_sexp (result, list); rc = read_eval_sexp (result, list); cleanup: mutt_buffer_free (&cond); return rc; } typedef struct { char *name; int (*func) (BUFFER *, BUFFER *); } lisp_func_t; const lisp_func_t LispFunctions[] = { {"concat", lisp_concat}, {"quote", lisp_quote}, {"equal", lisp_equal}, {"not", lisp_not}, {"and", lisp_and}, {"or", lisp_or}, {"if", lisp_if}, {NULL, NULL} }; static int eval_function (BUFFER *result, const char *func, BUFFER *list) { int i, rc = -1; for (i = 0; LispFunctions[i].name; i++) { if (!mutt_strcmp (func, LispFunctions[i].name)) { rc = LispFunctions[i].func (result, list); break; } } if (!LispFunctions[i].name) { /* L10N: Printed when a function is called that is not recognized by MuttLisp. */ mutt_error (_("MuttLisp: no such function %s"), NONULL (func)); } return rc; } int mutt_lisp_eval_list (BUFFER *result, BUFFER *line) { int rc = -1; BUFFER *list = NULL, *function = NULL; mutt_buffer_clear (result); if (!line->dptr || !*line->dptr) return 0; list = mutt_buffer_new (); mutt_buffer_increase_size (list, mutt_buffer_len (line)); if (read_list (list, line) != 0) goto cleanup; /* Rewind list and trim the outer parens */ *(list->dptr - 1) = '\0'; list->dptr = list->data + 1; function = mutt_buffer_new (); if (read_sexp (function, list) <= 0) goto cleanup; SKIPWS (list->dptr); if (eval_function (result, mutt_b2s (function), list) < 0) goto cleanup; rc = 0; cleanup: mutt_buffer_free (&list); mutt_buffer_free (&function); return rc; }