[BACK]Return to interpreter.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / mg

Annotation of src/usr.bin/mg/interpreter.c, Revision 1.9

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