1 /*******************************************************************
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.11 2001/12/05 07:21:34 jsadler Exp $
7 *******************************************************************/
9 ** Copyright (c) 1997-2001 John Sadler (john_sadler@alum.mit.edu)
10 ** All rights reserved.
12 ** Get the latest Ficl release at http://ficl.sourceforge.net
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.
19 ** L I C E N S E and D I S C L A I M E R
21 ** Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions
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.
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
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 ** colonParen doDoes createParen variableParen userParen constantParen
51 ** Step and break debugger for Ficl
52 ** debug ( xt -- ) Start debugging an xt
54 ** Specify breakpoint default action
60 #include <stdio.h> /* sprintf */
71 ** nBREAKPOINTS sizes the breakpoint array. One breakpoint (bp 0) is reserved
72 ** for the STEP command. The rest are user programmable.
74 #define nBREAKPOINTS 32
79 /**************************************************************************
81 ** Set a breakpoint at the current value of IP by
82 ** storing that address in a BREAKPOINT record
83 **************************************************************************/
84 static void vmSetBreak(FICL_VM
*pVM
, FICL_BREAKPOINT
*pBP
)
86 FICL_WORD
*pStep
= ficlLookup(pVM
->pSys
, "step-break");
89 pBP
->address
= pVM
->ip
;
90 pBP
->origXT
= *pVM
->ip
;
95 /**************************************************************************
96 ** d e b u g P r o m p t
97 **************************************************************************/
98 static void debugPrompt(FICL_VM
*pVM
)
100 vmTextOut(pVM
, "dbg> ", 0);
104 /**************************************************************************
105 ** i s A F i c l W o r d
106 ** Vet a candidate pointer carefully to make sure
107 ** it's not some chunk o' inline data...
108 ** It has to have a name, and it has to look
109 ** like it's in the dictionary address range.
110 ** NOTE: this excludes :noname words!
111 **************************************************************************/
112 int isAFiclWord(FICL_DICT
*pd
, FICL_WORD
*pFW
)
115 if (!dictIncludes(pd
, pFW
))
118 if (!dictIncludes(pd
, pFW
->name
))
121 if ((pFW
->link
!= NULL
) && !dictIncludes(pd
, pFW
->link
))
124 if ((pFW
->nName
<= 0) || (pFW
->name
[pFW
->nName
] != '\0'))
127 if (strlen(pFW
->name
) != pFW
->nName
)
135 static int isPrimitive(FICL_WORD
*pFW
)
137 WORDKIND wk
= ficlWordClassify(pFW
);
138 return ((wk
!= COLON
) && (wk
!= DOES
));
143 /**************************************************************************
144 f i n d E n c l o s i n g W o r d
145 ** Given a pointer to something, check to make sure it's an address in the
146 ** dictionary. If so, search backwards until we find something that looks
147 ** like a dictionary header. If successful, return the address of the
148 ** FICL_WORD found. Otherwise return NULL.
149 ** nSEARCH_CELLS sets the maximum neighborhood this func will search before giving up
150 **************************************************************************/
151 #define nSEARCH_CELLS 100
153 static FICL_WORD
*findEnclosingWord(FICL_VM
*pVM
, CELL
*cp
)
156 FICL_DICT
*pd
= vmGetDict(pVM
);
159 if (!dictIncludes(pd
, (void *)cp
))
162 for (i
= nSEARCH_CELLS
; i
> 0; --i
, --cp
)
164 pFW
= (FICL_WORD
*)(cp
+ 1 - (sizeof (FICL_WORD
) / sizeof (CELL
)));
165 if (isAFiclWord(pd
, pFW
))
173 /**************************************************************************
175 ** TOOLS ( "<spaces>name" -- )
176 ** Display a human-readable representation of the named word's definition.
177 ** The source of the representation (object-code decompilation, source
178 ** block, etc.) and the particular form of the display is implementation
180 **************************************************************************/
182 ** seeColon (for proctologists only)
183 ** Walks a colon definition, decompiling
184 ** on the fly. Knows about primitive control structures.
186 static void seeColon(FICL_VM
*pVM
, CELL
*pc
)
190 FICL_DICT
*pd
= vmGetDict(pVM
);
191 FICL_WORD
*pSemiParen
= ficlLookup(pVM
->pSys
, "(;)");
194 for (; pc
->p
!= pSemiParen
; pc
++)
196 FICL_WORD
*pFW
= (FICL_WORD
*)(pc
->p
);
199 if ((void *)pc
== (void *)pVM
->ip
)
203 cp
+= sprintf(cp
, "%3d ", (int)(pc
-param0
));
205 if (isAFiclWord(pd
, pFW
))
207 WORDKIND kind
= ficlWordClassify(pFW
);
214 if (isAFiclWord(pd
, c
.p
))
216 FICL_WORD
*pLit
= (FICL_WORD
*)c
.p
;
217 sprintf(cp
, "%.*s ( %#lx literal )",
218 pLit
->nName
, pLit
->name
, (unsigned long)c
.u
);
221 sprintf(cp
, "literal %ld (%#lx)",
222 (long)c
.i
, (unsigned long)c
.u
);
226 FICL_STRING
*sp
= (FICL_STRING
*)(void *)++pc
;
227 pc
= (CELL
*)alignPtr(sp
->text
+ sp
->count
+ 1) - 1;
228 sprintf(cp
, "s\" %.*s\"", sp
->count
, sp
->text
);
233 FICL_STRING
*sp
= (FICL_STRING
*)(void *)++pc
;
234 pc
= (CELL
*)alignPtr(sp
->text
+ sp
->count
+ 1) - 1;
235 sprintf(cp
, "c\" %.*s\"", sp
->count
, sp
->text
);
241 sprintf(cp
, "if / while (branch %d)", (int)(pc
+c
.i
-param0
));
243 sprintf(cp
, "until (branch %d)", (int)(pc
+c
.i
-param0
));
248 sprintf(cp
, "repeat (branch %d)", (int)(pc
+c
.i
-param0
));
250 sprintf(cp
, "else (branch %d)", (int)(pc
+c
.i
-param0
));
252 sprintf(cp
, "endof (branch %d)", (int)(pc
+c
.i
-param0
));
257 sprintf(cp
, "of (branch %d)", (int)(pc
+c
.i
-param0
));
262 sprintf(cp
, "?do (leave %d)", (int)((CELL
*)c
.p
-param0
));
266 sprintf(cp
, "do (leave %d)", (int)((CELL
*)c
.p
-param0
));
270 sprintf(cp
, "loop (branch %d)", (int)(pc
+c
.i
-param0
));
274 sprintf(cp
, "+loop (branch %d)", (int)(pc
+c
.i
-param0
));
277 sprintf(cp
, "%.*s", pFW
->nName
, pFW
->name
);
282 else /* probably not a word - punt and print value */
284 sprintf(cp
, "%ld ( %#lx )", (long)pc
->i
, (unsigned long)pc
->u
);
287 vmTextOut(pVM
, pVM
->pad
, 1);
290 vmTextOut(pVM
, ";", 1);
294 ** Here's the outer part of the decompiler. It's
295 ** just a big nested conditional that checks the
296 ** CFA of the word to decompile for each kind of
297 ** known word-builder code, and tries to do
298 ** something appropriate. If the CFA is not recognized,
299 ** just indicate that it is a primitive.
301 static void seeXT(FICL_VM
*pVM
)
306 pFW
= (FICL_WORD
*)stackPopPtr(pVM
->pStack
);
307 kind
= ficlWordClassify(pFW
);
312 sprintf(pVM
->pad
, ": %.*s", pFW
->nName
, pFW
->name
);
313 vmTextOut(pVM
, pVM
->pad
, 1);
314 seeColon(pVM
, pFW
->param
);
318 vmTextOut(pVM
, "does>", 1);
319 seeColon(pVM
, (CELL
*)pFW
->param
->p
);
323 vmTextOut(pVM
, "create", 1);
327 sprintf(pVM
->pad
, "variable = %ld (%#lx)",
328 (long)pFW
->param
->i
, (unsigned long)pFW
->param
->u
);
329 vmTextOut(pVM
, pVM
->pad
, 1);
334 sprintf(pVM
->pad
, "user variable %ld (%#lx)",
335 (long)pFW
->param
->i
, (unsigned long)pFW
->param
->u
);
336 vmTextOut(pVM
, pVM
->pad
, 1);
341 sprintf(pVM
->pad
, "constant = %ld (%#lx)",
342 (long)pFW
->param
->i
, (unsigned long)pFW
->param
->u
);
343 vmTextOut(pVM
, pVM
->pad
, 1);
346 sprintf(pVM
->pad
, "%.*s is a primitive", pFW
->nName
, pFW
->name
);
347 vmTextOut(pVM
, pVM
->pad
, 1);
351 if (pFW
->flags
& FW_IMMEDIATE
)
353 vmTextOut(pVM
, "immediate", 1);
356 if (pFW
->flags
& FW_COMPILE
)
358 vmTextOut(pVM
, "compile-only", 1);
365 static void see(FICL_VM
*pVM
)
373 /**************************************************************************
374 f i c l D e b u g X T
376 ** Given an xt of a colon definition or a word defined by DOES>, set the
377 ** VM up to debug the word: push IP, set the xt as the next thing to execute,
378 ** set a breakpoint at its first instruction, and run to the breakpoint.
379 ** Note: the semantics of this word are equivalent to "step in"
380 **************************************************************************/
381 void ficlDebugXT(FICL_VM
*pVM
)
383 FICL_WORD
*xt
= stackPopPtr(pVM
->pStack
);
384 WORDKIND wk
= ficlWordClassify(xt
);
386 stackPushPtr(pVM
->pStack
, xt
);
394 ** Run the colon code and set a breakpoint at the next instruction
397 vmSetBreak(pVM
, &(pVM
->pSys
->bpStep
));
409 /**************************************************************************
412 ** Execute the next instruction, stepping into it if it's a colon definition
413 ** or a does> word. This is the easy kind of step.
414 **************************************************************************/
415 void stepIn(FICL_VM
*pVM
)
418 ** Do one step of the inner loop
425 ** Now set a breakpoint at the next instruction
427 vmSetBreak(pVM
, &(pVM
->pSys
->bpStep
));
433 /**************************************************************************
436 ** Execute the next instruction atomically. This requires some insight into
437 ** the memory layout of compiled code. Set a breakpoint at the next instruction
438 ** in this word, and run until we hit it
439 **************************************************************************/
440 void stepOver(FICL_VM
*pVM
)
444 FICL_WORD
*pStep
= ficlLookup(pVM
->pSys
, "step-break");
448 kind
= ficlWordClassify(pFW
);
455 ** assume that the next cell holds an instruction
456 ** set a breakpoint there and return to the inner interp
458 pVM
->pSys
->bpStep
.address
= pVM
->ip
+ 1;
459 pVM
->pSys
->bpStep
.origXT
= pVM
->ip
[1];
472 /**************************************************************************
475 ** Handles breakpoints for stepped execution.
476 ** Upon entry, bpStep contains the address and replaced instruction
477 ** of the current breakpoint.
478 ** Clear the breakpoint
479 ** Get a command from the console.
480 ** i (step in) - execute the current instruction and set a new breakpoint
482 ** o (step over) - execute the current instruction to completion and set
483 ** a new breakpoint at the IP
484 ** g (go) - execute the current instruction and exit
485 ** q (quit) - abort current word
486 ** b (toggle breakpoint)
487 **************************************************************************/
488 void stepBreak(FICL_VM
*pVM
)
496 assert(pVM
->pSys
->bpStep
.address
);
497 assert(pVM
->pSys
->bpStep
.origXT
);
499 ** Clear the breakpoint that caused me to run
500 ** Restore the original instruction at the breakpoint,
501 ** and restore the IP
503 pVM
->ip
= (IPTYPE
)(pVM
->pSys
->bpStep
.address
);
504 *pVM
->ip
= pVM
->pSys
->bpStep
.origXT
;
507 ** If there's an onStep, do it
509 pOnStep
= ficlLookup(pVM
->pSys
, "on-step");
511 ficlExecXT(pVM
, pOnStep
);
514 ** Print the name of the next instruction
516 pFW
= pVM
->pSys
->bpStep
.origXT
;
517 sprintf(pVM
->pad
, "next: %.*s", pFW
->nName
, pFW
->name
);
519 if (isPrimitive(pFW
))
521 strcat(pVM
->pad
, " ( primitive )");
525 vmTextOut(pVM
, pVM
->pad
, 1);
535 if (!strincmp(si
.cp
, "i", si
.count
))
539 else if (!strincmp(si
.cp
, "g", si
.count
))
543 else if (!strincmp(si
.cp
, "l", si
.count
))
546 xt
= findEnclosingWord(pVM
, (CELL
*)(pVM
->ip
));
549 stackPushPtr(pVM
->pStack
, xt
);
554 vmTextOut(pVM
, "sorry - can't do that", 1);
556 vmThrow(pVM
, VM_RESTART
);
558 else if (!strincmp(si
.cp
, "o", si
.count
))
562 else if (!strincmp(si
.cp
, "q", si
.count
))
564 ficlTextOut(pVM
, FICL_PROMPT
, 0);
565 vmThrow(pVM
, VM_ABORT
);
567 else if (!strincmp(si
.cp
, "x", si
.count
))
570 ** Take whatever's left in the TIB and feed it to a subordinate ficlExec
573 char *cp
= pVM
->tib
.cp
+ pVM
->tib
.index
;
574 int count
= pVM
->tib
.end
- cp
;
575 FICL_WORD
*oldRun
= pVM
->runningWord
;
577 ret
= ficlExecC(pVM
, cp
, count
);
579 if (ret
== VM_OUTOFTEXT
)
582 pVM
->runningWord
= oldRun
;
583 vmTextOut(pVM
, "", 1);
590 vmTextOut(pVM
, "i -- step In", 1);
591 vmTextOut(pVM
, "o -- step Over", 1);
592 vmTextOut(pVM
, "g -- Go (execute to completion)", 1);
593 vmTextOut(pVM
, "l -- List source code", 1);
594 vmTextOut(pVM
, "q -- Quit (stop debugging and abort)", 1);
595 vmTextOut(pVM
, "x -- eXecute the rest of the line as ficl words", 1);
597 vmThrow(pVM
, VM_RESTART
);
604 /**************************************************************************
607 ** Signal the system to shut down - this causes ficlExec to return
608 ** VM_USEREXIT. The rest is up to you.
609 **************************************************************************/
610 static void bye(FICL_VM
*pVM
)
612 vmThrow(pVM
, VM_USEREXIT
);
617 /**************************************************************************
618 d i s p l a y S t a c k
620 ** Display the parameter stack (code for ".s")
621 **************************************************************************/
622 static void displayPStack(FICL_VM
*pVM
)
624 FICL_STACK
*pStk
= pVM
->pStack
;
625 int d
= stackDepth(pStk
);
629 vmCheckStack(pVM
, 0, 0);
632 vmTextOut(pVM
, "(Stack Empty) ", 0);
636 for (i
= 0; i
< d
; i
++)
638 vmTextOut(pVM
, ltoa((*pCell
++).i
, pVM
->pad
, pVM
->base
), 0);
639 vmTextOut(pVM
, " ", 0);
646 static void displayRStack(FICL_VM
*pVM
)
648 FICL_STACK
*pStk
= pVM
->rStack
;
649 int d
= stackDepth(pStk
);
652 FICL_DICT
*dp
= vmGetDict(pVM
);
654 vmCheckStack(pVM
, 0, 0);
657 vmTextOut(pVM
, "(Stack Empty) ", 0);
661 for (i
= 0; i
< d
; i
++)
665 ** Attempt to find the word that contains the
666 ** stacked address (as if it is part of a colon definition).
667 ** If this works, print the name of the word. Otherwise print
668 ** the value as a number.
670 if (dictIncludes(dp
, c
.p
))
672 FICL_WORD
*pFW
= findEnclosingWord(pVM
, c
.p
);
675 int offset
= (CELL
*)c
.p
- &pFW
->param
[0];
676 sprintf(pVM
->pad
, "%s+%d ", pFW
->name
, offset
);
677 vmTextOut(pVM
, pVM
->pad
, 0);
678 continue; /* no need to print the numeric value */
681 vmTextOut(pVM
, ltoa(c
.i
, pVM
->pad
, pVM
->base
), 0);
682 vmTextOut(pVM
, " ", 0);
690 /**************************************************************************
693 **************************************************************************/
694 static void forgetWid(FICL_VM
*pVM
)
696 FICL_DICT
*pDict
= vmGetDict(pVM
);
699 pHash
= (FICL_HASH
*)stackPopPtr(pVM
->pStack
);
700 hashForget(pHash
, pDict
->here
);
706 /**************************************************************************
708 ** TOOLS EXT ( "<spaces>name" -- )
709 ** Skip leading space delimiters. Parse name delimited by a space.
710 ** Find name, then delete name from the dictionary along with all
711 ** words added to the dictionary after name. An ambiguous
712 ** condition exists if name cannot be found.
714 ** If the Search-Order word set is present, FORGET searches the
715 ** compilation word list. An ambiguous condition exists if the
716 ** compilation word list is deleted.
717 **************************************************************************/
718 static void forget(FICL_VM
*pVM
)
721 FICL_DICT
*pDict
= vmGetDict(pVM
);
722 FICL_HASH
*pHash
= pDict
->pCompile
;
725 where
= ((FICL_WORD
*)stackPopPtr(pVM
->pStack
))->name
;
726 hashForget(pHash
, where
);
727 pDict
->here
= PTRtoCELL where
;
733 /**************************************************************************
736 **************************************************************************/
738 static void listWords(FICL_VM
*pVM
)
740 FICL_DICT
*dp
= vmGetDict(pVM
);
741 FICL_HASH
*pHash
= dp
->pSearch
[dp
->nLists
- 1];
749 char *pPad
= pVM
->pad
;
751 for (i
= 0; i
< pHash
->size
; i
++)
753 for (wp
= pHash
->table
[i
]; wp
!= NULL
; wp
= wp
->link
, nWords
++)
755 if (wp
->nName
== 0) /* ignore :noname defs */
759 nChars
+= sprintf(pPad
+ nChars
, "%s", cp
);
768 vmTextOut(pVM
, "--- Press Enter to continue ---",0);
770 vmTextOut(pVM
,"\r",0);
772 vmTextOut(pVM
, pPad
, 1);
776 len
= nCOLWIDTH
- nChars
% nCOLWIDTH
;
778 pPad
[nChars
++] = ' ';
788 vmTextOut(pVM
, "--- Press Enter to continue ---",0);
790 vmTextOut(pVM
,"\r",0);
792 vmTextOut(pVM
, pPad
, 1);
801 vmTextOut(pVM
, pPad
, 1);
804 sprintf(pVM
->pad
, "Dictionary: %d words, %ld cells used of %u total",
805 nWords
, (long) (dp
->here
- dp
->dict
), dp
->size
);
806 vmTextOut(pVM
, pVM
->pad
, 1);
811 /**************************************************************************
813 ** Print symbols defined in the environment
814 **************************************************************************/
815 static void listEnv(FICL_VM
*pVM
)
817 FICL_DICT
*dp
= pVM
->pSys
->envp
;
818 FICL_HASH
*pHash
= dp
->pForthWords
;
823 for (i
= 0; i
< pHash
->size
; i
++)
825 for (wp
= pHash
->table
[i
]; wp
!= NULL
; wp
= wp
->link
, nWords
++)
827 vmTextOut(pVM
, wp
->name
, 1);
831 sprintf(pVM
->pad
, "Environment: %d words, %ld cells used of %u total",
832 nWords
, (long) (dp
->here
- dp
->dict
), dp
->size
);
833 vmTextOut(pVM
, pVM
->pad
, 1);
838 /**************************************************************************
839 e n v C o n s t a n t
840 ** Ficl interface to ficlSetEnv and ficlSetEnvD - allow ficl code to set
841 ** environment constants...
842 **************************************************************************/
843 static void envConstant(FICL_VM
*pVM
)
848 vmCheckStack(pVM
, 1, 0);
853 ficlSetEnv(pVM
->pSys
, pVM
->pad
, (FICL_UNS
)value
);
857 static void env2Constant(FICL_VM
*pVM
)
862 vmCheckStack(pVM
, 2, 0);
868 ficlSetEnvD(pVM
->pSys
, pVM
->pad
, v1
, v2
);
873 /**************************************************************************
874 f i c l C o m p i l e T o o l s
875 ** Builds wordset for debugger and TOOLS optional word set
876 **************************************************************************/
878 void ficlCompileTools(FICL_SYSTEM
*pSys
)
880 FICL_DICT
*dp
= pSys
->dp
;
884 ** TOOLS and TOOLS EXT
886 dictAppendWord(dp
, ".s", displayPStack
, FW_DEFAULT
);
887 dictAppendWord(dp
, "bye", bye
, FW_DEFAULT
);
888 dictAppendWord(dp
, "forget", forget
, FW_DEFAULT
);
889 dictAppendWord(dp
, "see", see
, FW_DEFAULT
);
890 dictAppendWord(dp
, "words", listWords
, FW_DEFAULT
);
893 ** Set TOOLS environment query values
895 ficlSetEnv(pSys
, "tools", FICL_TRUE
);
896 ficlSetEnv(pSys
, "tools-ext", FICL_FALSE
);
901 dictAppendWord(dp
, "r.s", displayRStack
, FW_DEFAULT
); /* guy carver */
902 dictAppendWord(dp
, ".env", listEnv
, FW_DEFAULT
);
903 dictAppendWord(dp
, "env-constant",
904 envConstant
, FW_DEFAULT
);
905 dictAppendWord(dp
, "env-2constant",
906 env2Constant
, FW_DEFAULT
);
907 dictAppendWord(dp
, "debug-xt", ficlDebugXT
, FW_DEFAULT
);
908 dictAppendWord(dp
, "parse-order",
911 dictAppendWord(dp
, "step-break",stepBreak
, FW_DEFAULT
);
912 dictAppendWord(dp
, "forget-wid",forgetWid
, FW_DEFAULT
);
913 dictAppendWord(dp
, "see-xt", seeXT
, FW_DEFAULT
);