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(®ex_buff, r, REG_EXTENDED)) {
1.1 lum 430: regfree(®ex_buff);
1.8 lum 431: return(dobeep_msg("Regex compilation error"));
1.1 lum 432: }
1.8 lum 433: if (!regexec(®ex_buff, e, 0, NULL, 0)) {
434: regfree(®ex_buff);
435: return(TRUE);
1.1 lum 436: }
1.9 ! lum 437: regfree(®ex_buff);
! 438: return(FALSE);
1.1 lum 439: }