#Titel Programmieren/ Einsatz der Dos-Funktion ReadArgs
#Logo gadget28:pinsel/AG.Prog
#Font topaz 8
#Font Losse 16
#C31
Einsatz der Dos-Funktion ReadArgs()
-----------------------------------
#Font topaz 8
#C21

Bis vor kurzem habe ich in meinen Programmen eigene Parser verwendet, um
CLI-Argumente auszuwerten. Ich wollte kompatibel zu OS 1.2/1.3 bleiben.
Dann kam ich - ziemlich spät - zu der Erkenntnis, daß kein ernsthafter
Amiga-Benutzer mehr mit den alten Betriebssystemen arbeitet. Daher befaßte
ich mich mit den Möglichkeiten, die die Dos-Funktion ReadArgs() bietet, und
schrieb dazu ein kleines Testprogramm, das ich diesem Beitrag einschließlich
Assembler-Source beifüge.
#Y+6
#C10
[ Das Programm befindet sich in dem Unterverzeichnis "Args". Es darf auf
  keinen Fall von der Workbench aus gestartet werden ! (an) ]
#C21
#Y+6
Zur Erinnerung hier noch mal die Synopsis:

#C10
    result = ReadArgs (template, array, rdargs)
     D0                  D1        D2     D3
#C21
Wie üblich werden die Argumente für Funktionen der dos.library in Daten-
registern übergeben. Das ist allerdings nur für Assemblerprogrammierer
von Interesse.

"template" ist ein Zeiger auf eine Zeichenkette, die Art und Zahl der er-
warteten Argumente angibt. Das Format ist im Handbuch (RKM) ausführlich
erläutert, deshalb verzichte ich auf weitere Erklärungen. Als Beispiel die(?)
Template aus meinem Testprogramm:

#C10
template    dc.b    'SWITCH/S,N=NUMBER/K/N,F=FONT/K,FILE',0
#C21

"array" ist ein Zeiger auf einen Puffer, in dem von ReadArgs() Zeiger auf
die Resultate ablegt werden. Je Template-Eintrag ist ein Longword vorzusehen,
im Beispiel also 4*4=16 bytes. Der array-Puffer sollte vor Aufruf von
ReadArgs() mit Null initialisiert werden.

"rdargs" ist ein optionaler Zeiger auf eine RDArgs-Struktur. Wenn dieser
auf NULL gesetzt wird, übernimmt ReadArgs() Allozierung und Initialisierung
dieser Struktur selbst. Das ist natürlich einfacher, und die meisten
Programme setzen rdargs auf NULL. Es kann aber nützlich sein, diese Struktur
selbst anzulegen. Dazu weiter unten mehr.

"result" ist bei Erfolg ein Zeiger auf eine RDArgs-Struktur oder NULL, wenn
ein Fehler aufgetreten ist. Bei Erfolg muß nach Auswertung des von ReadArgs()
angelegten arrays FreeArgs() mit "result" als Parameter aufgerufen werden.
Übrigens auch dann, wenn man eine eigene RDArgs-Struktur angelegt hat!

Falls ein Fehler aufgetreten ist, kann man IoErr() aufrufen und mittels
PrintFault() eine Fehlermeldung in das Shell-Fenster ausgeben. Diese ist
sogar lokalisiert (!), jedenfalls ab OS 3.0. Man kann sich den zum jeweiligen
DosError passenden string auch mit Fault() besorgen, muß ihn dann aber selbst
ausgeben, z.B. mittels Requester oder im Fenstertitel.

Hier die von ReadArgs() zu erwartenden Fehlernummern und die dazugehörigen
error strings, ausgegeben vom CLI-Programm Fault:

#C10
Fault 115: Ungültiger Zahlenwert
Fault 116: Gefordertes Argument fehlt
Fault 117: Argument nach Schlüsselwort fehlt
Fault 118: Falsche Anzahl an Argumenten
Fault 119: Ungerade Anzahl von Anführungszeichen
Fault 120: Argumentzeile ist ungültig oder zu lang
#C21

Statt "Fault xyz:" kann man in eigenen Programmen natürlich einen anderen
"header" wählen.

