Commit | Line | Data |
---|---|---|
e29834b8 MW |
1 | /* -*-c-*- |
2 | * | |
7b8ff279 | 3 | * Invoke Lisp scripts and implementations |
e29834b8 MW |
4 | * |
5 | * (c) 2020 Mark Wooding | |
6 | */ | |
7 | ||
8 | /*----- Licensing notice --------------------------------------------------* | |
9 | * | |
10 | * This file is part of Runlisp, a tool for invoking Common Lisp scripts. | |
11 | * | |
12 | * Runlisp is free software: you can redistribute it and/or modify it | |
13 | * under the terms of the GNU General Public License as published by the | |
14 | * Free Software Foundation; either version 3 of the License, or (at your | |
15 | * option) any later version. | |
16 | * | |
17 | * Runlisp is distributed in the hope that it will be useful, but WITHOUT | |
18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
20 | * for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU General Public License | |
23 | * along with Runlisp. If not, see <https://www.gnu.org/licenses/>. | |
24 | */ | |
25 | ||
26 | /*----- Header files ------------------------------------------------------*/ | |
27 | ||
28 | #include "config.h" | |
29 | ||
e29834b8 MW |
30 | #include <ctype.h> |
31 | #include <errno.h> | |
e29834b8 MW |
32 | #include <stdio.h> |
33 | #include <stdlib.h> | |
34 | #include <string.h> | |
35 | ||
7b8ff279 MW |
36 | #include "common.h" |
37 | #include "lib.h" | |
38 | #include "mdwopt.h" | |
e29834b8 | 39 | |
7b8ff279 | 40 | /*----- Static data -------------------------------------------------------*/ |
e29834b8 | 41 | |
7b8ff279 MW |
42 | struct lispsys { |
43 | struct treap_node _node; | |
44 | struct lispsys *next_lisp, *next_accept, *next_prefer, *next_order; | |
e29834b8 | 45 | unsigned f; |
7b8ff279 MW |
46 | #define LF_KNOWN 1u |
47 | #define LF_ACCEPT 2u | |
48 | #define LF_PREFER 4u | |
49 | struct config_section *sect; | |
50 | struct config_var *var; | |
e29834b8 | 51 | }; |
7b8ff279 MW |
52 | #define LISPSYS_NAME(lisp) TREAP_NODE_KEY(lisp) |
53 | #define LISPSYS_NAMELEN(lisp) TREAP_NODE_KEYLEN(lisp) | |
e29834b8 | 54 | |
7b8ff279 MW |
55 | struct lispsys_list { |
56 | struct lispsys *head, **tail; | |
e29834b8 | 57 | }; |
e29834b8 | 58 | |
7b8ff279 MW |
59 | static struct argv argv_tail = ARGV_INIT; |
60 | const char *script = 0; | |
61 | ||
62 | static unsigned flags = 0; | |
63 | #define AF_CMDLINE 0x0000u | |
64 | #define AF_EMBED 0x0001u | |
65 | #define AF_ENV 0x0002u | |
66 | #define AF_CONF 0x0003u | |
67 | #define AF_STATEMASK 0x000fu | |
68 | #define AF_BOGUS 0x0010u | |
69 | #define AF_SETCONF 0x0020u | |
70 | #define AF_NOEMBED 0x0040u | |
71 | #define AF_DRYRUN 0x0080u | |
72 | #define AF_VANILLA 0x0100u | |
73 | ||
74 | struct treap lispsys = TREAP_INIT; | |
75 | static struct lispsys_list | |
76 | lisps = { 0, &lisps.head }, | |
77 | accept = { 0, &accept.head }, | |
78 | prefer = { 0, &prefer.head }; | |
e29834b8 | 79 | |
7b8ff279 | 80 | /*----- Main code ---------------------------------------------------------*/ |
e29834b8 | 81 | |
7b8ff279 MW |
82 | static void version(FILE *fp) |
83 | { fprintf(fp, "%s, version %s\n", progname, PACKAGE_VERSION); } | |
e29834b8 | 84 | |
7b8ff279 | 85 | static void usage(FILE *fp) |
e29834b8 | 86 | { |
7b8ff279 MW |
87 | fprintf(fp, "\ |
88 | usage:\n\ | |
89 | %s [OPTIONS] [--] SCRIPT [ARGUMENTS ...]\n\ | |
90 | %s [OPTIONS] [-e EXPR] [-p EXPR] [-l FILE] [--] [ARGUMENTS ...]\n\ | |
91 | OPTIONS:\n\ | |
92 | [-CDEnqv] [+DEn] [-L SYS,SYS,...] [-c CONF] [-o [SECT:]VAR=VAL]\n", | |
93 | progname, progname); | |
e29834b8 MW |
94 | } |
95 | ||
7b8ff279 | 96 | static void help(FILE *fp) |
e29834b8 | 97 | { |
7b8ff279 MW |
98 | version(fp); fputc('\n', fp); usage(fp); |
99 | fputs("\n\ | |
100 | Help options:\n\ | |
101 | -h, --help Show this help text and exit successfully.\n\ | |
102 | -V, --version Show version number and exit successfully.\n\ | |
103 | \n\ | |
104 | Diagnostics:\n\ | |
105 | -n, --dry-run Don't run run anything (useful with `-v').\n\ | |
106 | -q, --quiet Don't print warning messages.\n\ | |
107 | -v, --verbose Print informational messages (repeatable).\n\ | |
108 | \n\ | |
109 | Configuration:\n\ | |
110 | -E, --command-line-only Don't read embedded options from script.\n\ | |
111 | -c, --config-file=CONF Read configuration from CONF (repeatable).\n\ | |
112 | -o, --set-option=[SECT:]VAR=VAL Set configuration variable (repeatable).\n\ | |
113 | \n\ | |
114 | Lisp implementation selection:\n\ | |
115 | -D, --vanilla-image Run vanilla Lisp images, not custom ones.\n\ | |
116 | -L, --accept-lisp=SYS,SYS,... Only use the listed Lisp systems.\n\ | |
117 | \n\ | |
118 | Evaluation mode:\n\ | |
119 | -e, --evaluate-expression=EXPR Evaluate EXPR for effect (repeatable).\n\ | |
120 | -l, --load-file=FILE Load FILE (repeatable).\n\ | |
121 | -p, --print-expression=EXPR Print (`prin1') EXPR (repeatable).\n", | |
122 | fp); | |
e29834b8 | 123 | } |
e29834b8 | 124 | |
7b8ff279 | 125 | static struct lispsys *ensure_lispsys(const char *name, size_t n) |
e29834b8 | 126 | { |
7b8ff279 MW |
127 | struct lispsys *lisp; |
128 | struct treap_path path; | |
e29834b8 | 129 | |
7b8ff279 MW |
130 | lisp = treap_probe(&lispsys, name, n, &path); |
131 | if (!lisp) { | |
132 | lisp = xmalloc(sizeof(*lisp)); | |
133 | lisp->f = 0; lisp->sect = 0; | |
134 | treap_insert(&lispsys, &path, &lisp->_node, name, n); | |
e29834b8 | 135 | } |
7b8ff279 | 136 | return (lisp); |
e29834b8 MW |
137 | } |
138 | ||
7b8ff279 MW |
139 | #define LISP_LINK(lisp, linkoff) \ |
140 | ((struct lispsys **)((unsigned char *)(lisp) + (linkoff))) | |
e29834b8 | 141 | |
7b8ff279 MW |
142 | static void add_lispsys(const char *p, const char *what, |
143 | struct lispsys_list *list, | |
144 | unsigned flag, size_t linkoff) | |
e29834b8 | 145 | { |
7b8ff279 MW |
146 | struct lispsys *lisp, **link; |
147 | const char *q; | |
e29834b8 | 148 | |
7b8ff279 | 149 | if (!*p) return; |
e29834b8 | 150 | for (;;) { |
e29834b8 | 151 | if (!*p) break; |
7b8ff279 MW |
152 | q = p; while (*p && *p != ',') p++; |
153 | lisp = ensure_lispsys(q, p - q); | |
154 | if (lisp->f&flag) { | |
155 | if (verbose >= 1) | |
156 | moan("ignoring duplicate %s Lisp `%.*s'", what, (int)(p - q), q); | |
157 | } else { | |
158 | link = LISP_LINK(lisp, linkoff); | |
159 | lisp->f |= flag; *link = 0; | |
160 | *list->tail = lisp; list->tail = link; | |
e29834b8 | 161 | } |
7b8ff279 MW |
162 | if (!*p) break; |
163 | p++; | |
e29834b8 MW |
164 | } |
165 | } | |
166 | ||
7b8ff279 MW |
167 | static void check_lisps(const char *what, |
168 | struct lispsys_list *list, size_t linkoff) | |
e29834b8 | 169 | { |
7b8ff279 | 170 | struct lispsys *lisp; |
e29834b8 | 171 | |
7b8ff279 MW |
172 | for (lisp = list->head; lisp; lisp = *LISP_LINK(lisp, linkoff)) |
173 | if (!(lisp->f&LF_KNOWN)) | |
174 | lose("unknown Lisp implementation `%s'", LISPSYS_NAME(lisp)); | |
e29834b8 MW |
175 | } |
176 | ||
7b8ff279 MW |
177 | static void dump_lisps(const char *what, |
178 | struct lispsys_list *list, size_t linkoff) | |
e29834b8 MW |
179 | { |
180 | struct dstr d = DSTR_INIT; | |
7b8ff279 MW |
181 | struct lispsys *lisp; |
182 | int first; | |
183 | ||
184 | first = 1; | |
185 | for (lisp = list->head; lisp; lisp = *LISP_LINK(lisp, linkoff)) { | |
186 | if (first) first = 0; | |
187 | else dstr_puts(&d, ", "); | |
188 | dstr_putf(&d, "`%s'", LISPSYS_NAME(lisp)); | |
e29834b8 | 189 | } |
7b8ff279 MW |
190 | if (first) dstr_puts(&d, "(none)"); |
191 | dstr_putz(&d); | |
192 | moan("%s: %s", what, d.p); | |
e29834b8 MW |
193 | dstr_release(&d); |
194 | } | |
195 | ||
7b8ff279 | 196 | static void push_eval_op(char op, const char *val) |
e29834b8 MW |
197 | { |
198 | char *p; | |
199 | size_t n; | |
200 | ||
7b8ff279 | 201 | if ((flags&AF_STATEMASK) != AF_CMDLINE) { |
e29834b8 | 202 | moan("must use `-e', `-p', or `-l' on command line"); |
7b8ff279 | 203 | flags |= AF_BOGUS; |
e29834b8 MW |
204 | return; |
205 | } | |
206 | ||
207 | n = strlen(val) + 1; | |
208 | p = xmalloc(n + 1); | |
209 | p[0] = op; memcpy(p + 1, val, n); | |
7b8ff279 | 210 | argv_append(&argv_tail, p); |
e29834b8 MW |
211 | } |
212 | ||
7b8ff279 | 213 | static void parse_options(int argc, char *argv[]) |
e29834b8 | 214 | { |
7b8ff279 | 215 | int i; |
e29834b8 | 216 | |
7b8ff279 MW |
217 | static const struct option opts[] = { |
218 | { "help", 0, 0, 'h' }, | |
219 | { "version", 0, 0, 'V' }, | |
220 | { "vanilla-image", OPTF_NEGATE, 0, 'D' }, | |
221 | { "command-line-only", OPTF_NEGATE, 0, 'E' }, | |
222 | { "accept-lisp", OPTF_ARGREQ, 0, 'L' }, | |
223 | { "config-file", OPTF_ARGREQ, 0, 'c' }, | |
224 | { "evaluate-expression", OPTF_ARGREQ, 0, 'e' }, | |
225 | { "load-file", OPTF_ARGREQ, 0, 'l' }, | |
226 | { "dry-run", OPTF_NEGATE, 0, 'n' }, | |
227 | { "set-option", OPTF_ARGREQ, 0, 'o' }, | |
228 | { "print-expression", OPTF_ARGREQ, 0, 'p' }, | |
229 | { "quiet", 0, 0, 'q' }, | |
230 | { "verbose", 0, 0, 'v' }, | |
231 | { 0, 0, 0, 0 } | |
232 | }; | |
233 | ||
234 | optarg = 0; optind = 0; optprog = (/*unconst*/ char *)progname; | |
e29834b8 | 235 | for (;;) { |
7b8ff279 MW |
236 | i = mdwopt(argc, argv, "+hVD+E+L:c:e:l:n+o:p:qv", opts, 0, 0, |
237 | OPTF_NEGATION | OPTF_NOPROGNAME); | |
238 | if (i < 0) break; | |
239 | switch (i) { | |
240 | case 'h': help(stdout); exit(0); | |
241 | case 'V': version(stdout); exit(0); | |
242 | case 'D': flags |= AF_VANILLA; break; | |
243 | case 'D' | OPTF_NEGATED: flags &= ~AF_VANILLA; break; | |
244 | case 'E': flags |= AF_NOEMBED; break; | |
245 | case 'E' | OPTF_NEGATED: flags &= ~AF_NOEMBED; break; | |
246 | case 'L': | |
247 | add_lispsys(optarg, "acceptable", &accept, LF_ACCEPT, | |
248 | offsetof(struct lispsys, next_accept)); | |
249 | break; | |
250 | case 'c': read_config_path(optarg, 0); flags |= AF_SETCONF; break; | |
251 | case 'e': push_eval_op('!', optarg); break; | |
252 | case 'l': push_eval_op('<', optarg); break; | |
253 | case 'n': flags |= AF_DRYRUN; break; | |
254 | case 'n' | OPTF_NEGATED: flags &= ~AF_DRYRUN; break; | |
255 | case 'o': if (set_config_var(optarg)) flags |= AF_BOGUS; break; | |
256 | case 'p': push_eval_op('?', optarg); break; | |
257 | case 'q': if (verbose) verbose--; break; | |
258 | case 'v': verbose++; break; | |
259 | default: flags |= AF_BOGUS; break; | |
e29834b8 | 260 | } |
e29834b8 | 261 | } |
e29834b8 MW |
262 | } |
263 | ||
7b8ff279 | 264 | static void handle_embedded_args(const char *script) |
e29834b8 MW |
265 | { |
266 | struct dstr d = DSTR_INIT; | |
7b8ff279 MW |
267 | struct argv av = ARGV_INIT; |
268 | char *p, *q, *r; const char *l; | |
269 | size_t n; | |
270 | int qstate = 0; | |
e29834b8 MW |
271 | FILE *fp = 0; |
272 | ||
273 | fp = fopen(script, "r"); | |
274 | if (!fp) lose("can't read script `%s': %s", script, strerror(errno)); | |
275 | ||
276 | if (dstr_readline(&d, fp)) goto end; | |
277 | dstr_reset(&d); if (dstr_readline(&d, fp)) goto end; | |
278 | ||
7b8ff279 MW |
279 | p = strstr(d.p, "@RUNLISP:"); if (!p) goto end; |
280 | p += 9; q = p; l = d.p + d.len; | |
281 | for (;;) { | |
282 | while (p < l && ISSPACE(*p)) p++; | |
283 | if (p >= l) break; | |
284 | if (l - p >= 3 && p[0] == '-' && p[1] == '*' && p[2] == '-') { | |
285 | p = strstr(p + 3, "-*-"); | |
286 | if (!p || p + 3 > l) | |
287 | lose("%s:2: unfinished local-variables list", script); | |
288 | p += 3; | |
289 | continue; | |
290 | } | |
291 | if (l - p >= 2 && p[0] == '-' && p[1] == '-' && | |
292 | (l == p + 2 || ISSPACE(p[2]))) | |
293 | break; | |
e29834b8 | 294 | |
7b8ff279 MW |
295 | argv_append(&av, q); |
296 | while (p < l && (qstate || !ISSPACE(*p))) { | |
297 | if (*p == '"') { p++; qstate = !qstate; } | |
298 | else if (*p == '\\') { | |
299 | p++; if (p >= l) lose("%s:2: unfinished `\\' escape", script); | |
300 | *q++ = *p++; | |
301 | } else if (*p == '\'') { | |
302 | p++; r = strchr(p, '\''); | |
303 | if (!r || r > l) lose("%s:2: missing `''", script); | |
304 | n = r - p; memmove(q, p, n); q += n; p = r + 1; | |
305 | } else { | |
306 | n = strcspn(p, qstate ? "\"\\" : "\"'\\ \f\n\r\t\v"); | |
307 | if (n > l - p) n = l - p; | |
308 | memmove(q, p, n); q += n; p += n; | |
309 | } | |
e29834b8 | 310 | } |
7b8ff279 MW |
311 | if (qstate) lose("%s:2: missing `\"'", script); |
312 | if (p < l) p++; | |
313 | *q++ = 0; | |
e29834b8 | 314 | } |
7b8ff279 MW |
315 | |
316 | flags = (flags&~AF_STATEMASK) | AF_EMBED; | |
317 | parse_options(av.n, (char * /*unconst*/*)av.v); | |
318 | if (optind < av.n) | |
319 | lose("%s:2: positional argument `%s' not permitted here", | |
320 | script, av.v[optind]); | |
e29834b8 MW |
321 | |
322 | end: | |
323 | if (fp) { | |
324 | if (ferror(fp)) | |
7b8ff279 | 325 | lose("error reading script `%s': %s", script, strerror(errno)); |
e29834b8 MW |
326 | fclose(fp); |
327 | } | |
7b8ff279 | 328 | dstr_release(&d); argv_release(&av); |
e29834b8 MW |
329 | } |
330 | ||
331 | int main(int argc, char *argv[]) | |
332 | { | |
7b8ff279 MW |
333 | struct config_section_iter si; |
334 | struct config_section *sect; | |
335 | struct config_var *var; | |
336 | struct lispsys_list order; | |
337 | struct lispsys *lisp, **tail; | |
e29834b8 | 338 | struct dstr d = DSTR_INIT; |
7b8ff279 | 339 | struct argv av = ARGV_INIT; |
e29834b8 | 340 | |
7b8ff279 | 341 | set_progname(argv[0]); |
e29834b8 | 342 | |
7b8ff279 MW |
343 | init_config(); |
344 | config_set_var(&config, toplevel, 0, "prefer", "${@ENV:RUNLISP_PREFER?}"); | |
e29834b8 | 345 | |
7b8ff279 MW |
346 | flags = (flags&~AF_STATEMASK) | AF_CMDLINE; |
347 | parse_options(argc - 1, argv + 1); optind++; | |
e29834b8 | 348 | |
7b8ff279 MW |
349 | if (argv_tail.n) |
350 | flags |= AF_NOEMBED; | |
351 | else if (!script && !argv_tail.n) { | |
352 | if (optind < argc) script = argv[optind]++; | |
353 | else flags |= AF_BOGUS; | |
e29834b8 MW |
354 | } |
355 | ||
7b8ff279 MW |
356 | argc -= optind; argv += optind; |
357 | if (argv_tail.n) { | |
358 | argv_append(&argv_tail, "--"); | |
359 | argv_appendn(&argv_tail, (const char *const *)argv, argc); | |
360 | argc = argv_tail.n; argv = (/*unconst*/ char */*unconst*/ *)argv_tail.v; | |
e29834b8 MW |
361 | } |
362 | ||
7b8ff279 MW |
363 | if (flags&AF_BOGUS) { usage(stderr); exit(2); } |
364 | if (!(flags&AF_NOEMBED)) handle_embedded_args(script); | |
365 | if (!(flags&AF_SETCONF)) load_default_config(); | |
366 | if (verbose >= 5) dump_config(); | |
e29834b8 | 367 | |
7b8ff279 MW |
368 | dstr_reset(&d); |
369 | var = config_find_var(&config, toplevel, CF_INHERIT, "prefer"); | |
370 | config_subst_var(&config, toplevel, var, &d); | |
371 | add_lispsys(d.p, "preferred", &prefer, LF_PREFER, | |
372 | offsetof(struct lispsys, next_prefer)); | |
e29834b8 | 373 | |
7b8ff279 MW |
374 | if (!script) |
375 | script = config_subst_string_alloc | |
376 | (&config, common, "<internal>", | |
377 | "${@ENV:RUNLISP_EVAL?${@CONFIG:data-dir}/eval.lisp}"); | |
e29834b8 | 378 | |
e29834b8 MW |
379 | if (setenv("__CL_ARGV0", script, 1)) |
380 | lose("failed to set script-name environment variable"); | |
7b8ff279 MW |
381 | config_set_var(&config, builtin, CF_LITERAL, "@SCRIPT", script); |
382 | ||
383 | tail = lisps.tail; | |
384 | for (config_start_section_iter(&config, &si); | |
385 | (sect = config_next_section(&si)); ) { | |
386 | var = config_find_var(&config, sect, CF_INHERIT, "run-script"); | |
387 | if (!var) continue; | |
388 | lisp = ensure_lispsys(CONFIG_SECTION_NAME(sect), | |
389 | CONFIG_SECTION_NAMELEN(sect)); | |
390 | lisp->f |= LF_KNOWN; lisp->sect = sect; lisp->var = var; | |
391 | *tail = lisp; tail = &lisp->next_lisp; | |
392 | } | |
393 | *tail = 0; lisps.tail = tail; | |
394 | ||
395 | check_lisps("acceptable", &accept, offsetof(struct lispsys, next_accept)); | |
396 | check_lisps("preferred", &prefer, offsetof(struct lispsys, next_prefer)); | |
397 | ||
398 | if (!accept.head) { | |
399 | if (verbose >= 2) | |
400 | moan("no explicitly acceptable implementations: allowing all"); | |
401 | tail = accept.tail; | |
402 | for (lisp = lisps.head; lisp; lisp = lisp->next_lisp) | |
403 | { lisp->f |= LF_ACCEPT; *tail = lisp; tail = &lisp->next_accept; } | |
404 | *tail = 0; accept.tail = tail; | |
405 | } | |
e29834b8 | 406 | |
7b8ff279 MW |
407 | tail = &order.head; |
408 | for (lisp = prefer.head; lisp; lisp = lisp->next_prefer) | |
409 | if (lisp->f&LF_ACCEPT) { *tail = lisp; tail = &lisp->next_order; } | |
410 | for (lisp = accept.head; lisp; lisp = lisp->next_accept) | |
411 | if (!(lisp->f&LF_PREFER)) { *tail = lisp; tail = &lisp->next_order; } | |
412 | *tail = 0; | |
413 | ||
414 | if (verbose >= 4) | |
415 | dump_lisps("known Lisps", &lisps, offsetof(struct lispsys, next_lisp)); | |
416 | if (verbose >= 3) { | |
417 | dump_lisps("acceptable Lisps", &accept, | |
418 | offsetof(struct lispsys, next_accept)); | |
419 | dump_lisps("preferred Lisps", &prefer, | |
420 | offsetof(struct lispsys, next_prefer)); | |
421 | dump_lisps("overall preference order", &order, | |
422 | offsetof(struct lispsys, next_order)); | |
423 | } | |
e29834b8 | 424 | |
7b8ff279 MW |
425 | for (lisp = order.head; lisp; lisp = lisp->next_order) { |
426 | if (config_find_var(&config, lisp->sect, CF_INHERIT, "image-file")) { | |
427 | var = config_find_var(&config, lisp->sect, CF_INHERIT, "image-path"); | |
428 | dstr_reset(&d); config_subst_var(&config, lisp->sect, var, &d); | |
429 | if (file_exists_p(d.p, verbose >= 2 ? FEF_VERBOSE : 0)) | |
430 | config_set_var(&config, lisp->sect, CF_LITERAL, "@IMAGE", "t"); | |
e29834b8 | 431 | } |
7b8ff279 MW |
432 | argv_reset(&av); |
433 | config_subst_split_var(&config, lisp->sect, lisp->var, &av); | |
434 | if (!av.n) { | |
435 | moan("empty command for Lisp implementation `%s'", LISPSYS_NAME(lisp)); | |
436 | continue; | |
437 | } | |
438 | argv_appendn(&av, (const char *const *)argv, argc); | |
439 | if (!try_exec(&av, | |
440 | (flags&AF_DRYRUN ? TEF_DRYRUN : 0) | | |
441 | (verbose >= 2 ? TEF_VERBOSE : 0))) | |
442 | return (0); | |
443 | } | |
e29834b8 | 444 | |
7b8ff279 | 445 | lose("no acceptable Lisp systems found"); |
e29834b8 MW |
446 | } |
447 | ||
448 | /*----- That's all, folks -------------------------------------------------*/ |