added concrete implementations of putc(), getc(), getchar() and gets()
[tangerine.git] / rom / dos / readargs.c
bloba6c6a42762b7abaf334ad6680bf55be92a97d279
2 /*
3 Copyright © 1995-2007, The AROS Development Team. All rights reserved.
4 $Id$
6 Desc:
7 Lang: english
8 */
9 #include <exec/memory.h>
10 #include <proto/exec.h>
11 #include <dos/rdargs.h>
12 #include <dos/dosextens.h>
14 #include "dos_intern.h"
16 #include <aros/debug.h>
18 #ifdef TEST
19 # include <stdio.h>
20 # include <proto/dos.h>
21 # undef ReadArgs
22 # undef AROS_LH3
23 # define AROS_LH3(t,fn,a1,a2,a3,bt,bn,o,lib) t fn (a1,a2,a3)
24 # undef AROS_LHA
25 # define AROS_LHA(t,n,r) t n
26 # undef AROS_LIBFUNC_INIT
27 # define AROS_LIBFUNC_INIT
28 # undef AROS_LIBFUNC_EXIT
29 # define AROS_LIBFUNC_EXIT
30 #endif
32 /*****************************************************************************
34 NAME */
35 #include <proto/dos.h>
37 AROS_LH3(struct RDArgs *, ReadArgs,
39 /* SYNOPSIS */
40 AROS_LHA(CONST_STRPTR, template, D1),
41 AROS_LHA(IPTR *, array, D2),
42 AROS_LHA(struct RDArgs *, rdargs, D3),
44 /* LOCATION */
45 struct DosLibrary *, DOSBase, 133, Dos)
47 /* FUNCTION
48 Parses the commandline, a given string or Input() and fills
49 an argument array according to the options template given.
50 The array must be initialized to the wanted defaults before
51 each call to ReadArgs(). If the rdargs argument is NULL
52 ReadArgs() tries to parse the commandline and continues
53 on the input channel if it just consists of a single '?',
54 prompting the user for input.
56 INPUTS
57 template - Template string. The template string is given as
58 a number of options separated by ',' and modified
59 by '/' modifiers, e.g. 'NAME,WIDTH/N,HEIGHT/N'
60 means get a name string and two numbers (width and
61 height). The possible modifiers are:
62 /S Option is a switch. It may be either set or
63 left out.
64 /T Option is a boolean value. Requires an argument
65 which may be "ON", "YES" (setting the respective
66 argument to 1), "OFF" or "NO" (setting the
67 respective argument to 0).
68 /N Option is a number. Strings are not allowed.
69 If the option is optional, a pointer to the
70 actual number is returned. This is how you know
71 if it was really given.
72 /A Argument is required. If it is left out ReadArgs()
73 fails.
74 /K The keyword must be given when filling the option.
75 Normally it's skipped.
76 /M Multiple strings. The result is returned as a string
77 pointer array terminated with NULL. /M eats all strings
78 that don't fit into any other option. If there are
79 unfilled /A arguments after parsing they steal strings
80 from /M. This makes it possible to e.g. write a COPY
81 template like 'FROM/A/M,TO/A'. There may be only one
82 /M option in a template.
83 /F Eats the rest of the line even if there are option
84 keywords in it.
85 array - Array to be filled with the result values. The array must
86 be intialized to the default values before calling
87 ReadArgs().
88 rdargs - An optional RDArgs structure determinating the type of
89 input to process.
91 RESULT
92 A handle for the memory allocated by ReadArgs(). Must be freed
93 with FreeArgs() later.
95 SEE ALSO
96 FreeArgs(), Input()
98 *****************************************************************************/
100 AROS_LIBFUNC_INIT
102 /* Allocated resources */
103 struct DAList *dalist = NULL;
104 UBYTE *flags = NULL;
105 STRPTR strbuf = NULL, iline = NULL;
106 STRPTR *multvec = NULL, *argbuf = NULL;
107 ULONG multnum = 0, multmax = 0;
109 /* Some variables */
110 CONST_STRPTR cs1;
111 STRPTR s1, s2, *newmult;
112 ULONG arg, numargs, nextarg;
113 LONG it, item, chars, value;
114 struct CSource lcs, *cs;
116 ASSERT_VALID_PTR(template);
117 ASSERT_VALID_PTR(array);
118 ASSERT_VALID_PTR_OR_NULL(rdargs);
120 /* Get pointer to process structure. */
121 struct Process *me = (struct Process *) FindTask(NULL);
123 /* Error recovery. C has no exceptions. This is a simple replacement. */
124 LONG error;
126 #undef ERROR
127 #define ERROR(a) { error=a; goto end; }
129 /* Template options */
130 #define REQUIRED 0x80 /* /A */
131 #define KEYWORD 0x40 /* /K */
132 #define MULTIPLE 0x20 /* /M */
133 #define TYPEMASK 0x07
134 #define NORMAL 0x00 /* No option */
135 #define SWITCH 0x01 /* /S, implies /K */
136 #define TOGGLE 0x02 /* /T, implies /K */
137 #define NUMERIC 0x03 /* /N */
138 #define REST 0x04 /* /F */
140 /* Flags for each possible character. */
141 static const UBYTE argflags[] =
143 REQUIRED, 0, 0, 0, 0, REST, 0, 0, 0, 0, KEYWORD, 0, MULTIPLE,
144 NUMERIC, 0, 0, 0, 0, SWITCH | KEYWORD, TOGGLE | KEYWORD, 0, 0,
145 0, 0, 0, 0
148 /* Allocate readargs structure (and private internal one) */
149 if (!rdargs)
151 rdargs = (struct RDArgs *) AllocVec(sizeof(struct RDArgs),
152 MEMF_ANY | MEMF_CLEAR);
153 if (rdargs)
155 rdargs->RDA_Flags |= RDAF_ALLOCATED_BY_READARGS;
159 dalist = (struct DAList *) AllocVec(sizeof(struct DAList),
160 MEMF_ANY | MEMF_CLEAR);
162 if (rdargs == NULL || dalist == NULL)
164 ERROR(ERROR_NO_FREE_STORE);
167 /* Init character source. */
168 if (rdargs->RDA_Source.CS_Buffer)
170 cs = &rdargs->RDA_Source;
172 else
174 lcs.CS_Buffer = (me->pr_Arguments ? me->pr_Arguments : (UBYTE *) "");
176 cs1 = lcs.CS_Buffer;
178 while (*cs1++)
182 lcs.CS_Length = (IPTR) cs1 - (IPTR) lcs.CS_Buffer - 1;
183 lcs.CS_CurChr = 0;
185 cs = &lcs;
188 /* Check for optional reprompting */
189 if (!(rdargs->RDA_Flags & RDAF_NOPROMPT))
191 /* Check commandline for a single '?' */
192 cs1 = cs->CS_Buffer;
194 /* Skip leading whitespace */
195 while (*cs1 == ' ' || *cs1 == '\t')
197 cs1++;
200 /* Check for '?' */
201 if (*cs1++ == '?')
203 /* Skip whitespace */
204 while (*cs1 == ' ' || *cs1 == '\t')
206 cs1++;
209 /* Check for EOL */
210 if (*cs1 == '\n' || !*cs1)
212 /* Only a single '?' on the commandline. */
213 BPTR input = Input();
214 BPTR output = Output();
215 ULONG isize = 0, ibuf = 0;
216 LONG c;
218 /* Prompt for more input */
220 /* printf ("Only ? found\n");
221 printf ("rdargs=%p\n", rdargs);
222 if (rdargs)
223 printf ("rdargs->RDA_ExtHelp=%p\n", rdargs->RDA_ExtHelp); */
225 if (rdargs->RDA_ExtHelp != NULL)
227 if (FPuts(output, rdargs->RDA_ExtHelp))
228 ERROR(me->pr_Result2);
231 if (FPuts(output, template) || FPuts(output, ": "))
233 ERROR(me->pr_Result2);
236 if (!Flush(output))
238 ERROR(me->pr_Result2);
241 /* Read a line in. */
242 for (;;)
244 if (isize >= ibuf)
246 /* Buffer too small. Get a new one. */
247 STRPTR newiline;
249 ibuf += 256;
251 newiline = (STRPTR) AllocVec(ibuf, MEMF_ANY);
253 if (newiline == NULL)
255 ERROR(ERROR_NO_FREE_STORE);
258 CopyMemQuick((ULONG *) iline,
259 (ULONG *) newiline, isize);
261 FreeVec(iline);
263 iline = newiline;
266 /* Read character */
267 c = FGetC(input);
269 /* Check and write it. */
270 if (c == EOF && me->pr_Result2)
272 ERROR(me->pr_Result2);
275 if (c == EOF || c == '\n' || !c)
277 /* stegerg: added this. Otherwise try "list ?" then enter only "l" + RETURN
278 * and you will get a broken wall in FreeMem reported. This happens in
279 * FreeArgs() during the FreeVec() of the StrBuf. Appending '\n' here fixes
280 * this, but maybe the real bug is somewhere else. */
282 iline[isize++] = '\n';
284 /* end stegerg: */
286 break;
289 iline[isize++] = c;
292 /* Prepare input source for new line. */
293 cs->CS_Buffer = iline;
294 cs->CS_Length = isize;
300 * Get enough space for string buffer.
301 * It's always smaller than the size of the input line+1.
304 strbuf = (STRPTR) AllocVec(cs->CS_Length + 1, MEMF_ANY);
306 if (strbuf == NULL)
308 ERROR(ERROR_NO_FREE_STORE);
311 /* Count the number of items in the template (number of ','+1). */
312 numargs = 1;
313 cs1 = template;
315 while (*cs1)
317 if (*cs1++ == ',')
319 numargs++;
323 /* Use this count to get space for temporary flag array and result
324 * buffer. */
325 flags = (UBYTE *) AllocVec(numargs + 1, MEMF_CLEAR);
327 argbuf = (STRPTR *) AllocVec((numargs + 1) * sizeof(STRPTR), MEMF_CLEAR);
329 if (flags == NULL || argbuf == NULL)
331 ERROR(ERROR_NO_FREE_STORE);
334 /* Fill the flag array. */
335 cs1 = template;
336 s2 = flags;
338 while (*cs1)
340 /* A ',' means: goto next item. */
341 if (*cs1 == ',')
343 s2++;
346 /* In case of a '/' use the next character as option. */
347 if (*cs1++ == '/')
349 *s2 |= argflags[*cs1 - 'A'];
353 /* Add a dummy so that the whole line is processed. */
354 *++s2 = MULTIPLE;
357 * Now process commandline for the first time:
358 * Go from left to right and fill all items that need filling.
359 * If an item is given as 'OPTION=VALUE' or 'OPTION VALUE' fill
360 * it out of turn.
362 s1 = strbuf;
364 for (arg = 0; arg <= numargs; arg = nextarg)
366 nextarg = arg + 1;
368 /* Skip /K options and options that are already done. */
369 if (flags[arg] & KEYWORD || argbuf[arg] != NULL)
371 continue;
374 #if 0 /* stegerg: if so a template of CLOSE/S,QUICK/S,COMMAND/F would
375 not work correctly if command line for example is
376 "CLOSE QUICK" it would all end up being eaten by COMMAND/F
377 argument */
379 /* If the current option is of type /F do not look for keywords */
380 if ((flags[arg] & TYPEMASK) != REST)
381 #endif
384 /* Get item. Quoted items are no keywords. */
385 it = ReadItem(s1, ~0ul / 2, cs);
387 if (it == ITEM_UNQUOTED)
389 /* Not quoted. Check if it's a keyword. */
390 item = FindArg(template, s1);
392 if (item >= 0 && item < numargs && argbuf[item] == NULL)
395 * It's a keyword. Fill it and retry the current option
396 * at the next turn
398 nextarg = arg;
399 arg = item;
401 /* /S /T may not be given as 'OPTION=VALUE'. */
402 if ((flags[item] & TYPEMASK) != SWITCH
403 && (flags[item] & TYPEMASK) != TOGGLE)
405 /* Get value. */
406 it = ReadItem(s1, ~0ul / 2, cs);
408 if (it == ITEM_EQUAL)
410 it = ReadItem(s1, ~0ul / 2, cs);
416 /* Check returncode of ReadItem(). */
417 if (it == ITEM_EQUAL)
419 ERROR(ERROR_BAD_TEMPLATE);
421 else if (it == ITEM_ERROR)
423 ERROR(me->pr_Result2);
425 else if (it == ITEM_NOTHING)
427 break;
431 /* /F takes all the rest */
432 /* TODO: Take care of quoted strings(?) */
433 if ((flags[arg] & TYPEMASK) == REST)
435 #if 0
436 /* Skip leading whitespace */
437 while (cs->CS_CurChr < cs->CS_Length
438 && (cs->CS_Buffer[cs->CS_CurChr] == ' '
439 || cs->CS_Buffer[cs->CS_CurChr] == '\t'))
441 cs->CS_CurChr++;
443 #endif
444 argbuf[arg] = s1;
446 /* Copy part already read above by ReadItem() */
447 while (*s1)
449 s1++;
452 /* Find the last non-whitespace character */
453 s2 = s1 - 1;
455 while (cs->CS_CurChr < cs->CS_Length
456 && cs->CS_Buffer[cs->CS_CurChr]
457 && cs->CS_Buffer[cs->CS_CurChr] != '\n')
459 if (cs->CS_Buffer[cs->CS_CurChr] != ' '
460 && cs->CS_Buffer[cs->CS_CurChr] != '\t')
462 s2 = s1;
465 /* Copy string by the way. */
466 *s1++ = cs->CS_Buffer[cs->CS_CurChr++];
469 /* Add terminator (1 after the character found). */
470 s2[1] = 0;
471 it = ITEM_NOTHING;
472 break;
475 if (flags[arg] & MULTIPLE)
477 /* All /M arguments are stored in a buffer. */
478 if (multnum >= multmax)
480 /* Buffer too small. Get a new one. */
481 multmax += 16;
483 newmult = (STRPTR *) AllocVec(multmax * sizeof(char *),
484 MEMF_ANY);
485 if (newmult == NULL)
487 ERROR(ERROR_NO_FREE_STORE);
490 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
491 multnum * sizeof(char *));
493 FreeVec(multvec);
495 multvec = newmult;
498 /* Put string into the buffer. */
499 multvec[multnum++] = s1;
501 while (*s1++)
505 /* /M takes more than one argument, so retry. */
506 nextarg = arg;
508 else if ((flags[arg] & TYPEMASK) == SWITCH
509 || (flags[arg] & TYPEMASK) == TOGGLE)
511 /* /S or /T just set a flag */
512 argbuf[arg] = (char *) ~0;
514 else /* NORMAL || NUMERIC */
516 /* Put argument into argument buffer. */
517 argbuf[arg] = s1;
519 while (*s1++)
525 /* Unfilled /A options steal Arguments from /M */
526 for (arg = numargs; arg-- > 0;)
528 if (flags[arg] & REQUIRED && argbuf[arg] == NULL
529 && !(flags[arg] & MULTIPLE))
531 if (flags[arg] & KEYWORD)
533 /* /K/A argument, which inisits on keyword
534 * being used, cannot be satisfied */
536 ERROR(ERROR_TOO_MANY_ARGS); /* yes, strange error number,
537 * but it translates to "wrong
538 * number of arguments" */
542 if (!multnum)
544 /* No arguments left? Oh dear! */
545 ERROR(ERROR_REQUIRED_ARG_MISSING);
548 argbuf[arg] = multvec[--multnum];
552 /* Put the rest of /M where it belongs */
553 for (arg = 0; arg < numargs; arg++)
555 if (flags[arg] & MULTIPLE)
557 if (flags[arg] & REQUIRED && !multnum)
559 ERROR(ERROR_REQUIRED_ARG_MISSING);
562 if (multnum)
564 /* NULL terminate it. */
565 if (multnum >= multmax)
567 multmax += 16;
569 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
570 MEMF_ANY);
572 if (newmult == NULL)
574 ERROR(ERROR_NO_FREE_STORE);
577 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
578 multnum * sizeof(char *));
580 FreeVec(multvec);
582 multvec = newmult;
585 multvec[multnum++] = NULL;
586 argbuf[arg] = (STRPTR) multvec;
588 else
590 /* Shouldn't be necessary, but some buggy software relies on this */
591 argbuf[arg] = NULL;
594 break;
598 /* There are some arguments left? Return error. */
599 if (multnum && arg == numargs)
601 ERROR(ERROR_TOO_MANY_ARGS);
605 * The commandline is processed now. Put the results in the result array.
606 * Convert /N arguments by the way.
608 for (arg = 0; arg < numargs; arg++)
610 /* Just for the arguments given. */
611 if (argbuf[arg] != NULL)
613 if (flags[arg] & MULTIPLE)
615 array[arg] = (IPTR) argbuf[arg];
617 if ((flags[arg] & TYPEMASK) == NUMERIC)
619 STRPTR *p;
620 SIPTR *q;
622 if (multnum * 2 > multmax)
624 multmax = multnum * 2;
625 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
626 MEMF_ANY);
628 if (newmult == NULL)
630 ERROR(ERROR_NO_FREE_STORE);
633 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
634 multnum * sizeof(char *));
636 FreeVec(multvec);
638 multvec = newmult;
641 array[arg] = (IPTR) multvec;
642 p = multvec;
643 q = (SIPTR *) (multvec + multnum);
645 while (*p)
647 /* Convert /N argument. */
648 chars = StrToLong(*p, q);
650 if (chars <= 0 || (*p)[chars])
652 /* Conversion failed. */
653 ERROR(ERROR_BAD_NUMBER);
656 /* Put the result where it belongs. */
657 *p = (STRPTR) q;
658 p++;
659 q++;
663 else
665 switch (flags[arg] & TYPEMASK)
667 case NORMAL:
668 case REST:
669 case SWITCH:
670 /* Simple arguments are just copied. */
671 array[arg] = (IPTR) argbuf[arg];
672 break;
674 case TOGGLE:
675 /* /T logically inverts the argument. */
676 array[arg] = array[arg] ? 0 : ~0;
677 break;
679 case NUMERIC:
680 /* Convert /N argument. */
681 chars = StrToLong(argbuf[arg], &value);
683 if (chars <= 0 || argbuf[arg][chars])
685 /* Conversion failed. */
686 ERROR(ERROR_BAD_NUMBER);
689 /* Put the result where it belongs. */
690 #if 0
691 if (flags[arg] & REQUIRED)
692 /* Required argument. Return number. */
693 array[arg] = value;
694 else
695 #endif
697 /* Abuse the argbuf buffer. It's not needed anymore. */
698 argbuf[arg] = (STRPTR) value;
699 array[arg] = (IPTR) & argbuf[arg];
701 break;
705 else
707 if (flags[arg] & MULTIPLE)
709 /* Shouldn't be necessary, but some buggy software relies on this.
710 * IBrowse's URL field isn't set to zero.
712 array[arg] = (IPTR)NULL;
717 /* All OK. */
718 error = 0;
719 end:
720 /* Cleanup and return. */
721 FreeVec(iline);
722 FreeVec(flags);
724 if (error)
726 /* ReadArgs() failed. Clean everything up. */
727 if (rdargs)
729 if (rdargs->RDA_Flags & RDAF_ALLOCATED_BY_READARGS)
731 FreeVec(rdargs);
735 FreeVec(dalist);
736 FreeVec(argbuf);
737 FreeVec(strbuf);
738 FreeVec(multvec);
740 me->pr_Result2 = error;
742 return NULL;
744 else
746 /* All went well. Prepare result and return. */
747 rdargs->RDA_DAList = (IPTR) dalist;
748 dalist->ArgBuf = argbuf;
749 dalist->StrBuf = strbuf;
750 dalist->MultVec = multvec;
751 return rdargs;
753 AROS_LIBFUNC_EXIT
754 } /* ReadArgs */
756 #ifdef TEST
757 # include <dos/dos.h>
758 # include <dos/rdargs.h>
759 # include <utility/tagitem.h>
761 # include <proto/dos.h>
763 char cmlargs[] = "TEST/A";
765 char usage[] =
766 "This is exthelp for test\n"
767 "Enter something";
769 #define CML_TEST 0
770 #define CML_END 1
772 LONG cmlvec[CML_END];
775 main(int argc, char **argv)
777 struct RDArgs *rdargs;
779 if ((rdargs = AllocDosObject(DOS_RDARGS, NULL)))
781 rdargs->RDA_ExtHelp = usage; /* FIX: why doesn't this work? */
783 if (!(ReadArgs(cmlargs, cmlvec, rdargs)))
785 PrintFault(IoErr(), "AROS boot");
786 FreeDosObject(DOS_RDARGS, rdargs);
787 exit(RETURN_FAIL);
790 else
792 PrintFault(ERROR_NO_FREE_STORE, "AROS boot");
793 exit(RETURN_FAIL);
796 FreeArgs(rdargs);
797 FreeDosObject(DOS_RDARGS, rdargs);
799 return 0;
800 } /* main */
802 #endif /* TEST */