[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.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(&regex_buff, r, REG_EXTENDED)) {
1.1       lum       499:                regfree(&regex_buff);
1.8       lum       500:                return(dobeep_msg("Regex compilation error"));
1.1       lum       501:        }
1.8       lum       502:        if (!regexec(&regex_buff, e, 0, NULL, 0)) {
                    503:                regfree(&regex_buff);
                    504:                return(TRUE);
1.1       lum       505:        }
1.9       lum       506:        regfree(&regex_buff);
                    507:        return(FALSE);
1.1       lum       508: }