xref: /illumos-gate/usr/src/common/ficl/tools.c (revision 33c72b7598992897b94815b1f47b7b8077e53808)
1 /*
2  * t o o l s . c
3  * Forth Inspired Command Language - programming tools
4  * Author: John Sadler (john_sadler@alum.mit.edu)
5  * Created: 20 June 2000
6  * $Id: tools.c,v 1.12 2010/08/12 13:57:22 asau Exp $
7  */
8 /*
9  * Copyright (c) 1997-2001 John Sadler (john_sadler@alum.mit.edu)
10  * All rights reserved.
11  *
12  * Get the latest Ficl release at http://ficl.sourceforge.net
13  *
14  * I am interested in hearing from anyone who uses Ficl. If you have
15  * a problem, a success story, a defect, an enhancement request, or
16  * if you would like to contribute to the Ficl release, please
17  * contact me by email at the address above.
18  *
19  * L I C E N S E  and  D I S C L A I M E R
20  *
21  * Redistribution and use in source and binary forms, with or without
22  * modification, are permitted provided that the following conditions
23  * are met:
24  * 1. Redistributions of source code must retain the above copyright
25  *    notice, this list of conditions and the following disclaimer.
26  * 2. Redistributions in binary form must reproduce the above copyright
27  *    notice, this list of conditions and the following disclaimer in the
28  *    documentation and/or other materials provided with the distribution.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
31  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
34  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40  * SUCH DAMAGE.
41  */
42 
43 /*
44  * NOTES:
45  * SEE needs information about the addresses of functions that
46  * are the CFAs of colon definitions, constants, variables, DOES>
47  * words, and so on. It gets this information from a table and supporting
48  * functions in words.c.
49  * fiColonParen fiDoDoes createParen fiVariableParen fiUserParen fiConstantParen
50  *
51  * Step and break debugger for Ficl
52  * debug  ( xt -- )   Start debugging an xt
53  * Set a breakpoint
54  * Specify breakpoint default action
55  */
56 
57 #include <stdbool.h>
58 #include "ficl.h"
59 
60 extern void exit(int);
61 
62 static void ficlPrimitiveStepIn(ficlVm *vm);
63 static void ficlPrimitiveStepOver(ficlVm *vm);
64 static void ficlPrimitiveStepBreak(ficlVm *vm);
65 
66 void
67 ficlCallbackAssert(ficlCallback *callback, int expression,
68     char *expressionString, char *filename, int line)
69 {
70 #if FICL_ROBUST >= 1
71 	if (!expression) {
72 		static char buffer[256];
73 		(void) sprintf(buffer, "ASSERTION FAILED at %s:%d: \"%s\"\n",
74 		    filename, line, expressionString);
75 		ficlCallbackTextOut(callback, buffer);
76 		exit(-1);
77 	}
78 #else /* FICL_ROBUST >= 1 */
79 	FICL_IGNORE(callback);
80 	FICL_IGNORE(expression);
81 	FICL_IGNORE(expressionString);
82 	FICL_IGNORE(filename);
83 	FICL_IGNORE(line);
84 #endif /* FICL_ROBUST >= 1 */
85 }
86 
87 /*
88  * v m S e t B r e a k
89  * Set a breakpoint at the current value of IP by
90  * storing that address in a BREAKPOINT record
91  */
92 static void
93 ficlVmSetBreak(ficlVm *vm, ficlBreakpoint *pBP)
94 {
95 	ficlWord *pStep = ficlSystemLookup(vm->callback.system, "step-break");
96 	FICL_VM_ASSERT(vm, pStep);
97 
98 	pBP->address = vm->ip;
99 	pBP->oldXT = *vm->ip;
100 	*vm->ip = pStep;
101 }
102 
103 /*
104  * d e b u g P r o m p t
105  */
106 static void
107 ficlDebugPrompt(bool debug)
108 {
109 	if (debug)
110 		(void) setenv("prompt", "dbg> ", 1);
111 	else
112 		(void) setenv("prompt", "${interpret}", 1);
113 }
114 
115 #if 0
116 static int
117 isPrimitive(ficlWord *word)
118 {
119 	ficlWordKind wk = ficlWordClassify(word);
120 	return ((wk != COLON) && (wk != DOES));
121 }
122 #endif
123 
124 /*
125  * d i c t H a s h S u m m a r y
126  * Calculate a figure of merit for the dictionary hash table based
127  * on the average search depth for all the words in the dictionary,
128  * assuming uniform distribution of target keys. The figure of merit
129  * is the ratio of the total search depth for all keys in the table
130  * versus a theoretical optimum that would be achieved if the keys
131  * were distributed into the table as evenly as possible.
132  * The figure would be worse if the hash table used an open
133  * addressing scheme (i.e. collisions resolved by searching the
134  * table for an empty slot) for a given size table.
135  */
136 #if FICL_WANT_FLOAT
137 void
138 ficlPrimitiveHashSummary(ficlVm *vm)
139 {
140 	ficlDictionary *dictionary = ficlVmGetDictionary(vm);
141 	ficlHash *pFHash;
142 	ficlWord **hash;
143 	unsigned size;
144 	ficlWord *word;
145 	unsigned i;
146 	int nMax = 0;
147 	int nWords = 0;
148 	int nFilled;
149 	double avg = 0.0;
150 	double best;
151 	int nAvg, nRem, nDepth;
152 
153 	FICL_VM_DICTIONARY_CHECK(vm, dictionary, 0);
154 
155 	pFHash = dictionary->wordlists[dictionary->wordlistCount - 1];
156 	hash = pFHash->table;
157 	size = pFHash->size;
158 	nFilled = size;
159 
160 	for (i = 0; i < size; i++) {
161 		int n = 0;
162 		word = hash[i];
163 
164 		while (word) {
165 			++n;
166 			++nWords;
167 			word = word->link;
168 		}
169 
170 		avg += (double)(n * (n+1)) / 2.0;
171 
172 		if (n > nMax)
173 			nMax = n;
174 		if (n == 0)
175 			--nFilled;
176 	}
177 
178 	/* Calc actual avg search depth for this hash */
179 	avg = avg / nWords;
180 
181 	/* Calc best possible performance with this size hash */
182 	nAvg = nWords / size;
183 	nRem = nWords % size;
184 	nDepth = size * (nAvg * (nAvg+1))/2 + (nAvg+1)*nRem;
185 	best = (double)nDepth/nWords;
186 
187 	(void) sprintf(vm->pad, "%d bins, %2.0f%% filled, Depth: "
188 	    "Max=%d, Avg=%2.1f, Best=%2.1f, Score: %2.0f%%\n",
189 	    size, (double)nFilled * 100.0 / size, nMax,
190 	    avg, best, 100.0 * best / avg);
191 
192 	ficlVmTextOut(vm, vm->pad);
193 }
194 #endif
195 
196 /*
197  * Here's the outer part of the decompiler. It's
198  * just a big nested conditional that checks the
199  * CFA of the word to decompile for each kind of
200  * known word-builder code, and tries to do
201  * something appropriate. If the CFA is not recognized,
202  * just indicate that it is a primitive.
203  */
204 static void
205 ficlPrimitiveSeeXT(ficlVm *vm)
206 {
207 	ficlWord *word;
208 	ficlWordKind kind;
209 
210 	word = (ficlWord *)ficlStackPopPointer(vm->dataStack);
211 	kind = ficlWordClassify(word);
212 
213 	switch (kind) {
214 	case FICL_WORDKIND_COLON:
215 		(void) sprintf(vm->pad, ": %.*s\n", word->length, word->name);
216 		ficlVmTextOut(vm, vm->pad);
217 		ficlDictionarySee(ficlVmGetDictionary(vm), word,
218 		    &(vm->callback));
219 	break;
220 	case FICL_WORDKIND_DOES:
221 		ficlVmTextOut(vm, "does>\n");
222 		ficlDictionarySee(ficlVmGetDictionary(vm),
223 		    (ficlWord *)word->param->p, &(vm->callback));
224 	break;
225 	case FICL_WORDKIND_CREATE:
226 		ficlVmTextOut(vm, "create\n");
227 	break;
228 	case FICL_WORDKIND_VARIABLE:
229 		(void) sprintf(vm->pad, "variable = %ld (%#lx)\n",
230 		    (long)word->param->i, (long unsigned)word->param->u);
231 		ficlVmTextOut(vm, vm->pad);
232 	break;
233 #if FICL_WANT_USER
234 	case FICL_WORDKIND_USER:
235 		(void) sprintf(vm->pad, "user variable %ld (%#lx)\n",
236 		    (long)word->param->i, (long unsigned)word->param->u);
237 		ficlVmTextOut(vm, vm->pad);
238 	break;
239 #endif
240 	case FICL_WORDKIND_CONSTANT:
241 		(void) sprintf(vm->pad, "constant = %ld (%#lx)\n",
242 		    (long)word->param->i, (long unsigned)word->param->u);
243 		ficlVmTextOut(vm, vm->pad);
244 	break;
245 	case FICL_WORDKIND_2CONSTANT:
246 		(void) sprintf(vm->pad, "constant = %ld %ld (%#lx %#lx)\n",
247 		    (long)word->param[1].i, (long)word->param->i,
248 		    (long unsigned)word->param[1].u,
249 		    (long unsigned)word->param->u);
250 		ficlVmTextOut(vm, vm->pad);
251 	break;
252 
253 	default:
254 		(void) sprintf(vm->pad, "%.*s is a primitive\n", word->length,
255 		    word->name);
256 		ficlVmTextOut(vm, vm->pad);
257 	break;
258 	}
259 
260 	if (word->flags & FICL_WORD_IMMEDIATE) {
261 		ficlVmTextOut(vm, "immediate\n");
262 	}
263 
264 	if (word->flags & FICL_WORD_COMPILE_ONLY) {
265 		ficlVmTextOut(vm, "compile-only\n");
266 	}
267 }
268 
269 static void
270 ficlPrimitiveSee(ficlVm *vm)
271 {
272 	ficlPrimitiveTick(vm);
273 	ficlPrimitiveSeeXT(vm);
274 }
275 
276 /*
277  * f i c l D e b u g X T
278  * debug  ( xt -- )
279  * Given an xt of a colon definition or a word defined by DOES>, set the
280  * VM up to debug the word: push IP, set the xt as the next thing to execute,
281  * set a breakpoint at its first instruction, and run to the breakpoint.
282  * Note: the semantics of this word are equivalent to "step in"
283  */
284 static void
285 ficlPrimitiveDebugXT(ficlVm *vm)
286 {
287 	ficlWord *xt = ficlStackPopPointer(vm->dataStack);
288 	ficlWordKind wk = ficlWordClassify(xt);
289 
290 	ficlStackPushPointer(vm->dataStack, xt);
291 	ficlPrimitiveSeeXT(vm);
292 
293 	switch (wk) {
294 	case FICL_WORDKIND_COLON:
295 	case FICL_WORDKIND_DOES:
296 		/*
297 		 * Run the colon code and set a breakpoint at the next
298 		 * instruction
299 		 */
300 		ficlVmExecuteWord(vm, xt);
301 		ficlVmSetBreak(vm, &(vm->callback.system->breakpoint));
302 	break;
303 	default:
304 		ficlVmExecuteWord(vm, xt);
305 	break;
306 	}
307 }
308 
309 /*
310  * s t e p I n
311  * Ficl
312  * Execute the next instruction, stepping into it if it's a colon definition
313  * or a does> word. This is the easy kind of step.
314  */
315 static void
316 ficlPrimitiveStepIn(ficlVm *vm)
317 {
318 	/*
319 	 * Do one step of the inner loop
320 	 */
321 	ficlVmExecuteWord(vm, *vm->ip++);
322 
323 	/*
324 	 * Now set a breakpoint at the next instruction
325 	 */
326 	ficlVmSetBreak(vm, &(vm->callback.system->breakpoint));
327 }
328 
329 /*
330  * s t e p O v e r
331  * Ficl
332  * Execute the next instruction atomically. This requires some insight into
333  * the memory layout of compiled code. Set a breakpoint at the next instruction
334  * in this word, and run until we hit it
335  */
336 static void
337 ficlPrimitiveStepOver(ficlVm *vm)
338 {
339 	ficlWord *word;
340 	ficlWordKind kind;
341 	ficlWord *pStep = ficlSystemLookup(vm->callback.system, "step-break");
342 	FICL_VM_ASSERT(vm, pStep);
343 
344 	word = *vm->ip;
345 	kind = ficlWordClassify(word);
346 
347 	switch (kind) {
348 	case FICL_WORDKIND_COLON:
349 	case FICL_WORDKIND_DOES:
350 		/*
351 		 * assume that the next ficlCell holds an instruction
352 		 * set a breakpoint there and return to the inner interpreter
353 		 */
354 		vm->callback.system->breakpoint.address = vm->ip + 1;
355 		vm->callback.system->breakpoint.oldXT =  vm->ip[1];
356 		vm->ip[1] = pStep;
357 	break;
358 	default:
359 		ficlPrimitiveStepIn(vm);
360 	break;
361 	}
362 }
363 
364 /*
365  * s t e p - b r e a k
366  * Ficl
367  * Handles breakpoints for stepped execution.
368  * Upon entry, breakpoint contains the address and replaced instruction
369  * of the current breakpoint.
370  * Clear the breakpoint
371  * Get a command from the console.
372  * i (step in) - execute the current instruction and set a new breakpoint
373  *    at the IP
374  * o (step over) - execute the current instruction to completion and set
375  *    a new breakpoint at the IP
376  * g (go) - execute the current instruction and exit
377  * q (quit) - abort current word
378  * b (toggle breakpoint)
379  */
380 
381 extern char *ficlDictionaryInstructionNames[];
382 
383 static void
384 ficlPrimitiveStepBreak(ficlVm *vm)
385 {
386 	ficlString command;
387 	ficlWord *word;
388 	ficlWord *pOnStep;
389 	bool debug = true;
390 
391 	if (!vm->restart) {
392 		FICL_VM_ASSERT(vm, vm->callback.system->breakpoint.address);
393 		FICL_VM_ASSERT(vm, vm->callback.system->breakpoint.oldXT);
394 
395 		/*
396 		 * Clear the breakpoint that caused me to run
397 		 * Restore the original instruction at the breakpoint,
398 		 * and restore the IP
399 		 */
400 		vm->ip = (ficlIp)(vm->callback.system->breakpoint.address);
401 		*vm->ip = vm->callback.system->breakpoint.oldXT;
402 
403 		/*
404 		 * If there's an onStep, do it
405 		 */
406 		pOnStep = ficlSystemLookup(vm->callback.system, "on-step");
407 		if (pOnStep)
408 			(void) ficlVmExecuteXT(vm, pOnStep);
409 
410 		/*
411 		 * Print the name of the next instruction
412 		 */
413 		word = vm->callback.system->breakpoint.oldXT;
414 
415 		if ((((ficlInstruction)word) > ficlInstructionInvalid) &&
416 		    (((ficlInstruction)word) < ficlInstructionLast))
417 			(void) sprintf(vm->pad, "next: %s (instruction %ld)\n",
418 			    ficlDictionaryInstructionNames[(long)word],
419 			    (long)word);
420 		else {
421 			(void) sprintf(vm->pad, "next: %s\n", word->name);
422 			if (strcmp(word->name, "interpret") == 0)
423 				debug = false;
424 		}
425 
426 		ficlVmTextOut(vm, vm->pad);
427 		ficlDebugPrompt(debug);
428 	} else {
429 		vm->restart = 0;
430 	}
431 
432 	command = ficlVmGetWord(vm);
433 
434 	switch (command.text[0]) {
435 		case 'i':
436 			ficlPrimitiveStepIn(vm);
437 		break;
438 
439 		case 'o':
440 			ficlPrimitiveStepOver(vm);
441 		break;
442 
443 		case 'g':
444 		break;
445 
446 		case 'l': {
447 			ficlWord *xt;
448 			xt = ficlDictionaryFindEnclosingWord(
449 			    ficlVmGetDictionary(vm), (ficlCell *)(vm->ip));
450 			if (xt) {
451 				ficlStackPushPointer(vm->dataStack, xt);
452 				ficlPrimitiveSeeXT(vm);
453 			} else {
454 				ficlVmTextOut(vm, "sorry - can't do that\n");
455 			}
456 			ficlVmThrow(vm, FICL_VM_STATUS_RESTART);
457 		break;
458 		}
459 
460 		case 'q':
461 			ficlDebugPrompt(false);
462 			ficlVmThrow(vm, FICL_VM_STATUS_ABORT);
463 			break;
464 		case 'x': {
465 			/*
466 			 * Take whatever's left in the TIB and feed it to a
467 			 * subordinate ficlVmExecuteString
468 			 */
469 			int returnValue;
470 			ficlString s;
471 			ficlWord *oldRunningWord = vm->runningWord;
472 
473 			FICL_STRING_SET_POINTER(s,
474 			    vm->tib.text + vm->tib.index);
475 			FICL_STRING_SET_LENGTH(s,
476 			    vm->tib.end - FICL_STRING_GET_POINTER(s));
477 
478 			returnValue = ficlVmExecuteString(vm, s);
479 
480 			if (returnValue == FICL_VM_STATUS_OUT_OF_TEXT) {
481 				returnValue = FICL_VM_STATUS_RESTART;
482 				vm->runningWord = oldRunningWord;
483 				ficlVmTextOut(vm, "\n");
484 			}
485 			if (returnValue == FICL_VM_STATUS_ERROR_EXIT)
486 				ficlDebugPrompt(false);
487 
488 			ficlVmThrow(vm, returnValue);
489 			break;
490 		}
491 
492 		default:
493 			ficlVmTextOut(vm,
494 			    "i -- step In\n"
495 			    "o -- step Over\n"
496 			    "g -- Go (execute to completion)\n"
497 			    "l -- List source code\n"
498 			    "q -- Quit (stop debugging and abort)\n"
499 			    "x -- eXecute the rest of the line "
500 			    "as Ficl words\n");
501 			ficlDebugPrompt(true);
502 			ficlVmThrow(vm, FICL_VM_STATUS_RESTART);
503 		break;
504 	}
505 
506 	ficlDebugPrompt(false);
507 }
508 
509 /*
510  * b y e
511  * TOOLS
512  * Signal the system to shut down - this causes ficlExec to return
513  * VM_USEREXIT. The rest is up to you.
514  */
515 static void
516 ficlPrimitiveBye(ficlVm *vm)
517 {
518 	ficlVmThrow(vm, FICL_VM_STATUS_USER_EXIT);
519 }
520 
521 /*
522  * d i s p l a y S t a c k
523  * TOOLS
524  * Display the parameter stack (code for ".s")
525  */
526 
527 struct stackContext
528 {
529 	ficlVm *vm;
530 	ficlDictionary *dictionary;
531 	int count;
532 };
533 
534 static ficlInteger
535 ficlStackDisplayCallback(void *c, ficlCell *cell)
536 {
537 	struct stackContext *context = (struct stackContext *)c;
538 	char buffer[80];
539 
540 #ifdef _LP64
541 	(void) snprintf(buffer, sizeof (buffer),
542 	    "[0x%016lx %3d]: %20ld (0x%016lx)\n",
543 	    (unsigned long)cell, context->count++, (long)cell->i,
544 	    (unsigned long)cell->u);
545 #else
546 	(void) snprintf(buffer, sizeof (buffer),
547 	    "[0x%08x %3d]: %12d (0x%08x)\n",
548 	    (unsigned)cell, context->count++, cell->i, cell->u);
549 #endif
550 
551 	ficlVmTextOut(context->vm, buffer);
552 	return (FICL_TRUE);
553 }
554 
555 void
556 ficlStackDisplay(ficlStack *stack, ficlStackWalkFunction callback,
557     void *context)
558 {
559 	ficlVm *vm = stack->vm;
560 	char buffer[128];
561 	struct stackContext myContext;
562 
563 	FICL_STACK_CHECK(stack, 0, 0);
564 
565 #ifdef _LP64
566 	(void) sprintf(buffer, "[%s stack has %d entries, top at 0x%016lx]\n",
567 	    stack->name, ficlStackDepth(stack), (unsigned long)stack->top);
568 #else
569 	(void) sprintf(buffer, "[%s stack has %d entries, top at 0x%08x]\n",
570 	    stack->name, ficlStackDepth(stack), (unsigned)stack->top);
571 #endif
572 	ficlVmTextOut(vm, buffer);
573 
574 	if (callback == NULL) {
575 		myContext.vm = vm;
576 		myContext.count = 0;
577 		context = &myContext;
578 		callback = ficlStackDisplayCallback;
579 	}
580 	ficlStackWalk(stack, callback, context, FICL_FALSE);
581 
582 #ifdef _LP64
583 	(void) sprintf(buffer, "[%s stack base at 0x%016lx]\n", stack->name,
584 	    (unsigned long)stack->base);
585 #else
586 	(void) sprintf(buffer, "[%s stack base at 0x%08x]\n", stack->name,
587 	    (unsigned)stack->base);
588 #endif
589 	ficlVmTextOut(vm, buffer);
590 }
591 
592 void
593 ficlVmDisplayDataStack(ficlVm *vm)
594 {
595 	ficlStackDisplay(vm->dataStack, NULL, NULL);
596 }
597 
598 static ficlInteger
599 ficlStackDisplaySimpleCallback(void *c, ficlCell *cell)
600 {
601 	struct stackContext *context = (struct stackContext *)c;
602 	char buffer[32];
603 
604 	(void) sprintf(buffer, "%s%ld", context->count ? " " : "",
605 	    (long)cell->i);
606 	context->count++;
607 	ficlVmTextOut(context->vm, buffer);
608 	return (FICL_TRUE);
609 }
610 
611 void
612 ficlVmDisplayDataStackSimple(ficlVm *vm)
613 {
614 	ficlStack *stack = vm->dataStack;
615 	char buffer[32];
616 	struct stackContext context;
617 
618 	FICL_STACK_CHECK(stack, 0, 0);
619 
620 	(void) sprintf(buffer, "[%d] ", ficlStackDepth(stack));
621 	ficlVmTextOut(vm, buffer);
622 
623 	context.vm = vm;
624 	context.count = 0;
625 	ficlStackWalk(stack, ficlStackDisplaySimpleCallback, &context,
626 	    FICL_TRUE);
627 }
628 
629 static ficlInteger
630 ficlReturnStackDisplayCallback(void *c, ficlCell *cell)
631 {
632 	struct stackContext *context = (struct stackContext *)c;
633 	char buffer[128];
634 
635 #ifdef _LP64
636 	(void) sprintf(buffer, "[0x%016lx %3d] %20ld (0x%016lx)",
637 	    (unsigned long)cell, context->count++, cell->i, cell->u);
638 #else
639 	(void) sprintf(buffer, "[0x%08x %3d] %12d (0x%08x)", (unsigned)cell,
640 	    context->count++, cell->i, cell->u);
641 #endif
642 
643 	/*
644 	 * Attempt to find the word that contains the return
645 	 * stack address (as if it is part of a colon definition).
646 	 * If this works, also print the name of the word.
647 	 */
648 	if (ficlDictionaryIncludes(context->dictionary, cell->p)) {
649 		ficlWord *word;
650 		word = ficlDictionaryFindEnclosingWord(context->dictionary,
651 		    cell->p);
652 		if (word) {
653 			int offset = (ficlCell *)cell->p - &word->param[0];
654 			(void) sprintf(buffer + strlen(buffer), ", %s + %d ",
655 			    word->name, offset);
656 		}
657 	}
658 	(void) strcat(buffer, "\n");
659 	ficlVmTextOut(context->vm, buffer);
660 	return (FICL_TRUE);
661 }
662 
663 void
664 ficlVmDisplayReturnStack(ficlVm *vm)
665 {
666 	struct stackContext context;
667 	context.vm = vm;
668 	context.count = 0;
669 	context.dictionary = ficlVmGetDictionary(vm);
670 	ficlStackDisplay(vm->returnStack, ficlReturnStackDisplayCallback,
671 	    &context);
672 }
673 
674 /*
675  * f o r g e t - w i d
676  */
677 static void
678 ficlPrimitiveForgetWid(ficlVm *vm)
679 {
680 	ficlDictionary *dictionary = ficlVmGetDictionary(vm);
681 	ficlHash *hash;
682 
683 	hash = (ficlHash *)ficlStackPopPointer(vm->dataStack);
684 	ficlHashForget(hash, dictionary->here);
685 }
686 
687 /*
688  * f o r g e t
689  * TOOLS EXT  ( "<spaces>name" -- )
690  * Skip leading space delimiters. Parse name delimited by a space.
691  * Find name, then delete name from the dictionary along with all
692  * words added to the dictionary after name. An ambiguous
693  * condition exists if name cannot be found.
694  *
695  * If the Search-Order word set is present, FORGET searches the
696  * compilation word list. An ambiguous condition exists if the
697  * compilation word list is deleted.
698  */
699 static void
700 ficlPrimitiveForget(ficlVm *vm)
701 {
702 	void *where;
703 	ficlDictionary *dictionary = ficlVmGetDictionary(vm);
704 	ficlHash *hash = dictionary->compilationWordlist;
705 
706 	ficlPrimitiveTick(vm);
707 	where = ((ficlWord *)ficlStackPopPointer(vm->dataStack))->name;
708 	ficlHashForget(hash, where);
709 	dictionary->here = FICL_POINTER_TO_CELL(where);
710 }
711 
712 /*
713  * w o r d s
714  */
715 #define	nCOLWIDTH	8
716 
717 static void
718 ficlPrimitiveWordsBackend(ficlVm *vm, ficlDictionary *dictionary,
719     ficlHash *hash, char *ss)
720 {
721 	ficlWord *wp;
722 	int nChars = 0;
723 	int len;
724 	unsigned i;
725 	int nWords = 0, dWords = 0;
726 	char *cp;
727 	char *pPad;
728 	int columns;
729 
730 	cp = getenv("screen-#cols");
731 	/*
732 	 * using strtol for now. TODO: refactor number conversion from
733 	 * ficlPrimitiveToNumber() and use it instead.
734 	 */
735 	if (cp == NULL)
736 		columns = 80;
737 	else
738 		columns = strtol(cp, NULL, 0);
739 
740 	/*
741 	 * the pad is fixed size area, it's better to allocate
742 	 * dedicated buffer space to deal with custom terminal sizes.
743 	 */
744 	pPad = malloc(columns + 1);
745 	if (pPad == NULL)
746 		ficlVmThrowError(vm, "Error: out of memory");
747 
748 	pager_open();
749 	for (i = 0; i < hash->size; i++) {
750 		for (wp = hash->table[i]; wp != NULL; wp = wp->link, nWords++) {
751 			if (wp->length == 0) /* ignore :noname defs */
752 				continue;
753 
754 			if (ss != NULL && strstr(wp->name, ss) == NULL)
755 				continue;
756 			if (ss != NULL && dWords == 0) {
757 				(void) sprintf(pPad,
758 				    "        In vocabulary %s\n",
759 				    hash->name ? hash->name : "<unknown>");
760 				(void) pager_output(pPad);
761 			}
762 			dWords++;
763 
764 			/* prevent line wrap due to long words */
765 			if (nChars + wp->length >= columns) {
766 				pPad[nChars++] = '\n';
767 				pPad[nChars] = '\0';
768 				nChars = 0;
769 				if (pager_output(pPad))
770 					goto pager_done;
771 			}
772 
773 			cp = wp->name;
774 			nChars += sprintf(pPad + nChars, "%s", cp);
775 
776 			if (nChars > columns - 10) {
777 				pPad[nChars++] = '\n';
778 				pPad[nChars] = '\0';
779 				nChars = 0;
780 				if (pager_output(pPad))
781 					goto pager_done;
782 			} else {
783 				len = nCOLWIDTH - nChars % nCOLWIDTH;
784 				while (len-- > 0)
785 					pPad[nChars++] = ' ';
786 			}
787 
788 			if (nChars > columns - 10) {
789 				pPad[nChars++] = '\n';
790 				pPad[nChars] = '\0';
791 				nChars = 0;
792 				if (pager_output(pPad))
793 					goto pager_done;
794 			}
795 		}
796 	}
797 
798 	if (nChars > 0) {
799 		pPad[nChars++] = '\n';
800 		pPad[nChars] = '\0';
801 		nChars = 0;
802 		ficlVmTextOut(vm, pPad);
803 	}
804 
805 	if (ss == NULL) {
806 		(void) sprintf(pPad,
807 		    "Dictionary: %d words, %ld cells used of %u total\n",
808 		    nWords, (long)(dictionary->here - dictionary->base),
809 		    dictionary->size);
810 		(void) pager_output(pPad);
811 	}
812 
813 pager_done:
814 	free(pPad);
815 	pager_close();
816 }
817 
818 static void
819 ficlPrimitiveWords(ficlVm *vm)
820 {
821 	ficlDictionary *dictionary = ficlVmGetDictionary(vm);
822 	ficlHash *hash = dictionary->wordlists[dictionary->wordlistCount - 1];
823 	ficlPrimitiveWordsBackend(vm, dictionary, hash, NULL);
824 }
825 
826 void
827 ficlPrimitiveSiftingImpl(ficlVm *vm, char *ss)
828 {
829 	ficlDictionary *dict = ficlVmGetDictionary(vm);
830 	int i;
831 
832 	for (i = 0; i < dict->wordlistCount; i++)
833 		ficlPrimitiveWordsBackend(vm, dict, dict->wordlists[i], ss);
834 }
835 
836 /*
837  * l i s t E n v
838  * Print symbols defined in the environment
839  */
840 static void
841 ficlPrimitiveListEnv(ficlVm *vm)
842 {
843 	ficlDictionary *dictionary = vm->callback.system->environment;
844 	ficlHash *hash = dictionary->forthWordlist;
845 	ficlWord *word;
846 	unsigned i;
847 	int counter = 0;
848 
849 	pager_open();
850 	for (i = 0; i < hash->size; i++) {
851 		for (word = hash->table[i]; word != NULL;
852 		    word = word->link, counter++) {
853 			(void) sprintf(vm->pad, "%s\n", word->name);
854 			if (pager_output(vm->pad))
855 				goto pager_done;
856 		}
857 	}
858 
859 	(void) sprintf(vm->pad,
860 	    "Environment: %d words, %ld cells used of %u total\n",
861 	    counter, (long)(dictionary->here - dictionary->base),
862 	    dictionary->size);
863 	(void) pager_output(vm->pad);
864 
865 pager_done:
866 	pager_close();
867 }
868 
869 /*
870  * This word lists the parse steps in order
871  */
872 void
873 ficlPrimitiveParseStepList(ficlVm *vm)
874 {
875 	int i;
876 	ficlSystem *system = vm->callback.system;
877 	FICL_VM_ASSERT(vm, system);
878 
879 	ficlVmTextOut(vm, "Parse steps:\n");
880 	ficlVmTextOut(vm, "lookup\n");
881 
882 	for (i = 0; i < FICL_MAX_PARSE_STEPS; i++) {
883 		if (system->parseList[i] != NULL) {
884 			ficlVmTextOut(vm, system->parseList[i]->name);
885 			ficlVmTextOut(vm, "\n");
886 		} else
887 			break;
888 	}
889 }
890 
891 /*
892  * e n v C o n s t a n t
893  * Ficl interface to ficlSystemSetEnvironment and ficlSetEnvD - allow Ficl
894  * code to set environment constants...
895  */
896 static void
897 ficlPrimitiveEnvConstant(ficlVm *vm)
898 {
899 	unsigned value;
900 	FICL_STACK_CHECK(vm->dataStack, 1, 0);
901 
902 	(void) ficlVmGetWordToPad(vm);
903 	value = ficlStackPopUnsigned(vm->dataStack);
904 	(void) ficlDictionarySetConstant(
905 	    ficlSystemGetEnvironment(vm->callback.system),
906 	    vm->pad, (ficlUnsigned)value);
907 }
908 
909 static void
910 ficlPrimitiveEnv2Constant(ficlVm *vm)
911 {
912 	ficl2Integer value;
913 
914 	FICL_STACK_CHECK(vm->dataStack, 2, 0);
915 
916 	(void) ficlVmGetWordToPad(vm);
917 	value = ficlStackPop2Integer(vm->dataStack);
918 	(void) ficlDictionarySet2Constant(
919 	    ficlSystemGetEnvironment(vm->callback.system), vm->pad, value);
920 }
921 
922 
923 /*
924  * f i c l C o m p i l e T o o l s
925  * Builds wordset for debugger and TOOLS optional word set
926  */
927 void
928 ficlSystemCompileTools(ficlSystem *system)
929 {
930 	ficlDictionary *dictionary = ficlSystemGetDictionary(system);
931 	ficlDictionary *environment = ficlSystemGetEnvironment(system);
932 
933 	FICL_SYSTEM_ASSERT(system, dictionary);
934 	FICL_SYSTEM_ASSERT(system, environment);
935 
936 
937 	/*
938 	 * TOOLS and TOOLS EXT
939 	 */
940 	(void) ficlDictionarySetPrimitive(dictionary, ".s",
941 	    ficlVmDisplayDataStack, FICL_WORD_DEFAULT);
942 	(void) ficlDictionarySetPrimitive(dictionary, ".s-simple",
943 	    ficlVmDisplayDataStackSimple,  FICL_WORD_DEFAULT);
944 	(void) ficlDictionarySetPrimitive(dictionary, "bye", ficlPrimitiveBye,
945 	    FICL_WORD_DEFAULT);
946 	(void) ficlDictionarySetPrimitive(dictionary, "forget",
947 	    ficlPrimitiveForget, FICL_WORD_DEFAULT);
948 	(void) ficlDictionarySetPrimitive(dictionary, "see", ficlPrimitiveSee,
949 	    FICL_WORD_DEFAULT);
950 	(void) ficlDictionarySetPrimitive(dictionary, "words",
951 	    ficlPrimitiveWords, FICL_WORD_DEFAULT);
952 
953 	/*
954 	 * Set TOOLS environment query values
955 	 */
956 	(void) ficlDictionarySetConstant(environment, "tools", FICL_TRUE);
957 	(void) ficlDictionarySetConstant(environment, "tools-ext", FICL_FALSE);
958 
959 	/*
960 	 * Ficl extras
961 	 */
962 	(void) ficlDictionarySetPrimitive(dictionary, "r.s",
963 	    ficlVmDisplayReturnStack, FICL_WORD_DEFAULT);
964 	(void) ficlDictionarySetPrimitive(dictionary, ".env",
965 	    ficlPrimitiveListEnv, FICL_WORD_DEFAULT);
966 	(void) ficlDictionarySetPrimitive(dictionary, "env-constant",
967 	    ficlPrimitiveEnvConstant, FICL_WORD_DEFAULT);
968 	(void) ficlDictionarySetPrimitive(dictionary, "env-2constant",
969 	    ficlPrimitiveEnv2Constant, FICL_WORD_DEFAULT);
970 	(void) ficlDictionarySetPrimitive(dictionary, "debug-xt",
971 	    ficlPrimitiveDebugXT, FICL_WORD_DEFAULT);
972 	(void) ficlDictionarySetPrimitive(dictionary, "parse-order",
973 	    ficlPrimitiveParseStepList, FICL_WORD_DEFAULT);
974 	(void) ficlDictionarySetPrimitive(dictionary, "step-break",
975 	    ficlPrimitiveStepBreak, FICL_WORD_DEFAULT);
976 	(void) ficlDictionarySetPrimitive(dictionary, "forget-wid",
977 	    ficlPrimitiveForgetWid, FICL_WORD_DEFAULT);
978 	(void) ficlDictionarySetPrimitive(dictionary, "see-xt",
979 	    ficlPrimitiveSeeXT, FICL_WORD_DEFAULT);
980 
981 #if FICL_WANT_FLOAT
982 	(void) ficlDictionarySetPrimitive(dictionary, ".hash",
983 	    ficlPrimitiveHashSummary, FICL_WORD_DEFAULT);
984 #endif
985 }
986