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
);
546 /* Mod tos so that only lines with leading dots remain */
550 unsigned int idx
= 0;
554 while (at (tos
, idx
))
556 /* Every iteration begins at the start of a line. */
557 if (at (tos
, idx
) == '.')
564 while ((c
= at (tos
, idx
)) && c
!= '\n')
578 catchar (&out
, '\t');
585 if (c
== '{' && at (tos
, idx
+ 1) == '*')
587 cattext (&out
, "/*");
590 else if (c
== '*' && at (tos
, idx
+ 1) == '}')
592 cattext (&out
, "*/");
603 catchar (&out
, '\n');
607 idx
= skip_past_newline_1 (tos
, idx
);
611 overwrite_string (tos
, &out
);
615 /* Find lines starting with . and | and put example around them on tos */
620 unsigned int idx
= 0;
625 while (at (tos
, idx
))
627 if (at (tos
, idx
) == '\n'
628 && (at (tos
, idx
+1 ) == '.'
629 || at (tos
, idx
+ 1) == '|'))
631 cattext (&out
, "\n@example\n");
636 while (at (tos
, idx
) && at (tos
, idx
) != '\n')
640 /* We are inside {} parameters of some command;
641 Just pass through until matching brace. */
642 if (at (tos
, idx
) == '{')
644 else if (at (tos
, idx
) == '}')
647 else if (command
!= 0)
649 if (at (tos
, idx
) == '{')
651 else if (!islower ((unsigned char) at (tos
, idx
)))
654 else if (at (tos
, idx
) == '@'
655 && islower ((unsigned char) at (tos
, idx
+ 1)))
659 else if (at (tos
, idx
) == '{' && at (tos
, idx
+ 1) == '*')
661 cattext (&out
, "/*");
665 else if (at (tos
, idx
) == '*' && at (tos
, idx
+ 1) == '}')
667 cattext (&out
, "*/");
671 else if (at (tos
, idx
) == '{'
672 || at (tos
, idx
) == '}')
677 catchar (&out
, at (tos
, idx
));
680 catchar (&out
, '\n');
682 while (at (tos
, idx
) == '\n'
683 && ((at (tos
, idx
+ 1) == '.')
684 || (at (tos
, idx
+ 1) == '|')))
686 cattext (&out
, "@end example");
690 catchar (&out
, at (tos
, idx
));
695 overwrite_string (tos
, &out
);
699 /* Finds any lines starting with "o ", if there are any, then turns
700 on @itemize @bullet, and @items each of them. Then ends with @end
701 itemize, inplace at TOS*/
706 unsigned int idx
= 0;
711 while (at (tos
, idx
))
713 if (at (tos
, idx
) == '@'
714 && at (tos
, idx
+ 1) == '*')
719 else if (at (tos
, idx
) == '\n'
720 && at (tos
, idx
+ 1) == 'o'
721 && isspace ((unsigned char) at (tos
, idx
+ 2)))
725 cattext (&out
, "\n@itemize @bullet\n");
729 cattext (&out
, "\n@item\n");
734 catchar (&out
, at (tos
, idx
));
735 if (on
&& at (tos
, idx
) == '\n'
736 && at (tos
, idx
+ 1) == '\n'
737 && at (tos
, idx
+ 2) != 'o')
739 cattext (&out
, "@end itemize");
748 cattext (&out
, "@end itemize\n");
756 /* Turn <<foo>> into @code{foo} in place at TOS*/
759 do_fancy_stuff (void)
761 unsigned int idx
= 0;
764 while (at (tos
, idx
))
766 if (at (tos
, idx
) == '<'
767 && at (tos
, idx
+ 1) == '<'
768 && !isspace ((unsigned char) at (tos
, idx
+ 2)))
770 /* This qualifies as a << startup. */
772 cattext (&out
, "@code{");
774 && at (tos
, idx
) != '>' )
776 catchar (&out
, at (tos
, idx
));
785 catchar (&out
, at (tos
, idx
));
795 /* A command is all upper case,and alone on a line. */
798 iscommand (string_type
*ptr
, unsigned int idx
)
800 unsigned int len
= 0;
801 while (at (ptr
, idx
))
803 if (isupper ((unsigned char) at (ptr
, idx
))
804 || at (ptr
, idx
) == ' ' || at (ptr
, idx
) == '_')
809 else if (at (ptr
, idx
) == '\n')
822 copy_past_newline (string_type
*ptr
, unsigned int idx
, string_type
*dst
)
826 while (at (ptr
, idx
) && at (ptr
, idx
) != '\n')
828 if (at (ptr
, idx
) == '\t')
830 /* Expand tabs. Neither makeinfo nor TeX can cope well with
834 while (++column
& 7);
838 catchar (dst
, at (ptr
, idx
));
844 catchar (dst
, at (ptr
, idx
));
851 icopy_past_newline (void)
856 idx
= copy_past_newline (ptr
, idx
, tos
);
861 kill_bogus_lines (void)
871 /* Drop leading nl. */
872 while (at (tos
, idx
) == '\n')
878 /* If the first char is a '.' prepend a newline so that it is
879 recognized properly later. */
880 if (at (tos
, idx
) == '.')
881 catchar (&out
, '\n');
883 /* Find the last char. */
884 while (at (tos
, idx
))
889 /* Find the last non white before the nl. */
892 while (idx
&& isspace ((unsigned char) at (tos
, idx
)))
896 /* Copy buffer upto last char, but blank lines before and after
902 if (at (tos
, c
) == '\n'
903 && at (tos
, c
+ 1) == '\n'
904 && at (tos
, c
+ 2) == '.')
906 /* Ignore two newlines before a dot. */
909 else if (at (tos
, c
) == '.' && sl
)
911 /* remember that this line started with a dot. */
914 else if (at (tos
, c
) == '\n'
915 && at (tos
, c
+ 1) == '\n'
919 /* Ignore two newlines when last line was dot. */
922 catchar (&out
, at (tos
, c
));
923 if (at (tos
, c
) == '\n')
940 catchar (&out
, '\n');
948 collapse_whitespace (void)
956 for (idx
= 0; at (tos
, idx
) != 0; ++idx
)
958 char c
= at (tos
, idx
);
980 Take the string at the top of the stack, do some prettying. */
990 while (at (tos
, idx
))
992 switch (at (tos
, idx
))
995 catchar (&out
, '\n');
997 if (tab
&& at (tos
, idx
))
1000 for (i
= 0; i
< tab
- 1; i
+= 2)
1001 catchar (&out
, '\t');
1003 cattext (&out
, " ");
1011 for (i
= 1; i
< tab
- 1; i
+= 2)
1012 catchar (&out
, '\t');
1014 cattext (&out
, " ");
1015 cattext (&out
, " ");
1019 catchar (&out
, '(');
1024 catchar (&out
, ')');
1029 catchar (&out
, at (tos
, idx
));
1037 delete_string (tos
);
1043 get_stuff_in_command (void)
1049 while (at (ptr
, idx
))
1051 if (iscommand (ptr
, idx
))
1053 idx
= copy_past_newline (ptr
, idx
, tos
);
1075 catstr (tos
, tos
- 1);
1084 catstr (tos
, tos
+ 1);
1085 delete_string (tos
+ 1);
1090 skip_past_newline (void)
1092 idx
= skip_past_newline_1 (ptr
, idx
);
1099 if (internal_wanted
== *internal_mode
)
1101 catstr (tos
- 1, tos
);
1103 delete_string (tos
);
1116 catstr (tos
- 1, tos
);
1117 delete_string (tos
);
1124 nextword (char *string
, char **word
)
1133 while (isspace ((unsigned char) *string
) || *string
== '-')
1137 while (*string
&& *string
!= '\n')
1152 word_start
= string
;
1159 if (*string
== '\\')
1165 while (*string
!= '"');
1169 while (!isspace ((unsigned char) *string
))
1177 *word
= xmalloc (length
+ 1);
1182 for (idx
= 0; idx
< length
; idx
++)
1184 if (src
[idx
] == '\\')
1185 switch (src
[idx
+ 1])
1193 *dst
++ = src
[idx
+ 1];
1212 lookup_word (char *word
)
1214 dict_type
*ptr
= root
;
1217 if (strcmp (ptr
->word
, word
) == 0)
1222 fprintf (stderr
, "Can't find %s\n", word
);
1229 dict_type
*ptr
= root
;
1239 for (i
= 0; i
< ptr
->code_end
- 1; i
++)
1240 if (ptr
->code
[i
].f
== push_text
1241 && ptr
->code
[i
+ 1].s
)
1243 free (ptr
->code
[i
+ 1].s
- 1);
1246 else if (ptr
->code
[i
].f
== push_variable
)
1248 free ((void *) ptr
->code
[i
+ 1].l
);
1264 while (at (ptr
, idx
))
1266 /* It's worth looking through the command list. */
1267 if (iscommand (ptr
, idx
))
1272 (void) nextword (addr (ptr
, idx
), &next
);
1274 word
= lookup_word (next
);
1283 fprintf (stderr
, "warning, %s is not recognised\n", next
);
1284 idx
= skip_past_newline_1 (ptr
, idx
);
1289 idx
= skip_past_newline_1 (ptr
, idx
);
1294 newentry (char *word
)
1296 dict_type
*new_d
= xmalloc (sizeof (*new_d
));
1300 new_d
->code
= xmalloc (sizeof (*new_d
->code
));
1301 new_d
->code_length
= 1;
1302 new_d
->code_end
= 0;
1307 add_to_definition (dict_type
*entry
, pcu word
)
1309 if (entry
->code_end
== entry
->code_length
)
1311 entry
->code_length
+= 2;
1312 entry
->code
= xrealloc (entry
->code
,
1313 entry
->code_length
* sizeof (*entry
->code
));
1315 entry
->code
[entry
->code_end
] = word
;
1317 return entry
->code_end
++;
1321 add_intrinsic (char *name
, void (*func
) (void))
1323 dict_type
*new_d
= newentry (xstrdup (name
));
1325 add_to_definition (new_d
, p
);
1327 add_to_definition (new_d
, p
);
1331 add_variable (char *name
, intptr_t *loc
)
1333 dict_type
*new_d
= newentry (name
);
1334 pcu p
= { push_variable
};
1335 add_to_definition (new_d
, p
);
1336 p
.l
= (intptr_t) loc
;
1337 add_to_definition (new_d
, p
);
1339 add_to_definition (new_d
, p
);
1343 add_intrinsic_variable (const char *name
, intptr_t *loc
)
1345 add_variable (xstrdup (name
), loc
);
1349 compile (char *string
)
1351 /* Add words to the dictionary. */
1354 string
= nextword (string
, &word
);
1355 while (string
&& *string
&& word
[0])
1362 /* Compile a word and add to dictionary. */
1364 string
= nextword (string
, &word
);
1367 ptr
= newentry (word
);
1368 string
= nextword (string
, &word
);
1376 while (word
[0] != ';')
1381 /* got a string, embed magic push string
1384 add_to_definition (ptr
, p
);
1386 add_to_definition (ptr
, p
);
1398 /* Got a number, embedd the magic push number
1401 add_to_definition (ptr
, p
);
1403 add_to_definition (ptr
, p
);
1408 add_to_definition (ptr
, p
);
1409 p
.e
= lookup_word (word
);
1410 add_to_definition (ptr
, p
);
1414 string
= nextword (string
, &word
);
1417 add_to_definition (ptr
, p
);
1419 string
= nextword (string
, &word
);
1421 else if (strcmp (word
, "variable") == 0)
1424 string
= nextword (string
, &word
);
1427 intptr_t *loc
= xmalloc (sizeof (intptr_t));
1429 add_variable (word
, loc
);
1430 string
= nextword (string
, &word
);
1434 fprintf (stderr
, "syntax error at %s\n", string
- 1);
1443 *(intptr_t *) ((isp
[0])) = isp
[-1];
1452 isp
[0] = *(intptr_t *) (isp
[0]);
1485 write_buffer (tos
, stdout
);
1487 write_buffer (tos
, stderr
);
1489 fprintf (stderr
, "print: illegal print destination `%" PRIdPTR
"'\n", *isp
);
1498 read_in (string_type
*str
, FILE *file
)
1504 r
= fread (buff
, 1, sizeof (buff
), file
);
1505 catbuf (str
, buff
, r
);
1510 catbuf (str
, buff
, 1);
1516 fprintf (stderr
, "usage: -[d|i|g] <file >file\n");
1520 /* There is no reliable way to declare exit. Sometimes it returns
1521 int, and sometimes it returns void. Sometimes it changes between
1522 OS releases. Trying to get it declared correctly in the hosts file
1523 is a pointless waste of time. */
1532 main (int ac
, char *av
[])
1538 init_string (&buffer
);
1539 init_string (&pptr
);
1540 init_string (stack
+ 0);
1544 add_intrinsic ("push_text", push_text
);
1545 add_intrinsic ("!", bang
);
1546 add_intrinsic ("@", atsign
);
1547 add_intrinsic ("hello", hello
);
1548 add_intrinsic ("stdout", stdout_
);
1549 add_intrinsic ("stderr", stderr_
);
1550 add_intrinsic ("print", print
);
1551 add_intrinsic ("skip_past_newline", skip_past_newline
);
1552 add_intrinsic ("catstr", icatstr
);
1553 add_intrinsic ("copy_past_newline", icopy_past_newline
);
1554 add_intrinsic ("dup", other_dup
);
1555 add_intrinsic ("drop", drop
);
1556 add_intrinsic ("idrop", idrop
);
1557 add_intrinsic ("remchar", remchar
);
1558 add_intrinsic ("get_stuff_in_command", get_stuff_in_command
);
1559 add_intrinsic ("do_fancy_stuff", do_fancy_stuff
);
1560 add_intrinsic ("bulletize", bulletize
);
1561 add_intrinsic ("courierize", courierize
);
1562 /* If the following line gives an error, exit() is not declared in the
1563 ../hosts/foo.h file for this host. Fix it there, not here! */
1564 /* No, don't fix it anywhere; see comment on chew_exit--Ian Taylor. */
1565 add_intrinsic ("exit", chew_exit
);
1566 add_intrinsic ("swap", swap
);
1567 add_intrinsic ("outputdots", outputdots
);
1568 add_intrinsic ("maybecatstr", maybecatstr
);
1569 add_intrinsic ("catstrif", catstrif
);
1570 add_intrinsic ("translatecomments", translatecomments
);
1571 add_intrinsic ("wrap_comment", wrap_comment
);
1572 add_intrinsic ("kill_bogus_lines", kill_bogus_lines
);
1573 add_intrinsic ("indent", indent
);
1574 add_intrinsic ("print_stack_level", print_stack_level
);
1575 add_intrinsic ("strip_trailing_newlines", strip_trailing_newlines
);
1576 add_intrinsic ("collapse_whitespace", collapse_whitespace
);
1578 internal_mode
= xmalloc (sizeof (intptr_t));
1580 add_intrinsic_variable ("internalmode", internal_mode
);
1582 /* Put a nl at the start. */
1583 catchar (&buffer
, '\n');
1585 read_in (&buffer
, stdin
);
1586 remove_noncomments (&buffer
, ptr
);
1587 for (i
= 1; i
< (unsigned int) ac
; i
++)
1589 if (av
[i
][0] == '-')
1591 if (av
[i
][1] == 'f')
1597 f
= fopen (av
[i
+ 1], "r");
1600 fprintf (stderr
, "Can't open the input file %s\n",
1610 else if (av
[i
][1] == 'i')
1612 internal_wanted
= 1;
1614 else if (av
[i
][1] == 'w')
1622 write_buffer (stack
+ 0, stdout
);
1624 delete_string (&pptr
);
1625 delete_string (&buffer
);
1628 fprintf (stderr
, "finishing with current stack level %ld\n",
1629 (long) (tos
- stack
));