1 /*- 2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 #include <sys/param.h> /* to pick up __FreeBSD_version */ 29 #include <string.h> 30 #include <stand.h> 31 #include "bootstrap.h" 32 #include "ficl.h" 33 34 INTERP_DEFINE("4th"); 35 36 /* #define BFORTH_DEBUG */ 37 38 #ifdef BFORTH_DEBUG 39 #define DPRINTF(fmt, args...) printf("%s: " fmt "\n" , __func__ , ## args) 40 #else 41 #define DPRINTF(fmt, args...) ((void)0) 42 #endif 43 44 /* 45 * Eventually, all builtin commands throw codes must be defined 46 * elsewhere, possibly bootstrap.h. For now, just this code, used 47 * just in this file, it is getting defined. 48 */ 49 #define BF_PARSE 100 50 51 /* 52 * FreeBSD loader default dictionary cells 53 */ 54 #ifndef BF_DICTSIZE 55 #define BF_DICTSIZE 10000 56 #endif 57 58 /* 59 * BootForth Interface to Ficl Forth interpreter. 60 */ 61 62 FICL_SYSTEM *bf_sys; 63 FICL_VM *bf_vm; 64 65 /* 66 * Shim for taking commands from BF and passing them out to 'standard' 67 * argv/argc command functions. 68 */ 69 static void 70 bf_command(FICL_VM *vm) 71 { 72 char *name, *line, *tail, *cp; 73 size_t len; 74 struct bootblk_command **cmdp; 75 bootblk_cmd_t *cmd; 76 int nstrings, i; 77 int argc, result; 78 char **argv; 79 80 /* Get the name of the current word */ 81 name = vm->runningWord->name; 82 83 /* Find our command structure */ 84 cmd = NULL; 85 SET_FOREACH(cmdp, Xcommand_set) { 86 if (((*cmdp)->c_name != NULL) && !strcmp(name, (*cmdp)->c_name)) 87 cmd = (*cmdp)->c_fn; 88 } 89 if (cmd == NULL) 90 panic("callout for unknown command '%s'", name); 91 92 /* Check whether we have been compiled or are being interpreted */ 93 if (stackPopINT(vm->pStack)) { 94 /* 95 * Get parameters from stack, in the format: 96 * an un ... a2 u2 a1 u1 n -- 97 * Where n is the number of strings, a/u are pairs of 98 * address/size for strings, and they will be concatenated 99 * in LIFO order. 100 */ 101 nstrings = stackPopINT(vm->pStack); 102 for (i = 0, len = 0; i < nstrings; i++) 103 len += stackFetch(vm->pStack, i * 2).i + 1; 104 line = malloc(strlen(name) + len + 1); 105 strcpy(line, name); 106 107 if (nstrings) 108 for (i = 0; i < nstrings; i++) { 109 len = stackPopINT(vm->pStack); 110 cp = stackPopPtr(vm->pStack); 111 strcat(line, " "); 112 strncat(line, cp, len); 113 } 114 } else { 115 /* Get remainder of invocation */ 116 tail = vmGetInBuf(vm); 117 for (cp = tail, len = 0; cp != vm->tib.end && *cp != 0 && *cp != '\n'; cp++, len++) 118 ; 119 120 line = malloc(strlen(name) + len + 2); 121 strcpy(line, name); 122 if (len > 0) { 123 strcat(line, " "); 124 strncat(line, tail, len); 125 vmUpdateTib(vm, tail + len); 126 } 127 } 128 DPRINTF("cmd '%s'", line); 129 130 command_errmsg = command_errbuf; 131 command_errbuf[0] = 0; 132 if (!parse(&argc, &argv, line)) { 133 result = (cmd)(argc, argv); 134 free(argv); 135 } else { 136 result=BF_PARSE; 137 } 138 139 switch (result) { 140 case CMD_CRIT: 141 printf("%s\n", command_errmsg); 142 command_errmsg = NULL; 143 break; 144 case CMD_FATAL: 145 panic("%s", command_errmsg); 146 } 147 148 free(line); 149 /* 150 * If there was error during nested ficlExec(), we may no longer have 151 * valid environment to return. Throw all exceptions from here. 152 */ 153 if (result != CMD_OK) 154 vmThrow(vm, result); 155 156 /* This is going to be thrown!!! */ 157 stackPushINT(vm->pStack,result); 158 } 159 160 /* 161 * Replace a word definition (a builtin command) with another 162 * one that: 163 * 164 * - Throw error results instead of returning them on the stack 165 * - Pass a flag indicating whether the word was compiled or is 166 * being interpreted. 167 * 168 * There is one major problem with builtins that cannot be overcome 169 * in anyway, except by outlawing it. We want builtins to behave 170 * differently depending on whether they have been compiled or they 171 * are being interpreted. Notice that this is *not* the interpreter's 172 * current state. For example: 173 * 174 * : example ls ; immediate 175 * : problem example ; \ "ls" gets executed while compiling 176 * example \ "ls" gets executed while interpreting 177 * 178 * Notice that, though the current state is different in the two 179 * invocations of "example", in both cases "ls" has been 180 * *compiled in*, which is what we really want. 181 * 182 * The problem arises when you tick the builtin. For example: 183 * 184 * : example-1 ['] ls postpone literal ; immediate 185 * : example-2 example-1 execute ; immediate 186 * : problem example-2 ; 187 * example-2 188 * 189 * We have no way, when we get EXECUTEd, of knowing what our behavior 190 * should be. Thus, our only alternative is to "outlaw" this. See RFI 191 * 0007, and ANS Forth Standard's appendix D, item 6.7 for a related 192 * problem, concerning compile semantics. 193 * 194 * The problem is compounded by the fact that "' builtin CATCH" is valid 195 * and desirable. The only solution is to create an intermediary word. 196 * For example: 197 * 198 * : my-ls ls ; 199 * : example ['] my-ls catch ; 200 * 201 * So, with the below implementation, here is a summary of the behavior 202 * of builtins: 203 * 204 * ls -l \ "interpret" behavior, ie, 205 * \ takes parameters from TIB 206 * : ex-1 s" -l" 1 ls ; \ "compile" behavior, ie, 207 * \ takes parameters from the stack 208 * : ex-2 ['] ls catch ; immediate \ undefined behavior 209 * : ex-3 ['] ls catch ; \ undefined behavior 210 * ex-2 ex-3 \ "interpret" behavior, 211 * \ catch works 212 * : ex-4 ex-2 ; \ "compile" behavior, 213 * \ catch does not work 214 * : ex-5 ex-3 ; immediate \ same as ex-2 215 * : ex-6 ex-3 ; \ same as ex-3 216 * : ex-7 ['] ex-1 catch ; \ "compile" behavior, 217 * \ catch works 218 * : ex-8 postpone ls ; immediate \ same as ex-2 219 * : ex-9 postpone ls ; \ same as ex-3 220 * 221 * As the definition below is particularly tricky, and it's side effects 222 * must be well understood by those playing with it, I'll be heavy on 223 * the comments. 224 * 225 * (if you edit this definition, pay attention to trailing spaces after 226 * each word -- I warned you! :-) ) 227 */ 228 #define BUILTIN_CONSTRUCTOR \ 229 ": builtin: " \ 230 ">in @ " /* save the tib index pointer */ \ 231 "' " /* get next word's xt */ \ 232 "swap >in ! " /* point again to next word */ \ 233 "create " /* create a new definition of the next word */ \ 234 ", " /* save previous definition's xt */ \ 235 "immediate " /* make the new definition an immediate word */ \ 236 \ 237 "does> " /* Now, the *new* definition will: */ \ 238 "state @ if " /* if in compiling state: */ \ 239 "1 postpone literal " /* pass 1 flag to indicate compile */ \ 240 "@ compile, " /* compile in previous definition */ \ 241 "postpone throw " /* throw stack-returned result */ \ 242 "else " /* if in interpreting state: */ \ 243 "0 swap " /* pass 0 flag to indicate interpret */ \ 244 "@ execute " /* call previous definition */ \ 245 "throw " /* throw stack-returned result */ \ 246 "then ; " 247 248 /* 249 * Initialise the Forth interpreter, create all our commands as words. 250 */ 251 void 252 bf_init(void) 253 { 254 struct bootblk_command **cmdp; 255 char create_buf[41]; /* 31 characters-long builtins */ 256 int fd; 257 258 bf_sys = ficlInitSystem(BF_DICTSIZE); 259 bf_vm = ficlNewVM(bf_sys); 260 261 /* Put all private definitions in a "builtins" vocabulary */ 262 ficlExec(bf_vm, "vocabulary builtins also builtins definitions"); 263 264 /* Builtin constructor word */ 265 ficlExec(bf_vm, BUILTIN_CONSTRUCTOR); 266 267 /* make all commands appear as Forth words */ 268 SET_FOREACH(cmdp, Xcommand_set) { 269 ficlBuild(bf_sys, (char *)(*cmdp)->c_name, bf_command, FW_DEFAULT); 270 ficlExec(bf_vm, "forth definitions builtins"); 271 sprintf(create_buf, "builtin: %s", (*cmdp)->c_name); 272 ficlExec(bf_vm, create_buf); 273 ficlExec(bf_vm, "builtins definitions"); 274 } 275 ficlExec(bf_vm, "only forth definitions"); 276 277 /* Export some version numbers so that code can detect the loader/host version */ 278 ficlSetEnv(bf_sys, "FreeBSD_version", __FreeBSD_version); 279 ficlSetEnv(bf_sys, "loader_version", bootprog_rev); 280 281 /* try to load and run init file if present */ 282 if ((fd = open("/boot/boot.4th", O_RDONLY)) != -1) { 283 #ifdef LOADER_VERIEXEC 284 if (verify_file(fd, "/boot/boot.4th", 0, VE_GUESS, __func__) < 0) { 285 close(fd); 286 return; 287 } 288 #endif 289 (void)ficlExecFD(bf_vm, fd); 290 close(fd); 291 } 292 } 293 294 /* 295 * Feed a line of user input to the Forth interpreter 296 */ 297 static int 298 bf_run(const char *line) 299 { 300 int result; 301 302 /* 303 * ficl would require extensive changes to accept a const char * 304 * interface. Instead, cast it away here and hope for the best. 305 * We know at the present time the caller for us in the boot 306 * forth loader can tolerate the string being modified because 307 * the string is passed in here and then not touched again. 308 */ 309 result = ficlExec(bf_vm, __DECONST(char *, line)); 310 311 DPRINTF("ficlExec '%s' = %d", line, result); 312 switch (result) { 313 case VM_OUTOFTEXT: 314 case VM_ABORTQ: 315 case VM_QUIT: 316 case VM_ERREXIT: 317 break; 318 case VM_USEREXIT: 319 printf("No where to leave to!\n"); 320 break; 321 case VM_ABORT: 322 printf("Aborted!\n"); 323 break; 324 case BF_PARSE: 325 printf("Parse error!\n"); 326 break; 327 default: 328 if (command_errmsg != NULL) { 329 printf("%s\n", command_errmsg); 330 command_errmsg = NULL; 331 } 332 } 333 334 if (result == VM_USEREXIT) 335 panic("interpreter exit"); 336 setenv("interpret", bf_vm->state ? "" : "OK", 1); 337 338 return (result); 339 } 340 341 void 342 interp_init(void) 343 { 344 345 setenv("script.lang", "forth", 1); 346 bf_init(); 347 /* Read our default configuration. */ 348 interp_include("/boot/loader.rc"); 349 } 350 351 int 352 interp_run(const char *input) 353 { 354 355 bf_vm->sourceID.i = 0; 356 return bf_run(input); 357 } 358 359 /* 360 * Header prepended to each line. The text immediately follows the header. 361 * We try to make this short in order to save memory -- the loader has 362 * limited memory available, and some of the forth files are very long. 363 */ 364 struct includeline 365 { 366 struct includeline *next; 367 char text[0]; 368 }; 369 370 int 371 interp_include(const char *filename) 372 { 373 struct includeline *script, *se, *sp; 374 char input[256]; /* big enough? */ 375 int res; 376 char *cp; 377 int prevsrcid, fd, line; 378 379 if (((fd = open(filename, O_RDONLY)) == -1)) { 380 snprintf(command_errbuf, sizeof(command_errbuf), 381 "can't open '%s': %s", filename, strerror(errno)); 382 return(CMD_ERROR); 383 } 384 385 #ifdef LOADER_VERIEXEC 386 if (verify_file(fd, filename, 0, VE_GUESS, __func__) < 0) { 387 close(fd); 388 sprintf(command_errbuf,"can't verify '%s'", filename); 389 return(CMD_ERROR); 390 } 391 #endif 392 /* 393 * Read the script into memory. 394 */ 395 script = se = NULL; 396 line = 0; 397 398 while (fgetstr(input, sizeof(input), fd) >= 0) { 399 line++; 400 cp = input; 401 /* Allocate script line structure and copy line, flags */ 402 if (*cp == '\0') 403 continue; /* ignore empty line, save memory */ 404 sp = malloc(sizeof(struct includeline) + strlen(cp) + 1); 405 /* On malloc failure (it happens!), free as much as possible and exit */ 406 if (sp == NULL) { 407 while (script != NULL) { 408 se = script; 409 script = script->next; 410 free(se); 411 } 412 snprintf(command_errbuf, sizeof(command_errbuf), 413 "file '%s' line %d: memory allocation failure - aborting", 414 filename, line); 415 close(fd); 416 return (CMD_ERROR); 417 } 418 strcpy(sp->text, cp); 419 sp->next = NULL; 420 421 if (script == NULL) { 422 script = sp; 423 } else { 424 se->next = sp; 425 } 426 se = sp; 427 } 428 close(fd); 429 430 /* 431 * Execute the script 432 */ 433 prevsrcid = bf_vm->sourceID.i; 434 bf_vm->sourceID.i = fd; 435 res = CMD_OK; 436 for (sp = script; sp != NULL; sp = sp->next) { 437 res = bf_run(sp->text); 438 if (res != VM_OUTOFTEXT) { 439 snprintf(command_errbuf, sizeof(command_errbuf), 440 "Error while including %s, in the line:\n%s", 441 filename, sp->text); 442 res = CMD_ERROR; 443 break; 444 } else 445 res = CMD_OK; 446 } 447 bf_vm->sourceID.i = prevsrcid; 448 449 while (script != NULL) { 450 se = script; 451 script = script->next; 452 free(se); 453 } 454 return(res); 455 } 456