Im Prinzip finden sich alle erforderlich Angaben zum Einsatz von ReadArgs()
im RKM (Includes and Autodocs), aber manches wurde mir erst beim Herumspielen
mit dem Testprogramm klar. Hier die wichtigsten Erkenntnisse:

    - ReadArgs() parst nicht weiter, wenn ein Fehler auftritt. Bis dahin
      ausgewertete Parameter können nur dann verwertet werden, wenn man eine
      eigene RDArgs-Struktur anlegt und den erforderlichen Puffer selbst
      verwaltet. Im anderen Fall sind die im array vorgefundenen Zeiger
      ungültig, weil der Puffer, auf den sie zeigen, von ReadArgs() bereits
      freigegeben wurde und überschrieben sein kann.

    - ReadArgs() holt sich normalerweise die Benutzereingaben direkt aus dem
      Eingabepuffer der Shell. Am Ende des zu parsenden strings muß(!) ein
      Linefeed stehen. Wenn man dieses löscht, gibt es eine Fehlermeldung.

    - Es empfiehlt sich, in eigenen Templates Schlüsselwörter (keywords)
      zu verwenden, die mit der Endung '/K' gekennzeichnet sind. Dadurch
      kann der Benutzer die Eingaben in beliebiger Reihenfolge machen.
      Also nicht NUMBER/N, sondern NUMBER/K/N verwenden. Im ersten Fall
      ist 'NUMBER' kein Schlüsselwort, sondern nur ein Hinweis für den
      Benutzer, daß an entsprechender Stelle eine Zahl erwartet wird.

    - Eine Eingabe wie NUMBER=20 wird wie NUMBER 20 behandelt.

    - ReadArgs() ist nicht unfehlbar. Wenn z.B. nach einem keyword das gefor-
      derte Argument fehlt und sich dann ein zweites keyword anschließt,
      wird dieses als Argument genommen. Bei folgender Eingabe mit obiger
      Template bekommt man als font "number" und als file "20":

#C10
      > args font number 20
#Y-10
#C21
                             (args ist der Programmname)
    - ReadArgs() gibt automatisch die Template aus und holt sich weitere
      Eingaben, wenn ein _einzelnes_ Fragezeichen eingegeben wurde. Mehrere
      ??? oder ein ? kombiniert mit anderen Zeichen werden als Muster durch-
      gelassen.
      Man kann das "Prompten" unterbinden, indem man das Flag NOPROMPT in
      einer eigenen RDArgs-Struktur setzt.

    - ReadArgs() kann zum Parsen von Tooltypes und Konfigurationsdateien
      eingesetzt werden. Man muß dann nur alles zu einem einzigen string (mit
      Linefeed abgeschlossen) umformen und einen Zeiger darauf in die dann
      zwingend erforderliche eigene RDArgs-Struktur eintragen.


#C31
Zur Auswertung:
#C21

Im array (s.o.) sind die Ergebnisse des Parsens in der Reihenfolge abgelegt,
wie von der Template vorgegeben. Die LONGs enthalten je nach Optionstyp
unterschiedliche Werte. Bei Schaltern (/S) finden wir TRUE (-1) oder
FALSE (0), ansonsten Zeiger auf einen weiteren Pufferbereich, wo die
Benutzereingaben säuberlich hintereinander abgelegt sind. Zahlen (/N) werden
von ReadArgs() konvertiert und in einem Longword gespeichert. strings be-
ginnen immer an WORD-Grenzen und enden mit einem Nullbyte. Der maximale
Abstand zwischen zwei Einträgen beträt 2 bytes. Damit läßt sich die Größe
des eigenen Puffers abschätzen, den man zur Verfügung stellen muß.


#C31
Zum Testprogramm "args":
#C21

Es gibt bei Eingabe von '?' die Template aus. Ansonsten informiert es darüber,
was ReadArgs() mit den Eingaben gemacht hat und an welcher Stelle was abge-
legt wurde. Falls Eingabefehler entdeckt wurden, werden mittels IoErr() und
PrintFault() die entsprechenden Fehlermeldungen ausgegeben. Aufruf nur von
der Shell aus!!

#C10
;------------------------- Start Sourcecode ---------------------------------

;PROGRAMM       args
;ZWECK          testet ReadArgs()
;AUTOR          Jürgen Klawitter
;Datum          3.10.96
;MAKE           c:a68k ram:args.s, c:dlink ram:args.o -oram:args

                include ass:inc/macros.i

AllocMem        = -$0c6 (byteSize,requirements)(D0/D1)
FreeMem         = -$0d2 (memoryBlock,byteSize)(A1,D0)
IoErr           = -$084 ()
PrintFault      = -$1da (code,header) (d1,d2)
AllocDosObject  = -$0e4 (type,tags) (d1,d2)
FreeDosObject   = -$0ea (type,ptr) (d1,d2)
OpenLibrary     = -$228 (libName,version)(A1,D0)
CloseLibrary    = -$19E (library)(A1)
PutStr          = -$3b4 (str) (d1)
RawDoFmt        = -522  ()  (a0,a1,a2,a3)
ReadArgs        = -$31e (template,array,args) (d1,d2,d3)
FreeArgs        = -$35a (args) (d1)

