Annotation of src/usr.bin/mg/interpreter.c, Revision 1.10
1.10 ! lum 1: /* $OpenBSD: interpreter.c,v 1.9 2021/03/08 20:01:43 lum Exp $ */
1.1 lum 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:
1.4 lum 23: * (find-file a.txt b.txt. c.txt)
1.1 lum 24: *
1.7 lum 25: * 2. Define a single value variable:
26: * (define myfile d.txt)
1.1 lum 27: *
1.7 lum 28: * 3. Define a list:
29: * (define myfiles(list e.txt f.txt))
30: *
31: * 4. Use the previously defined variable or list:
1.1 lum 32: * (find-file myfiles)
33: *
34: * To do:
35: * 1. multiline parsing - currently only single lines supported.
36: * 2. parsing for '(' and ')' throughout whole string and evaluate correctly.
37: * 3. conditional execution.
1.10 ! lum 38: * 4. deal with quotes around a string: "x x"
! 39: * 5. oh so many things....
1.1 lum 40: * [...]
41: * n. implement user definable functions.
42: */
43: #include <sys/queue.h>
44: #include <regex.h>
45: #include <signal.h>
46: #include <stdio.h>
47: #include <stdlib.h>
48: #include <string.h>
49:
50: #include "def.h"
51: #include "funmap.h"
52:
1.2 lum 53: #ifdef MGLOG
54: #include "kbd.h"
55: #include "log.h"
56: #endif
57:
1.1 lum 58: static int multiarg(char *);
59: static int isvar(char **, char **, int);
60: static int foundvar(char *);
1.8 lum 61: static int doregex(char *, char *);
1.10 ! lum 62: static int parseexp(char *);
! 63: static void clearexp(void);
! 64:
! 65: struct expentry {
! 66: SLIST_ENTRY(expentry) eentry;
! 67: char *exp; /* The string found between paraenthesis. */
! 68: int par1; /* Parenthesis at start of string (=1 */
! 69: int par2; /* Parenthesis at end of string )=2 */
! 70: int expctr; /* An incremental counter:+1 for each exp */
! 71: int blkid; /* Which block are we in? */
! 72: };
! 73: SLIST_HEAD(elisthead, expentry) exphead = SLIST_HEAD_INITIALIZER(exphead);
1.1 lum 74:
75: /*
76: * Structure for variables during buffer evaluation.
77: */
78: struct varentry {
79: SLIST_ENTRY(varentry) entry;
80: char *name;
81: char *vals;
82: int count;
83: };
84: SLIST_HEAD(vlisthead, varentry) varhead = SLIST_HEAD_INITIALIZER(varhead);
85:
86: /*
1.10 ! lum 87: * Line has a '(' as the first non-white char.
! 88: * Do some very basic parsing of line.
! 89: * Multi-line not supported at the moment, To do.
! 90: */
! 91: int
! 92: foundparen(char *funstr)
! 93: {
! 94: struct expentry *e1 = NULL;
! 95: char *p, *valp, *endp = NULL, *regs;
! 96: char expbuf[BUFSIZE], tmpbuf[BUFSIZE];
! 97: int ret, pctr, fndstart, expctr, blkid, fndchr, fndend;
! 98:
! 99: pctr = fndstart = expctr = fndchr = fndend = 0;
! 100: blkid = 1;
! 101: /*
! 102: * Check for blocks of code with opening and closing ().
! 103: * One block = (cmd p a r a m)
! 104: * Two blocks = (cmd p a r a m s)(hola)
! 105: * Two blocks = (cmd p a r (list a m s))(hola)
! 106: * Only single line at moment, but more for multiline.
! 107: */
! 108: p = funstr;
! 109:
! 110: /*
! 111: * Currently can't do () or (( at the moment,
! 112: * just drop out - stops a segv. TODO.
! 113: */
! 114: regs = "[(]+[\t ]*[)]+";
! 115: if (doregex(regs, funstr))
! 116: return(dobeep_msg("Empty lists not supported at moment"));
! 117: regs = "[(]+[\t ]*[(]+";
! 118: if (doregex(regs, funstr))
! 119: return(dobeep_msg("Multiple left parantheses found"));
! 120: /*
! 121: * load expressions into a list called 'expentry', to be processd
! 122: * when all are obtained.
! 123: * Not really live code at the moment. Just part of the process of
! 124: * working out what needs to be done.
! 125: */
! 126: while (*p != '\0') {
! 127: if (*p == '(') {
! 128: if (fndstart == 1) {
! 129: if (endp == NULL)
! 130: *p = '\0';
! 131: else
! 132: *endp = '\0';
! 133: e1->par2 = 1;
! 134: if ((e1->exp = strndup(valp, BUFSIZE)) == NULL)
! 135: return(dobeep_msg("strndup error"));
! 136: }
! 137: if ((e1 = malloc(sizeof(struct expentry))) == NULL)
! 138: return (dobeep_msg("malloc Error"));
! 139: SLIST_INSERT_HEAD(&exphead, e1, eentry);
! 140: e1->exp = NULL;
! 141: e1->expctr = ++expctr;
! 142: e1->blkid = blkid;
! 143: e1->par1 = 1;
! 144: fndstart = 1;
! 145: fndend = 0;
! 146: fndchr = 0;
! 147: endp = NULL;
! 148: pctr++;
! 149: } else if (*p == ')') {
! 150: if (endp == NULL)
! 151: *p = '\0';
! 152: else
! 153: *endp = '\0';
! 154: if ((e1->exp = strndup(valp, BUFSIZE)) == NULL)
! 155: return(dobeep_msg("strndup error"));
! 156: fndstart = 0;
! 157: pctr--;
! 158: } else if (*p != ' ' && *p != '\t') {
! 159: if (fndchr == 0) {
! 160: valp = p;
! 161: fndchr = 1;
! 162: }
! 163: fndend = 0;
! 164: endp = NULL;
! 165: } else if (fndend == 0 && (*p == ' ' || *p == '\t')) {
! 166: *p = ' ';
! 167: fndend = 1;
! 168: endp = p;
! 169: } else if (*p == '\t') /* need to check not between "" */
! 170: *p = ' ';
! 171: if (pctr == 0)
! 172: blkid++;
! 173: p++;
! 174: }
! 175: expbuf[0] = tmpbuf[0] = '\0';
! 176:
! 177: /*
! 178: * Join expressions together for the moment, to progess.
! 179: * This needs to be totally redone and
! 180: * iterate in-to-out, evaluating as we go. Eventually.
! 181: */
! 182: SLIST_FOREACH(e1, &exphead, eentry) {
! 183: if (strlcpy(tmpbuf, expbuf, sizeof(tmpbuf)) >= sizeof(tmpbuf))
! 184: return (dobeep_msg("strlcpy error"));
! 185: expbuf[0] = '\0';
! 186: if (strlcpy(expbuf, e1->exp, sizeof(expbuf)) >= sizeof(expbuf))
! 187: return (dobeep_msg("strlcat error"));
! 188: if (*tmpbuf != '\0')
! 189: if (strlcat(expbuf, " ", sizeof(expbuf)) >=
! 190: sizeof(expbuf))
! 191: return (dobeep_msg("strlcat error"));
! 192: if (strlcat(expbuf, tmpbuf, sizeof(expbuf)) >= sizeof(expbuf))
! 193: return (dobeep_msg("strlcat error"));
! 194: #ifdef MGLOG
! 195: mglog_misc("exp|%s|\n", e1->exp);
! 196: #endif
! 197: }
! 198: if (pctr != 0) {
! 199: clearexp();
! 200: return(dobeep_msg("Opening and closing parentheses error"));
! 201: }
! 202:
! 203: ret = parseexp(expbuf);
! 204: clearexp();
! 205:
! 206: return (ret);
! 207: }
! 208:
! 209: /*
! 210: * At the moment, only paring list defines. Much more to do.
! 211: */
! 212: static int
! 213: parseexp(char *funstr)
! 214: {
! 215: char *regs;
! 216:
! 217: /* Does the line have a list 'define' like: */
! 218: /* (define alist(list 1 2 3 4)) */
! 219: regs = "^define[ ]+.*[ ]+list[ ]+.*[ ]*";
! 220: if (doregex(regs, funstr))
! 221: return(foundvar(funstr));
! 222:
! 223: /* Does the line have a incorrect variable 'define' like: */
! 224: /* (define i y z) */
! 225: regs = "^define[ ]+.*[ ]+.*[ ]+.*$";
! 226: if (doregex(regs, funstr))
! 227: return(dobeep_msg("Invalid use of define."));
! 228:
! 229: /* Does the line have a single variable 'define' like: */
! 230: /* (define i 0) */
! 231: regs = "^define[ ]+.*[ ]+.*$";
! 232: if (doregex(regs, funstr))
! 233: return(foundvar(funstr));
! 234:
! 235: /* Does the line have an unrecognised 'define' */
! 236: regs = "^define[\t ]+";
! 237: if (doregex(regs, funstr))
! 238: return(dobeep_msg("Invalid use of define"));
! 239:
! 240: return(multiarg(funstr));
! 241: }
! 242:
! 243: /*
1.1 lum 244: * Pass a list of arguments to a function.
245: */
246: static int
247: multiarg(char *funstr)
248: {
249: PF funcp;
1.10 ! lum 250: char excbuf[BUFSIZE], argbuf[BUFSIZE];
! 251: char contbuf[BUFSIZE], varbuf[BUFSIZE];
! 252: char *cmdp = NULL, *argp, *fendp = NULL, *endp, *p, *v, *s = " ";
! 253: int spc, numparams, numspc;
! 254: int inlist, sizof, fin;
1.1 lum 255:
1.10 ! lum 256: if (doregex("^[A-Za-z-]+$", funstr))
! 257: return(excline(funstr));
1.1 lum 258:
1.10 ! lum 259: cmdp = funstr;
! 260: fendp = strchr(cmdp, ' ');
1.1 lum 261: *fendp = '\0';
262: /*
263: * If no extant mg command found, just return.
264: */
265: if ((funcp = name_function(cmdp)) == NULL)
266: return (dobeep_msgs("Unknown command: ", cmdp));
267:
268: numparams = numparams_function(funcp);
269: if (numparams == 0)
270: return (dobeep_msgs("Command takes no arguments: ", cmdp));
271:
272: /* now find the first argument */
1.10 ! lum 273: p = fendp + 1;
1.1 lum 274: p = skipwhite(p);
1.10 ! lum 275:
1.1 lum 276: if (strlcpy(argbuf, p, sizeof(argbuf)) >= sizeof(argbuf))
277: return (dobeep_msg("strlcpy error"));
278: argp = argbuf;
279: numspc = spc = 1; /* initially fake a space so we find first argument */
1.10 ! lum 280: inlist = fin = 0;
! 281:
! 282: for (p = argbuf; *p != '\0'; p++) {
! 283: if (*(p + 1) == '\0')
! 284: fin = 1;
1.1 lum 285:
1.10 ! lum 286: if (*p != ' ') {
! 287: if (spc == 1)
! 288: argp = p;
! 289: spc = 0;
1.1 lum 290: }
1.10 ! lum 291: if (*p == ' ' || fin) {
1.1 lum 292: if (spc == 1)
293: continue;
294:
1.10 ! lum 295: if (*p == ' ') {
! 296: *p = '\0'; /* terminate arg string */
! 297: }
! 298: endp = p + 1;
! 299: excbuf[0] = '\0';
! 300: varbuf[0] = '\0';
! 301: contbuf[0] = '\0';
! 302: sizof = sizeof(varbuf);
! 303: v = varbuf;
! 304: if (isvar(&argp, &v, sizof)) {
! 305: (void)(strlcat(varbuf, " ",
! 306: sizof) >= sizof);
! 307:
! 308: *p = ' ';
! 309:
! 310: (void)(strlcpy(contbuf, endp,
! 311: sizeof(contbuf)) >= sizeof(contbuf));
! 312:
! 313: (void)(strlcat(varbuf, contbuf,
! 314: sizof) >= sizof);
! 315:
! 316: (void)(strlcpy(argbuf, varbuf,
! 317: sizof) >= sizof);
! 318:
! 319: p = argp = argbuf;
! 320: while (*p != ' ') {
! 321: if (*p == '\0')
! 322: break;
! 323: p++;
1.1 lum 324: }
1.10 ! lum 325: *p = '\0';
! 326: spc = 1;
! 327: fin = 0;
1.1 lum 328: }
1.10 ! lum 329: if (strlcpy(excbuf, cmdp, sizeof(excbuf))
! 330: >= sizeof(excbuf))
! 331: return (dobeep_msg("strlcpy error"));
! 332: if (strlcat(excbuf, s, sizeof(excbuf))
! 333: >= sizeof(excbuf))
! 334: return (dobeep_msg("strlcat error"));
! 335: if (strlcat(excbuf, argp, sizeof(excbuf))
! 336: >= sizeof(excbuf))
! 337: return (dobeep_msg("strlcat error"));
! 338:
! 339: excline(excbuf);
! 340:
! 341: if (fin)
! 342: break;
! 343:
! 344: *p = ' '; /* unterminate arg string */
1.1 lum 345: spc = 1;
346: }
347: }
348: return (TRUE);
349: }
350:
351:
352: /*
353: * Is an item a value or a variable?
354: */
355: static int
1.10 ! lum 356: isvar(char **argp, char **varbuf, int sizof)
1.1 lum 357: {
358: struct varentry *v1 = NULL;
359:
360: if (SLIST_EMPTY(&varhead))
361: return (FALSE);
1.2 lum 362: #ifdef MGLOG
1.10 ! lum 363: mglog_isvar(*varbuf, *argp, sizof);
1.2 lum 364: #endif
1.1 lum 365: SLIST_FOREACH(v1, &varhead, entry) {
366: if (strcmp(*argp, v1->name) == 0) {
1.10 ! lum 367: (void)(strlcpy(*varbuf, v1->vals, sizof) >= sizof);
1.1 lum 368: return (TRUE);
369: }
370: }
371: return (FALSE);
372: }
373:
374:
375: /*
1.10 ! lum 376: * The define string _must_ adhere to the regex in parsexp().
1.1 lum 377: * This is not the correct way to do parsing but it does highlight
378: * the issues.
379: */
380: static int
1.7 lum 381: foundvar(char *defstr)
1.1 lum 382: {
383: struct varentry *vt, *v1 = NULL;
1.10 ! lum 384: const char t[2] = "t";
! 385: char *p, *vnamep, *vendp = NULL, *valp;
! 386: int spc;
! 387:
! 388: p = strstr(defstr, " "); /* move to first ' ' char. */
! 389: vnamep = skipwhite(p); /* find first char of var name. */
1.1 lum 390: vendp = vnamep;
391:
392: /* now find the end of the list name */
393: while (1) {
394: ++vendp;
1.10 ! lum 395: if (*vendp == ' ')
1.1 lum 396: break;
397: }
398: *vendp = '\0';
1.10 ! lum 399:
1.1 lum 400: /*
401: * Check list name is not an existing function.
402: * Although could this be allowed? Shouldn't context dictate?
403: */
404: if (name_function(vnamep) != NULL)
405: return(dobeep_msgs("Variable/function name clash:", vnamep));
406:
407: p = ++vendp;
1.7 lum 408: p = skipwhite(p);
1.10 ! lum 409:
! 410: if ((*p == 'l') && (*(p + 1) == 'i') && (*(p + 2) == 's')) {
1.7 lum 411: p = strstr(p, t); /* find 't' in 'list'. */
412: valp = skipwhite(++p); /* find first value */
413: } else
414: valp = p;
1.1 lum 415: /*
416: * Now we have the name of the list starting at 'vnamep',
417: * and the first value is at 'valp', record the details
418: * in a linked list. But first remove variable, if existing already.
419: */
420: if (!SLIST_EMPTY(&varhead)) {
421: SLIST_FOREACH_SAFE(v1, &varhead, entry, vt) {
422: if (strcmp(vnamep, v1->name) == 0)
423: SLIST_REMOVE(&varhead, v1, varentry, entry);
424: }
425: }
426: if ((v1 = malloc(sizeof(struct varentry))) == NULL)
427: return (ABORT);
428: SLIST_INSERT_HEAD(&varhead, v1, entry);
429: if ((v1->name = strndup(vnamep, BUFSIZE)) == NULL)
430: return(dobeep_msg("strndup error"));
431: v1->count = 0;
432: vendp = NULL;
1.3 lum 433:
1.1 lum 434: /* initially fake a space so we find first value */
435: spc = 1;
436: /* now loop through values in list value string while counting them */
437: for (p = valp; *p != '\0'; p++) {
1.10 ! lum 438: if (*p != ' ' && *p != '\t') {
1.1 lum 439: if (spc == 1)
440: v1->count++;
441: spc = 0;
442: }
443: }
444: if ((v1->vals = strndup(valp, BUFSIZE)) == NULL)
445: return(dobeep_msg("strndup error"));
446:
1.7 lum 447: #ifdef MGLOG
1.10 ! lum 448: mglog_misc("var:%s\t#items:%d\tvals:|%s|\n", vnamep, v1->count, v1->vals);
1.7 lum 449: #endif
1.1 lum 450:
451: return (TRUE);
452: }
453:
454: /*
1.10 ! lum 455: * Finished with buffer evaluation, so clean up any vars.
! 456: * Perhaps keeps them in mg even after use,...
1.1 lum 457: */
458: int
459: clearvars(void)
460: {
461: struct varentry *v1 = NULL;
462:
463: while (!SLIST_EMPTY(&varhead)) {
464: v1 = SLIST_FIRST(&varhead);
465: SLIST_REMOVE_HEAD(&varhead, entry);
466: free(v1->vals);
467: free(v1->name);
468: free(v1);
469: }
470: return (FALSE);
471: }
472:
473: /*
1.10 ! lum 474: * Finished with block evaluation, so clean up any expressions.
1.1 lum 475: */
1.10 ! lum 476: static void
! 477: clearexp(void)
1.1 lum 478: {
1.10 ! lum 479: struct expentry *e1 = NULL;
1.9 lum 480:
1.10 ! lum 481: while (!SLIST_EMPTY(&exphead)) {
! 482: e1 = SLIST_FIRST(&exphead);
! 483: SLIST_REMOVE_HEAD(&exphead, eentry);
! 484: free(e1->exp);
! 485: free(e1);
1.9 lum 486: }
1.10 ! lum 487: return;
1.8 lum 488: }
489:
490: /*
491: * Test a string against a regular expression.
492: */
1.10 ! lum 493: static int
1.8 lum 494: doregex(char *r, char *e)
495: {
496: regex_t regex_buff;
497:
498: if (regcomp(®ex_buff, r, REG_EXTENDED)) {
1.1 lum 499: regfree(®ex_buff);
1.8 lum 500: return(dobeep_msg("Regex compilation error"));
1.1 lum 501: }
1.8 lum 502: if (!regexec(®ex_buff, e, 0, NULL, 0)) {
503: regfree(®ex_buff);
504: return(TRUE);
1.1 lum 505: }
1.9 lum 506: regfree(®ex_buff);
507: return(FALSE);
1.1 lum 508: }