1 /* $OpenBSD: interpreter.c,v 1.5 2019/07/20 11:06:33 lum Exp $ */ 2 /* 3 * This file is in the public domain. 4 * 5 * Author: Mark Lumsden <mark@showcomplex.com> 6 */ 7 8 /* 9 * This file attempts to add some 'scripting' functionality into mg. 10 * 11 * The initial goal is to give mg the ability to use it's existing functions 12 * and structures in a linked-up way. Hopefully resulting in user definable 13 * functions. The syntax is 'scheme' like but currently it is not a scheme 14 * interpreter. 15 * 16 * At the moment there is no manual page reference to this file. The code below 17 * is liable to change, so use at your own risk! 18 * 19 * If you do want to do some testing, you can add some lines to your .mg file 20 * like: 21 * 22 * 1. Give multiple arguments to a function that usually would accept only one: 23 * (find-file a.txt b.txt. c.txt) 24 * 25 * 2. Define a list: 26 * (define myfiles(list d.txt e.txt)) 27 * 28 * 3. Use the previously defined list: 29 * (find-file myfiles) 30 * 31 * To do: 32 * 1. multiline parsing - currently only single lines supported. 33 * 2. parsing for '(' and ')' throughout whole string and evaluate correctly. 34 * 3. conditional execution. 35 * 4. define single value variables (define i 0) 36 * 5. deal with quotes around a string: "x x" 37 * 6. oh so many things.... 38 * [...] 39 * n. implement user definable functions. 40 */ 41 #include <sys/queue.h> 42 #include <regex.h> 43 #include <signal.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 48 #include "def.h" 49 #include "funmap.h" 50 51 #ifdef MGLOG 52 #include "kbd.h" 53 #include "log.h" 54 #endif 55 56 static int multiarg(char *); 57 static int isvar(char **, char **, int); 58 static int foundvar(char *); 59 static int foundlist(char *); 60 61 62 /* 63 * Structure for variables during buffer evaluation. 64 */ 65 struct varentry { 66 SLIST_ENTRY(varentry) entry; 67 char *name; 68 char *vals; 69 int count; 70 }; 71 SLIST_HEAD(vlisthead, varentry) varhead = SLIST_HEAD_INITIALIZER(varhead); 72 73 /* 74 * Pass a list of arguments to a function. 75 */ 76 static int 77 multiarg(char *funstr) 78 { 79 regex_t regex_buff; 80 PF funcp; 81 char excbuf[BUFSIZE], argbuf[BUFSIZE], *contbuf, tmpbuf[BUFSIZE]; 82 char *cmdp, *argp, *fendp, *endp, *p, *t, *s = " "; 83 int singlecmd = 0, spc, numparams, numspc; 84 int inlist, foundlst = 0, eolst, rpar, sizof, fin; 85 86 contbuf = NULL; 87 endp = strrchr(funstr, ')'); 88 if (endp == NULL) { 89 ewprintf("No closing parenthesis found"); 90 return(FALSE); 91 } 92 p = endp + 1; 93 if (*p != '\0') 94 *p = '\0'; 95 /* we now know that string starts with '(' and ends with ')' */ 96 if (regcomp(®ex_buff, "^[(][\t ]*[)]$", REG_EXTENDED)) { 97 regfree(®ex_buff); 98 return (dobeep_msg("Could not compile regex")); 99 } 100 if (!regexec(®ex_buff, funstr, 0, NULL, 0)) { 101 regfree(®ex_buff); 102 return (dobeep_msg("No command found")); 103 } 104 /* currently there are no mg commands that don't have a letter */ 105 if (regcomp(®ex_buff, "^[(][\t ]*[A-Za-z-]+[\t ]*[)]$", 106 REG_EXTENDED)) { 107 regfree(®ex_buff); 108 return (dobeep_msg("Could not compile regex")); 109 } 110 if (!regexec(®ex_buff, funstr, 0, NULL, 0)) 111 singlecmd = 1; 112 113 regfree(®ex_buff); 114 p = funstr + 1; /* move past first '(' char. */ 115 cmdp = skipwhite(p); /* find first char of command. */ 116 117 if (singlecmd) { 118 /* remove ')', then check for spaces at the end */ 119 cmdp[strlen(cmdp) - 1] = '\0'; 120 if ((fendp = strchr(cmdp, ' ')) != NULL) 121 *fendp = '\0'; 122 else if ((fendp = strchr(cmdp, '\t')) != NULL) 123 *fendp = '\0'; 124 return(excline(cmdp)); 125 } 126 if ((fendp = strchr(cmdp, ' ')) == NULL) 127 fendp = strchr(cmdp, '\t'); 128 129 *fendp = '\0'; 130 /* 131 * If no extant mg command found, just return. 132 */ 133 if ((funcp = name_function(cmdp)) == NULL) 134 return (dobeep_msgs("Unknown command: ", cmdp)); 135 136 numparams = numparams_function(funcp); 137 if (numparams == 0) 138 return (dobeep_msgs("Command takes no arguments: ", cmdp)); 139 140 /* now find the first argument */ 141 p = fendp + 1; 142 p = skipwhite(p); 143 if (strlcpy(argbuf, p, sizeof(argbuf)) >= sizeof(argbuf)) 144 return (dobeep_msg("strlcpy error")); 145 argp = argbuf; 146 numspc = spc = 1; /* initially fake a space so we find first argument */ 147 inlist = eolst = fin = rpar = 0; 148 149 for (p = argp; fin == 0; p++) { 150 #ifdef MGLOG 151 mglog_execbuf("", excbuf, argbuf, argp, eolst, inlist, cmdp, 152 p, contbuf); 153 #endif 154 if (foundlst) { 155 foundlst = 0; 156 p--; /* otherwise 1st arg is missed from list. */ 157 } 158 if (*p == ')') { 159 rpar = 1; 160 *p = '\0'; 161 } 162 if (*p == ' ' || *p == '\t' || *p == '\0') { 163 if (spc == 1) 164 continue; 165 if (spc == 0 && (numspc % numparams == 0)) { 166 if (*p == '\0') 167 eolst = 1; 168 else 169 eolst = 0; 170 *p = '\0'; /* terminate arg string */ 171 endp = p + 1; 172 excbuf[0] = '\0'; 173 /* Is arg a var? */ 174 if (!inlist) { 175 sizof = sizeof(tmpbuf); 176 t = tmpbuf; 177 if (isvar(&argp, &t, sizof)) { 178 if ((contbuf = strndup(endp, 179 BUFSIZE)) == NULL) 180 return(FALSE); 181 *p = ' '; 182 (void)(strlcpy(argbuf, tmpbuf, 183 sizof) >= sizof); 184 p = argp = argbuf; 185 spc = 1; 186 foundlst = inlist = 1; 187 continue; 188 } 189 } 190 if (strlcpy(excbuf, cmdp, sizeof(excbuf)) 191 >= sizeof(excbuf)) 192 return (dobeep_msg("strlcpy error")); 193 if (strlcat(excbuf, s, sizeof(excbuf)) 194 >= sizeof(excbuf)) 195 return (dobeep_msg("strlcat error")); 196 if (strlcat(excbuf, argp, sizeof(excbuf)) 197 >= sizeof(excbuf)) 198 return (dobeep_msg("strlcat error")); 199 200 excline(excbuf); 201 #ifdef MGLOG 202 mglog_execbuf(" ", excbuf, argbuf, argp, 203 eolst, inlist, cmdp, p, contbuf); 204 #endif 205 *p = ' '; /* so 'for' loop can continue */ 206 if (eolst) { 207 if (contbuf != NULL) { 208 (void)strlcpy(argbuf, contbuf, 209 sizeof(argbuf)); 210 free(contbuf); 211 contbuf = NULL; 212 p = argp = argbuf; 213 foundlst = 1; 214 inlist = 0; 215 if (rpar) 216 fin = 1; 217 continue; 218 } 219 spc = 1; 220 inlist = 0; 221 } 222 if (eolst && rpar) 223 fin = 1; 224 } 225 numspc++; 226 spc = 1; 227 } else { 228 if (spc == 1) 229 if ((numparams == 1) || 230 ((numspc + 1) % numparams) == 0) 231 argp = p; 232 spc = 0; 233 } 234 } 235 return (TRUE); 236 } 237 238 239 /* 240 * Is an item a value or a variable? 241 */ 242 static int 243 isvar(char **argp, char **tmpbuf, int sizof) 244 { 245 struct varentry *v1 = NULL; 246 247 if (SLIST_EMPTY(&varhead)) 248 return (FALSE); 249 #ifdef MGLOG 250 mglog_isvar(*tmpbuf, *argp, sizof); 251 #endif 252 SLIST_FOREACH(v1, &varhead, entry) { 253 if (strcmp(*argp, v1->name) == 0) { 254 (void)(strlcpy(*tmpbuf, v1->vals, sizof) >= sizof); 255 return (TRUE); 256 } 257 } 258 return (FALSE); 259 } 260 261 262 /* 263 * The (define string _must_ adhere to the regex in foundparen. 264 * This is not the correct way to do parsing but it does highlight 265 * the issues. 266 */ 267 static int 268 foundlist(char *defstr) 269 { 270 struct varentry *vt, *v1 = NULL; 271 const char e[1] = "e", t[1] = "t"; 272 char *p, *vnamep, *vendp = NULL, *valp, *o; 273 int spc; 274 275 276 p = defstr + 1; /* move past first '(' char. */ 277 p = skipwhite(p); /* find first char of 'define'. */ 278 p = strstr(p, e); /* find first 'e' in 'define'. */ 279 p = strstr(++p, e); /* find second 'e' in 'define'. */ 280 p++; /* move past second 'e'. */ 281 vnamep = skipwhite(p); /* find first char of var name. */ 282 vendp = vnamep; 283 284 /* now find the end of the list name */ 285 while (1) { 286 ++vendp; 287 if (*vendp == '(' || *vendp == ' ' || *vendp == '\t') 288 break; 289 } 290 *vendp = '\0'; 291 /* 292 * Check list name is not an existing function. 293 * Although could this be allowed? Shouldn't context dictate? 294 */ 295 if (name_function(vnamep) != NULL) 296 return(dobeep_msgs("Variable/function name clash:", vnamep)); 297 298 p = ++vendp; 299 p = strstr(p, t); /* find 't' in 'list'. */ 300 valp = skipwhite(++p); /* find first value */ 301 /* 302 * Now we have the name of the list starting at 'vnamep', 303 * and the first value is at 'valp', record the details 304 * in a linked list. But first remove variable, if existing already. 305 */ 306 if (!SLIST_EMPTY(&varhead)) { 307 SLIST_FOREACH_SAFE(v1, &varhead, entry, vt) { 308 if (strcmp(vnamep, v1->name) == 0) 309 SLIST_REMOVE(&varhead, v1, varentry, entry); 310 } 311 } 312 if ((v1 = malloc(sizeof(struct varentry))) == NULL) 313 return (ABORT); 314 SLIST_INSERT_HEAD(&varhead, v1, entry); 315 if ((v1->name = strndup(vnamep, BUFSIZE)) == NULL) 316 return(dobeep_msg("strndup error")); 317 v1->count = 0; 318 vendp = NULL; 319 320 /* initially fake a space so we find first value */ 321 spc = 1; 322 /* now loop through values in list value string while counting them */ 323 for (p = valp; *p != '\0'; p++) { 324 if (*p == ' ' || *p == '\t') { 325 if (spc == 0) 326 vendp = p; 327 spc = 1; 328 } else if (*p == ')') { 329 o = p - 1; 330 if (*o != ' ' && *o != '\t') 331 vendp = p; 332 break; 333 } else { 334 if (spc == 1) 335 v1->count++; 336 spc = 0; 337 } 338 } 339 *vendp = '\0'; 340 if ((v1->vals = strndup(valp, BUFSIZE)) == NULL) 341 return(dobeep_msg("strndup error")); 342 343 return (TRUE); 344 } 345 346 347 /* 348 * to do 349 */ 350 static int 351 foundvar(char *funstr) 352 { 353 ewprintf("to do"); 354 return (TRUE); 355 } 356 357 /* 358 * Finished with evaluation, so clean up any vars. 359 */ 360 int 361 clearvars(void) 362 { 363 struct varentry *v1 = NULL; 364 365 while (!SLIST_EMPTY(&varhead)) { 366 v1 = SLIST_FIRST(&varhead); 367 SLIST_REMOVE_HEAD(&varhead, entry); 368 free(v1->vals); 369 free(v1->name); 370 free(v1); 371 } 372 return (FALSE); 373 } 374 375 /* 376 * Line has a '(' as the first non-white char. 377 * Do some very basic parsing of line with '(' as the first character. 378 * Multi-line not supported at the moment, To do. 379 */ 380 int 381 foundparen(char *funstr) 382 { 383 regex_t regex_buff; 384 char *regs; 385 386 /* Does the line have a list 'define' like: */ 387 /* (define alist(list 1 2 3 4)) */ 388 regs = "^[(][\t ]*define[\t ]+[^\t (]+[\t ]*[(][\t ]*list[\t ]+"\ 389 "[^\t ]+.*[)][\t ]*[)]"; 390 if (regcomp(®ex_buff, regs, REG_EXTENDED)) { 391 regfree(®ex_buff); 392 return(dobeep_msg("Could not compile regex")); 393 } 394 if (!regexec(®ex_buff, funstr, 0, NULL, 0)) { 395 regfree(®ex_buff); 396 return(foundlist(funstr)); 397 } 398 /* Does the line have a single variable 'define' like: */ 399 /* (define i 0) */ 400 regs = "^[(][\t ]*define[\t ]+[^\t (]+[\t ]*[^\t (]+[\t ]*[)]"; 401 if (regcomp(®ex_buff, regs, REG_EXTENDED)) { 402 regfree(®ex_buff); 403 return(dobeep_msg("Could not compile regex")); 404 } 405 if (!regexec(®ex_buff, funstr, 0, NULL, 0)) { 406 regfree(®ex_buff); 407 return(foundvar(funstr)); 408 } 409 /* Does the line have an unrecognised 'define' */ 410 regs = "^[(][\t ]*define[\t ]+"; 411 if (regcomp(®ex_buff, regs, REG_EXTENDED)) { 412 regfree(®ex_buff); 413 return(dobeep_msg("Could not compile regex")); 414 } 415 if (!regexec(®ex_buff, funstr, 0, NULL, 0)) { 416 regfree(®ex_buff); 417 return(dobeep_msg("Invalid use of define")); 418 } 419 regfree(®ex_buff); 420 return(multiarg(funstr)); 421 } 422