Annotation of src/usr.bin/mg/interpreter.c, Revision 1.5
1.5 ! lum 1: /* $OpenBSD: interpreter.c,v 1.4 2019/07/20 06:00:16 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: *
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:
1.2 lum 51: #ifdef MGLOG
52: #include "kbd.h"
53: #include "log.h"
54: #endif
55:
1.1 lum 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;
1.3 lum 84: int inlist, foundlst = 0, eolst, rpar, sizof, fin;
1.1 lum 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 */
1.3 lum 147: inlist = eolst = fin = rpar = 0;
1.1 lum 148:
1.3 lum 149: for (p = argp; fin == 0; p++) {
1.2 lum 150: #ifdef MGLOG
1.3 lum 151: mglog_execbuf("", excbuf, argbuf, argp, eolst, inlist, cmdp,
1.2 lum 152: p, contbuf);
153: #endif
1.1 lum 154: if (foundlst) {
155: foundlst = 0;
156: p--; /* otherwise 1st arg is missed from list. */
157: }
1.3 lum 158: if (*p == ')') {
159: rpar = 1;
160: *p = '\0';
161: }
162: if (*p == ' ' || *p == '\t' || *p == '\0') {
1.1 lum 163: if (spc == 1)
164: continue;
165: if (spc == 0 && (numspc % numparams == 0)) {
1.3 lum 166: if (*p == '\0')
167: eolst = 1;
1.1 lum 168: else
1.3 lum 169: eolst = 0;
1.1 lum 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)) {
1.4 lum 178: if ((contbuf = strndup(endp,
179: BUFSIZE)) == NULL)
180: return(FALSE);
1.1 lum 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);
1.2 lum 201: #ifdef MGLOG
202: mglog_execbuf(" ", excbuf, argbuf, argp,
1.3 lum 203: eolst, inlist, cmdp, p, contbuf);
1.2 lum 204: #endif
1.1 lum 205: *p = ' '; /* so 'for' loop can continue */
1.3 lum 206: if (eolst) {
1.1 lum 207: if (contbuf != NULL) {
208: (void)strlcpy(argbuf, contbuf,
209: sizeof(argbuf));
1.5 ! lum 210: free(contbuf);
1.1 lum 211: contbuf = NULL;
212: p = argp = argbuf;
213: foundlst = 1;
214: inlist = 0;
1.3 lum 215: if (rpar)
216: fin = 1;
1.1 lum 217: continue;
218: }
219: spc = 1;
220: inlist = 0;
221: }
1.3 lum 222: if (eolst && rpar)
223: fin = 1;
1.1 lum 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);
1.2 lum 249: #ifdef MGLOG
250: mglog_isvar(*tmpbuf, *argp, sizof);
251: #endif
1.1 lum 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";
1.3 lum 272: char *p, *vnamep, *vendp = NULL, *valp, *o;
1.1 lum 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;
1.3 lum 319:
1.1 lum 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 == ')') {
1.3 lum 329: o = p - 1;
330: if (*o != ' ' && *o != '\t')
331: vendp = p;
1.1 lum 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: }