2 Copyright (C) 1990-2024 Free Software Foundation, Inc.
3 Contributed by steve chamberlain @cygnus
5 This file is part of BFD, the Binary File Descriptor library.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
20 MA 02110-1301, USA. */
22 /* Yet another way of extracting documentation from source.
23 No, I haven't finished it yet, but I hope you people like it better
28 Basically, this is a sort of string forth, maybe we should call it
31 You define new words thus:
32 : <newword> <oldwords> ;
34 Variables are defined using:
39 /* Primitives provided by the program:
41 Two stacks are provided, a string stack and an integer stack.
43 Internal state variables:
44 internal_wanted - indicates whether `-i' was passed
45 internal_mode - user-settable
49 ! - pop top of integer stack for address, pop next for value; store
50 @ - treat value on integer stack as the address of an integer; push
51 that integer on the integer stack after popping the "address"
52 hello - print "hello\n" to stdout
53 stdout - put stdout marker on TOS
54 stderr - put stderr marker on TOS
55 print - print TOS-1 on TOS (eg: "hello\n" stdout print)
58 copy_past_newline - append input, up to and including newline into TOS
62 remchar - delete last character from TOS
64 do_fancy_stuff - translate <<foo>> to @code{foo} in TOS
65 bulletize - if "o" lines found, prepend @itemize @bullet to TOS
66 and @item to each "o" line; append @end itemize
67 courierize - put @example around . and | lines, translate {* *} { }
70 outputdots - strip out lines without leading dots
71 maybecatstr - do catstr if internal_mode == internal_wanted, discard
73 catstrif - do catstr if top of integer stack is nonzero
74 translatecomments - turn {* and *} into comment delimiters
75 kill_bogus_lines - get rid of extra newlines
77 print_stack_level - print current stack depth to stderr
78 strip_trailing_newlines - go ahead, guess...
79 [quoted string] - push string onto string stack
80 [word starting with digit] - push atol(str) onto integer stack
82 internalmode - the internalmode variable (evaluates to address)
84 A command must be all upper-case, and alone on a line.
99 /* Here is a string type ... */
101 typedef struct buffer
104 unsigned long write_idx
;
108 /* Compiled programs consist of arrays of these. */
113 struct dict_struct
*e
;
118 typedef struct dict_struct
121 struct dict_struct
*next
;
128 intptr_t *internal_mode
;
132 string_type stack
[STACK
];
135 unsigned int idx
= 0; /* Pos in input buffer */
136 string_type
*ptr
; /* and the buffer */
138 intptr_t istack
[STACK
];
139 intptr_t *isp
= &istack
[0];
148 fprintf (stderr
, "%s\n", msg
);
153 xmalloc (size_t size
)
159 newmem
= malloc (size
);
161 die ("out of memory");
167 xrealloc (void *oldmem
, size_t size
)
174 newmem
= malloc (size
);
176 newmem
= realloc (oldmem
, size
);
178 die ("out of memory");
184 xstrdup (const char *s
)
186 size_t len
= strlen (s
) + 1;
187 char *ret
= xmalloc (len
);
188 return memcpy (ret
, s
, len
);
192 init_string_with_size (string_type
*buffer
, unsigned int size
)
194 buffer
->write_idx
= 0;
196 buffer
->ptr
= xmalloc (size
);
200 init_string (string_type
*buffer
)
202 init_string_with_size (buffer
, DEF_SIZE
);
206 find (string_type
*str
, char *what
)
211 for (i
= 0; i
< str
->write_idx
&& *p
; i
++)
213 if (*p
== str
->ptr
[i
])
222 write_buffer (string_type
*buffer
, FILE *f
)
224 if (buffer
->write_idx
!= 0
225 && fwrite (buffer
->ptr
, buffer
->write_idx
, 1, f
) != 1)
226 die ("cannot write output");
230 delete_string (string_type
*buffer
)
237 addr (string_type
*buffer
, unsigned int idx
)
239 return buffer
->ptr
+ idx
;
243 at (string_type
*buffer
, unsigned int pos
)
245 if (pos
>= buffer
->write_idx
)
247 return buffer
->ptr
[pos
];
251 catchar (string_type
*buffer
, int ch
)
253 if (buffer
->write_idx
== buffer
->size
)
256 buffer
->ptr
= xrealloc (buffer
->ptr
, buffer
->size
);
259 buffer
->ptr
[buffer
->write_idx
++] = ch
;
263 overwrite_string (string_type
*dst
, string_type
*src
)
266 dst
->size
= src
->size
;
267 dst
->write_idx
= src
->write_idx
;
272 catbuf (string_type
*buffer
, char *buf
, unsigned int len
)
274 if (buffer
->write_idx
+ len
>= buffer
->size
)
276 while (buffer
->write_idx
+ len
>= buffer
->size
)
278 buffer
->ptr
= xrealloc (buffer
->ptr
, buffer
->size
);
280 memcpy (buffer
->ptr
+ buffer
->write_idx
, buf
, len
);
281 buffer
->write_idx
+= len
;
285 cattext (string_type
*buffer
, char *string
)
287 catbuf (buffer
, string
, (unsigned int) strlen (string
));
291 catstr (string_type
*dst
, string_type
*src
)
293 catbuf (dst
, src
->ptr
, src
->write_idx
);
297 skip_white_and_stars (string_type
*src
, unsigned int idx
)
300 while ((c
= at (src
, idx
)),
301 isspace ((unsigned char) c
)
303 /* Don't skip past end-of-comment or star as first
304 character on its line. */
305 && at (src
, idx
+1) != '/'
306 && at (src
, idx
-1) != '\n'))
312 skip_past_newline_1 (string_type
*ptr
, unsigned int idx
)
315 && at (ptr
, idx
) != '\n')
317 if (at (ptr
, idx
) == '\n')
326 die ("underflow in string stack");
327 if (tos
>= stack
+ STACK
)
328 die ("overflow in string stack");
335 die ("underflow in integer stack");
336 if (isp
>= istack
+ STACK
)
337 die ("overflow in integer stack");
345 delete_string (tos
+ 1);
358 exec (dict_type
*word
)
369 dict_type
*e
= pc
[1].e
;
383 strip_trailing_newlines (void)
385 while (tos
->write_idx
> 0
386 && (isspace ((unsigned char) at (tos
, tos
->write_idx
- 1))
387 || at (tos
, tos
->write_idx
- 1) == '\n'))
402 /* This is a wrapper for push_number just so we can correctly free the
403 variable at the end. */
417 cattext (tos
, pc
->s
);
421 /* This function removes everything not inside comments starting on
422 the first char of the line from the string, also when copying
423 comments, removes blank space and leading *'s.
424 Blank lines are turned into one blank line. */
427 remove_noncomments (string_type
*src
, string_type
*dst
)
429 unsigned int idx
= 0;
431 while (at (src
, idx
))
433 /* Now see if we have a comment at the start of the line. */
434 if (at (src
, idx
) == '\n'
435 && at (src
, idx
+ 1) == '/'
436 && at (src
, idx
+ 2) == '*')
440 idx
= skip_white_and_stars (src
, idx
);
442 /* Remove leading dot */
443 if (at (src
, idx
) == '.')
446 /* Copy to the end of the line, or till the end of the
448 while (at (src
, idx
))
450 if (at (src
, idx
) == '\n')
452 /* end of line, echo and scrape of leading blanks */
453 if (at (src
, idx
+ 1) == '\n')
457 idx
= skip_white_and_stars (src
, idx
);
459 else if (at (src
, idx
) == '*' && at (src
, idx
+ 1) == '/')
462 cattext (dst
, "\nENDDD\n");
467 catchar (dst
, at (src
, idx
));
478 print_stack_level (void)
480 fprintf (stderr
, "current string stack depth = %ld, ",
481 (long) (tos
- stack
));
482 fprintf (stderr
, "current integer stack depth = %ld\n",
483 (long) (isp
- istack
));
488 and *} into comments */
491 translatecomments (void)
493 unsigned int idx
= 0;
497 while (at (tos
, idx
))
499 if (at (tos
, idx
) == '{' && at (tos
, idx
+ 1) == '*')
501 cattext (&out
, "/*");
504 else if (at (tos
, idx
) == '*' && at (tos
, idx
+ 1) == '}')
506 cattext (&out
, "*/");
511 catchar (&out
, at (tos
, idx
));
516 overwrite_string (tos
, &out
);
521 /* Wrap tos-1 as a C comment, indenting by tos. */
530 cattext (&out
, "/* ");
531 for (unsigned int idx
= 0; at (tos
- 1, idx
); idx
++)
533 catchar (&out
, at (tos
- 1, idx
));
534 if (at (tos
- 1, idx
) == '\n' && at (tos
- 1, idx
+ 1) != '\n')
540 cattext (&out
, " */");
542 overwrite_string (tos
- 1, &out
);
548 /* Mod tos so that only lines with leading dots remain */
552 unsigned int idx
= 0;
556 while (at (tos
, idx
))
558 /* Every iteration begins at the start of a line. */
559 if (at (tos
, idx
) == '.')
566 while ((c
= at (tos
, idx
)) && c
!= '\n')
580 catchar (&out
, '\t');
587 if (c
== '{' && at (tos
, idx
+ 1) == '*')
589 cattext (&out
, "/*");
592 else if (c
== '*' && at (tos
, idx
+ 1) == '}')
594 cattext (&out
, "*/");
605 catchar (&out
, '\n');
609 idx
= skip_past_newline_1 (tos
, idx
);
613 overwrite_string (tos
, &out
);
617 /* Find lines starting with . and | and put example around them on tos */
622 unsigned int idx
= 0;
627 while (at (tos
, idx
))
629 if (at (tos
, idx
) == '\n'
630 && (at (tos
, idx
+1 ) == '.'
631 || at (tos
, idx
+ 1) == '|'))
633 cattext (&out
, "\n@example\n");
638 while (at (tos
, idx
) && at (tos
, idx
) != '\n')
642 /* We are inside {} parameters of some command;
643 Just pass through until matching brace. */
644 if (at (tos
, idx
) == '{')
646 else if (at (tos
, idx
) == '}')
649 else if (command
!= 0)
651 if (at (tos
, idx
) == '{')
653 else if (!islower ((unsigned char) at (tos
, idx
)))
656 else if (at (tos
, idx
) == '@'
657 && islower ((unsigned char) at (tos
, idx
+ 1)))
661 else if (at (tos
, idx
) == '{' && at (tos
, idx
+ 1) == '*')
663 cattext (&out
, "/*");
667 else if (at (tos
, idx
) == '*' && at (tos
, idx
+ 1) == '}')
669 cattext (&out
, "*/");
673 else if (at (tos
, idx
) == '{'
674 || at (tos
, idx
) == '}')
679 catchar (&out
, at (tos
, idx
));
682 catchar (&out
, '\n');
684 while (at (tos
, idx
) == '\n'
685 && ((at (tos
, idx
+ 1) == '.')
686 || (at (tos
, idx
+ 1) == '|')))
688 cattext (&out
, "@end example");
692 catchar (&out
, at (tos
, idx
));
697 overwrite_string (tos
, &out
);
701 /* Finds any lines starting with "o ", if there are any, then turns
702 on @itemize @bullet, and @items each of them. Then ends with @end
703 itemize, inplace at TOS*/
708 unsigned int idx
= 0;
713 while (at (tos
, idx
))
715 if (at (tos
, idx
) == '@'
716 && at (tos
, idx
+ 1) == '*')
721 else if (at (tos
, idx
) == '\n'
722 && at (tos
, idx
+ 1) == 'o'
723 && isspace ((unsigned char) at (tos
, idx
+ 2)))
727 cattext (&out
, "\n@itemize @bullet\n");
731 cattext (&out
, "\n@item\n");
736 catchar (&out
, at (tos
, idx
));
737 if (on
&& at (tos
, idx
) == '\n'
738 && at (tos
, idx
+ 1) == '\n'
739 && at (tos
, idx
+ 2) != 'o')
741 cattext (&out
, "@end itemize");
750 cattext (&out
, "@end itemize\n");
758 /* Turn <<foo>> into @code{foo} in place at TOS*/
761 do_fancy_stuff (void)
763 unsigned int idx
= 0;
766 while (at (tos
, idx
))
768 if (at (tos
, idx
) == '<'
769 && at (tos
, idx
+ 1) == '<'
770 && !isspace ((unsigned char) at (tos
, idx
+ 2)))
772 /* This qualifies as a << startup. */
774 cattext (&out
, "@code{");
776 && at (tos
, idx
) != '>' )
778 catchar (&out
, at (tos
, idx
));
787 catchar (&out
, at (tos
, idx
));
797 /* A command is all upper case,and alone on a line. */
800 iscommand (string_type
*ptr
, unsigned int idx
)
802 unsigned int len
= 0;
803 while (at (ptr
, idx
))
805 if (isupper ((unsigned char) at (ptr
, idx
))
806 || at (ptr
, idx
) == ' ' || at (ptr
, idx
) == '_')
811 else if (at (ptr
, idx
) == '\n')
824 copy_past_newline (string_type
*ptr
, unsigned int idx
, string_type
*dst
)
828 while (at (ptr
, idx
) && at (ptr
, idx
) != '\n')
830 if (at (ptr
, idx
) == '\t')
832 /* Expand tabs. Neither makeinfo nor TeX can cope well with
836 while (++column
& 7);
840 catchar (dst
, at (ptr
, idx
));
846 catchar (dst
, at (ptr
, idx
));
853 icopy_past_newline (void)
858 idx
= copy_past_newline (ptr
, idx
, tos
);
863 kill_bogus_lines (void)
873 /* Drop leading nl. */
874 while (at (tos
, idx
) == '\n')
880 /* If the first char is a '.' prepend a newline so that it is
881 recognized properly later. */
882 if (at (tos
, idx
) == '.')
883 catchar (&out
, '\n');
885 /* Find the last char. */
886 while (at (tos
, idx
))
891 /* Find the last non white before the nl. */
894 while (idx
&& isspace ((unsigned char) at (tos
, idx
)))
898 /* Copy buffer upto last char, but blank lines before and after
904 if (at (tos
, c
) == '\n'
905 && at (tos
, c
+ 1) == '\n'
906 && at (tos
, c
+ 2) == '.')
908 /* Ignore two newlines before a dot. */
911 else if (at (tos
, c
) == '.' && sl
)
913 /* remember that this line started with a dot. */
916 else if (at (tos
, c
) == '\n'
917 && at (tos
, c
+ 1) == '\n'
921 /* Ignore two newlines when last line was dot. */
924 catchar (&out
, at (tos
, c
));
925 if (at (tos
, c
) == '\n')
942 catchar (&out
, '\n');
950 collapse_whitespace (void)
958 for (idx
= 0; at (tos
, idx
) != 0; ++idx
)
960 char c
= at (tos
, idx
);
982 Take the string at the top of the stack, do some prettying. */
992 while (at (tos
, idx
))
994 switch (at (tos
, idx
))
997 catchar (&out
, '\n');
999 if (tab
&& at (tos
, idx
))
1002 for (i
= 0; i
< tab
- 1; i
+= 2)
1003 catchar (&out
, '\t');
1005 cattext (&out
, " ");
1013 for (i
= 1; i
< tab
- 1; i
+= 2)
1014 catchar (&out
, '\t');
1016 cattext (&out
, " ");
1017 cattext (&out
, " ");
1021 catchar (&out
, '(');
1026 catchar (&out
, ')');
1031 catchar (&out
, at (tos
, idx
));
1039 delete_string (tos
);
1045 get_stuff_in_command (void)
1051 while (at (ptr
, idx
))
1053 if (iscommand (ptr
, idx
))
1055 idx
= copy_past_newline (ptr
, idx
, tos
);
1077 catstr (tos
, tos
- 1);
1086 catstr (tos
, tos
+ 1);
1087 delete_string (tos
+ 1);
1092 skip_past_newline (void)
1094 idx
= skip_past_newline_1 (ptr
, idx
);
1101 if (internal_wanted
== *internal_mode
)
1103 catstr (tos
- 1, tos
);
1105 delete_string (tos
);
1118 catstr (tos
- 1, tos
);
1119 delete_string (tos
);
1126 nextword (char *string
, char **word
)
1135 while (isspace ((unsigned char) *string
) || *string
== '-')
1139 while (*string
&& *string
!= '\n')
1154 word_start
= string
;
1161 if (*string
== '\\')
1167 while (*string
!= '"');
1171 while (!isspace ((unsigned char) *string
))
1179 *word
= xmalloc (length
+ 1);
1184 for (idx
= 0; idx
< length
; idx
++)
1186 if (src
[idx
] == '\\')
1187 switch (src
[idx
+ 1])
1195 *dst
++ = src
[idx
+ 1];
1214 lookup_word (char *word
)
1216 dict_type
*ptr
= root
;
1219 if (strcmp (ptr
->word
, word
) == 0)
1224 fprintf (stderr
, "Can't find %s\n", word
);
1231 dict_type
*ptr
= root
;
1241 for (i
= 0; i
< ptr
->code_end
- 1; i
++)
1242 if (ptr
->code
[i
].f
== push_text
1243 && ptr
->code
[i
+ 1].s
)
1245 free (ptr
->code
[i
+ 1].s
- 1);
1248 else if (ptr
->code
[i
].f
== push_variable
)
1250 free ((void *) ptr
->code
[i
+ 1].l
);
1266 while (at (ptr
, idx
))
1268 /* It's worth looking through the command list. */
1269 if (iscommand (ptr
, idx
))
1274 (void) nextword (addr (ptr
, idx
), &next
);
1276 word
= lookup_word (next
);
1285 fprintf (stderr
, "warning, %s is not recognised\n", next
);
1286 idx
= skip_past_newline_1 (ptr
, idx
);
1291 idx
= skip_past_newline_1 (ptr
, idx
);
1296 newentry (char *word
)
1298 dict_type
*new_d
= xmalloc (sizeof (*new_d
));
1302 new_d
->code
= xmalloc (sizeof (*new_d
->code
));
1303 new_d
->code_length
= 1;
1304 new_d
->code_end
= 0;
1309 add_to_definition (dict_type
*entry
, pcu word
)
1311 if (entry
->code_end
== entry
->code_length
)
1313 entry
->code_length
+= 2;
1314 entry
->code
= xrealloc (entry
->code
,
1315 entry
->code_length
* sizeof (*entry
->code
));
1317 entry
->code
[entry
->code_end
] = word
;
1319 return entry
->code_end
++;
1323 add_intrinsic (char *name
, void (*func
) (void))
1325 dict_type
*new_d
= newentry (xstrdup (name
));
1327 add_to_definition (new_d
, p
);
1329 add_to_definition (new_d
, p
);
1333 add_variable (char *name
, intptr_t *loc
)
1335 dict_type
*new_d
= newentry (name
);
1336 pcu p
= { push_variable
};
1337 add_to_definition (new_d
, p
);
1338 p
.l
= (intptr_t) loc
;
1339 add_to_definition (new_d
, p
);
1341 add_to_definition (new_d
, p
);
1345 add_intrinsic_variable (const char *name
, intptr_t *loc
)
1347 add_variable (xstrdup (name
), loc
);
1351 compile (char *string
)
1353 /* Add words to the dictionary. */
1356 string
= nextword (string
, &word
);
1357 while (string
&& *string
&& word
[0])
1364 /* Compile a word and add to dictionary. */
1366 string
= nextword (string
, &word
);
1369 ptr
= newentry (word
);
1370 string
= nextword (string
, &word
);
1378 while (word
[0] != ';')
1383 /* got a string, embed magic push string
1386 add_to_definition (ptr
, p
);
1388 add_to_definition (ptr
, p
);
1400 /* Got a number, embedd the magic push number
1403 add_to_definition (ptr
, p
);
1405 add_to_definition (ptr
, p
);
1410 add_to_definition (ptr
, p
);
1411 p
.e
= lookup_word (word
);
1412 add_to_definition (ptr
, p
);
1416 string
= nextword (string
, &word
);
1419 add_to_definition (ptr
, p
);
1421 string
= nextword (string
, &word
);
1423 else if (strcmp (word
, "variable") == 0)
1426 string
= nextword (string
, &word
);
1429 intptr_t *loc
= xmalloc (sizeof (intptr_t));
1431 add_variable (word
, loc
);
1432 string
= nextword (string
, &word
);
1436 fprintf (stderr
, "syntax error at %s\n", string
- 1);
1445 *(intptr_t *) ((isp
[0])) = isp
[-1];
1454 isp
[0] = *(intptr_t *) (isp
[0]);
1487 write_buffer (tos
, stdout
);
1489 write_buffer (tos
, stderr
);
1491 fprintf (stderr
, "print: illegal print destination `%" PRIdPTR
"'\n", *isp
);
1500 read_in (string_type
*str
, FILE *file
)
1506 r
= fread (buff
, 1, sizeof (buff
), file
);
1507 catbuf (str
, buff
, r
);
1512 catbuf (str
, buff
, 1);
1518 fprintf (stderr
, "usage: -[d|i|g] <file >file\n");
1522 /* There is no reliable way to declare exit. Sometimes it returns
1523 int, and sometimes it returns void. Sometimes it changes between
1524 OS releases. Trying to get it declared correctly in the hosts file
1525 is a pointless waste of time. */
1534 main (int ac
, char *av
[])
1540 init_string (&buffer
);
1541 init_string (&pptr
);
1542 init_string (stack
+ 0);
1546 add_intrinsic ("push_text", push_text
);
1547 add_intrinsic ("!", bang
);
1548 add_intrinsic ("@", atsign
);
1549 add_intrinsic ("hello", hello
);
1550 add_intrinsic ("stdout", stdout_
);
1551 add_intrinsic ("stderr", stderr_
);
1552 add_intrinsic ("print", print
);
1553 add_intrinsic ("skip_past_newline", skip_past_newline
);
1554 add_intrinsic ("catstr", icatstr
);
1555 add_intrinsic ("copy_past_newline", icopy_past_newline
);
1556 add_intrinsic ("dup", other_dup
);
1557 add_intrinsic ("drop", drop
);
1558 add_intrinsic ("idrop", idrop
);
1559 add_intrinsic ("remchar", remchar
);
1560 add_intrinsic ("get_stuff_in_command", get_stuff_in_command
);
1561 add_intrinsic ("do_fancy_stuff", do_fancy_stuff
);
1562 add_intrinsic ("bulletize", bulletize
);
1563 add_intrinsic ("courierize", courierize
);
1564 /* If the following line gives an error, exit() is not declared in the
1565 ../hosts/foo.h file for this host. Fix it there, not here! */
1566 /* No, don't fix it anywhere; see comment on chew_exit--Ian Taylor. */
1567 add_intrinsic ("exit", chew_exit
);
1568 add_intrinsic ("swap", swap
);
1569 add_intrinsic ("outputdots", outputdots
);
1570 add_intrinsic ("maybecatstr", maybecatstr
);
1571 add_intrinsic ("catstrif", catstrif
);
1572 add_intrinsic ("translatecomments", translatecomments
);
1573 add_intrinsic ("wrap_comment", wrap_comment
);
1574 add_intrinsic ("kill_bogus_lines", kill_bogus_lines
);
1575 add_intrinsic ("indent", indent
);
1576 add_intrinsic ("print_stack_level", print_stack_level
);
1577 add_intrinsic ("strip_trailing_newlines", strip_trailing_newlines
);
1578 add_intrinsic ("collapse_whitespace", collapse_whitespace
);
1580 internal_mode
= xmalloc (sizeof (intptr_t));
1582 add_intrinsic_variable ("internalmode", internal_mode
);
1584 /* Put a nl at the start. */
1585 catchar (&buffer
, '\n');
1587 read_in (&buffer
, stdin
);
1588 remove_noncomments (&buffer
, ptr
);
1589 for (i
= 1; i
< (unsigned int) ac
; i
++)
1591 if (av
[i
][0] == '-')
1593 if (av
[i
][1] == 'f')
1599 f
= fopen (av
[i
+ 1], "r");
1602 fprintf (stderr
, "Can't open the input file %s\n",
1612 else if (av
[i
][1] == 'i')
1614 internal_wanted
= 1;
1616 else if (av
[i
][1] == 'w')
1624 write_buffer (stack
+ 0, stdout
);
1626 delete_string (&pptr
);
1627 delete_string (&buffer
);
1630 fprintf (stderr
, "finishing with current stack level %ld\n",
1631 (long) (tos
- stack
));