LF              = 10
RDA_Flags       = 28
NOPROMPT        =  4
NOALLOC         =  2
DOS_RDARGS      =  5            ;für AllocDosObject

    STRUCTURE   globals,0
    STRUCT      mainbuf,200
    STRUCT      formatbuf,140
    APTR        DosBase
    APTR        RDArgs          ;für result von ReadArgs()
    APTR        DosObj
    LABEL       gl_size

;start  moveq   #20,d7          ;vorläufiger Returncode
        move.l  a0,a3
        move.l  d0,d3
        move.l  4,a6
        move.l  #gl_size,d0
        moveq   #1,d1           ;MEMF_CHIP!MEMF_CLEAR
        swap    d1
        Call    AllocMem
        tst.l   d0
        beq     exit
        move.l  d0,a4
        moveq   #10,d7
        lea     dosname(pc),a1
        moveq   #37,d0          ;OS 2.04 mindestens
        Call    OpenLibrary
        move.l  d0,DosBase(a4)
        beq     \freemem
        move.l  d0,a6
        move.l  a3,a0
        cmp.b   #'?',(a0)+
        bne     \allocdos
        cmp.b   (a0),d7         ;LF ?
        beq     \readargs       ;einzelnes ?: keine eigene RDArgs-Struktur
\allocdos
        moveq   #DOS_RDARGS,d1
        Zero    d2              ;keine tags
        Call    AllocDosObject
        move.l  d0,DosObj(a4)
        beq     \closelib
        move.l  d0,a0           ;jetzt RDArgs initialisieren
        move.l  a3,(a0)+        ;zu parsender string
        move.l  d3,(a0)+        ;Länge einschl. Linefeed am Ende
        clr.l   (a0)+
        clr.l   (a0)+
        lea     40(a4),a1
        move.l  a1,(a0)+        ;Zeiger auf eigenen Puffer für Ergebnisse
        moveq   #120,d1         ;Länge des Puffers
        move.l  d1,(a0)+
        clr.l   (a0)+           ;ExtHelp, gibt es nicht
        addq.l  #NOPROMPT|NOALLOC,(a0)  ;RDA_Flags
\readargs
        lea     template(pc),a1
        move.l  a1,d1
        move.l  a4,d2           ;array
        move.l  DosObj(a4),d3   ;bei '?' NULL
        Zero    d7
        Call    ReadArgs
        move.l  d0,RDArgs(a4)
        bne     \result
        Call    IoErr
        move.l  d0,d1
        lea     header(pc),a0
        move.l  a0,d2
        Call    PrintFault
        moveq   #10,d7          ;RC=10
\result
        move.l  4(a4),d0        ;pointer auf NUMBER
        beq     \nolen
        move.l  d0,a0
        move.l  (a0),4(a4)      ;NUMBER
\nolen
        lea     result.fmt(pc),a0
        bsr     _Format
        bsr     _Print
\freeargs
        move.l  RDArgs(a4),d1
        beq     \freeobj
        Call    FreeArgs
\freeobj
        moveq   #DOS_RDARGS,d1
        move.l  DosObj(a4),d2
        beq     \closelib
        Call    FreeDosObject
\closelib
        move.l  a6,a1
        move.l  4,a6
        Call    CloseLibrary
\freemem
        move.l  a4,a1
        move.l  #gl_size,d0
        Call    FreeMem
exit    move.l  d7,d0
        rts

;---------------------- Subroutinen  ----------------------------------

_Print
    move.l  a0,d1
    Call    PutStr
    rts

_Format                         ;>a0=formatstr >>a0=formatierter string
    movem.l a1-a6,-(sp)
    move.l  a4,a1
    lea     _PutChar(pc),a2
    lea     formatbuf(a4),a3
    move.l  4,a6
    Call    RawDoFmt
    move.l  a3,a0
    movem.l (sp)+,a1-a6
    rts

_PutChar
    move.b  d0,(a3)+
    rts

;---------------------- Datenbereich ----------------------------------

dosname     dc.b    'dos.library',0
template    dc.b    'SWITCH/S,N=NUMBER/K/N,F=FONT/K,FILE',0
result.fmt  dc.b    'SWITCH %ld NUMBER %ld FONT "%s" FILE "%s"',LF,0
header      dc.b    'Fehler',0
            END
