.title scanf Formatted input conversion ; ; The information in this document is subject to change ; without notice and should not be construed as a commitment ; by Digital Equipment Corporation or by DECUS. ; ; Neither Digital Equipment Corporation, DECUS, nor the authors ; assume any responsibility for the use or reliability of this ; document or the described software. ; ; Copyright (C) 1980, DECUS ; ; ; General permission to copy or modify, but not for profit, is ; hereby granted, provided that the above copyright notice is ; included and reference made to the fact that reproduction ; privileges were granted by DECUS. ; .ident /000002/ ;+ ; ; Index Formatted input conversion ; ; Usage ; ; scanf(fmt, pointer(s)); ; char *fmt; /* Format string */ ; ; fscanf(fd, fmt, pointer(s)); ; FILE *fd; /* Input file pointer */ ; char *fmt; /* Format string */ ; ; sscanf(buf, fmt, pointer(s)); ; char *buf; /* Input text buffer */ ; char *fmt; /* Format string */ ; ; Description ; ; Using the format string, these functions parse the input file ; (or given text file), storing the results in the pointer ; arguments. The three routines differ as follows: ; ; scanf Reads from the standard input file. ; ; fscanf Reads from the indicated file. ; ; sscanf Reads from the text buffer. ; ; The format string may contain control characters to direct ; the conversion of the input textual data: ; ; ' ' Blanks, tabs, or newlines are ignored in format ; strings. To match whitespace, use "%[ \t\n]". ; ; x An ordinary character (not %) must match the ; next input character. ; ; % Conversion specifications consist of a '%', an ; optional '*' (to supress assignment), an ; optional maximum numeric field width, and a ; conversion specifier. ; ; Unless value assignment was supressed by the '*' format modifier, ; the next field is converted and stored in the variable pointed to ; by the corresponding argument. An input field is defined as a ; string of non-space characters, extending to the first inappropriate ; character or until the field width (if given) is exhausted. ; ; The following conversion characters are specified: ; ; % A single '%' is expected in the input, no ; assignment is done. ; ; d An integer of the specified class (decimal, ; o octal, or hexadecimal) is expected. The ; x corresponding argument should be an integer ; pointer. If the format specifer is given in ; upper-case, or preceeded by 'l', a long integer ; will be stored. For example, "%ld" is identical ; to "%D". Leading whitespace will be ignored. ; A null field is not converted. Scanf() returns ; a count of the number of fields converted, but ; does not indicate which fields were ignored. ; ; s A character string is expected. The input field ; will be terminated by a space, tab, or newline. ; The corresponding argument should point to a ; buffer large enough to contain the text and a ; terminating NULL. Leading whitespace will be ; ignored. ; ; c A single character is read. Leading white space ; is not supressed -- to read the next non-blank ; character, use "%1s". If a field width is given ; the corresponding argument is a pointer to a ; vector of characters and the indicated number of ; characters are read. ; ; e A floating-point number is converted and stored ; f appropriately. If the format indicator is ; capitalized, or preceeded by 'l', a double- ; precision floating-point number will be stored. ; The floating-point format is the same as in the ; C language: an optionally signed string of ; digits possibly containing a decimal point, ; followed by an optional exponent field which ; consists of an 'E' or 'e' followed by an ; optionally signed integer. ; ; [ ] A string not to be delimited by white-space ; characters. Unless the first character is '^', ; the format constitutes an acceptance list: the ; input field is all characters until the first ; character which is not in the set within ; brackets. Note that leading whitespace is not ; ignored. ; ; If the first character after the left bracket is ; '^', the format defines a stop list: the input ; field is all characters until the first ; character specified within the bracketed string. ; ; The corresponding argument points to a character ; vector. The result will be null-terminated. ; ; Scanf() returns the number of successfully matched and ; assigned input items. If the end of input was reached, ; EOF (-1) is returned. For example: ; ; main() ; { ; register int c; ; int i; ; char text[10]; ; ; c = scanf("%d%9s", &i, &text); ; printf("i = %d, text = %s\n", ; i, text); ; } ; ; If the input line is "150 foobar", the program will print: ; ; i = 150, text = foobar ; ; Diagnostics ; ; Scanf() returns -1 if an end of file (end of string) condition ; exists and no data was stored. It returns -2 if a palpably ; incorrect format, such as "%" is encountered. ; ; Bugs ; ; No floating point -- trying it will crash the program. ; ;- ; ; Edit history ; 000001 19-Aug-80 MM Initial Edit ; 000002 18-Sep-80 MM Fixed .psect name .MACRO PRINTF FMT,A1,A2,A3,A4,A5,A6,A7,A8,A9,?FORMAT,?EXIT MOV R0,-(SP) MOV R1,-(SP) $$$$$$ = 4 .IF NB A9 MOV A9,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A8 MOV A8,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A7 MOV A7,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A6 MOV A6,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A5 MOV A5,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A4 MOV A4,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A3 MOV A3,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A2 MOV A2,-(SP) $$$$$$ = $$$$$$+2 .ENDC .IF NB A1 MOV A1,-(SP) $$$$$$ = $$$$$$+2 .ENDC MOV #FORMAT,-(SP) MOV STDERR,-(SP) CALL FPRINTF ADD #$$$$$$,SP MOV (SP)+,R1 MOV (SP)+,R0 BR EXIT FORMAT: .ASCII FMT .BYTE 12,0 .EVEN EXIT: .ENDM PRINTF .MACRO NOTE TEXT,?TLOC,?EXIT mov r0,-(sp) mov #tloc,r0 call $$msg mov (sp)+,r0 br exit tloc: .asciz text .even EXIT: .ENDM NOTE ; ; .psect .data. ; ; These are made static to simplify argument passing to getnum ; myget, and gettext ; fd: .word 0 ; File or string pointer func: .word 0 ; Get next char. function endflg: .word 0 ; "End of input" flag length: .word 0 ; Current field length argptr: .word 0 ; Actual argument pointer match: .word 0 ; Number of items compiled ; ; Character class table ; ccltab: .byte 0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0 ; , .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 .byte 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; .psect .strn. ;02 err01: .asciz /?scanf/ ; ; Define local (stack) storage: ; ; ; Register usage within main scan loop: ; ; r2 Current (format) character ; r3 Current argument pointer ; r4 Current format pointer ; .psect .prog. scanf:: jsr r5,csv$ ; Link environments mov r5,r3 ; R3 -> add #C$PMTR+0,r3 ; first argument mov stdin,fd ; Input from stdin br fscan1 ; Continue with mainstream sscanf:: jsr r5,csv$ ; Link environments mov r5,r3 ; R3 -> add #C$PMTR+0,r3 ; first argument mov (r3)+,fd ; first arg. is buffer pointer mov #myget,func ; Get funny getc function br fscan2 ; Continue with mainstream fscanf:: jsr r5,csv$ ; Link environments mov r5,r3 ; R3 -> add #C$PMTR+0,r3 ; first argument mov (r3)+,fd ; first arg. is file pointer fscan1: mov #getc,func ; Store "get character" function fscan2: tst -(sp) ; Get space for match mov (r3)+,r4 ; R4 -> format string. clr match ; Number matched = 0 clr endflg ; End of input flag = 0 ; ; Loop through here for all format characters ; loop: movb (r4)+,r2 ; Next format character bne 10$ ; If none, mov match,r0 ; return number matched jmp done ; Off to exit. 10$: mov r2,r0 ; What is it? call whtest ; Whitespace? bne loop ; Ignore it if so cmpb r2,#'% ; '%'? bne text ; no, match next character. ; ; Percent seen ; percnt: movb (r4)+,r2 ; Get format type cmpb r2,#'% ; Explicit percent? beq text ; Yes, go for a match clr argptr ; No argument yet cmpb r2,#'* ; Supress assignment? beq 10$ ; Br if so, mov (r3)+,argptr ; Want this one. br 20$ ; Continue mainstream 10$: movb (r4)+,r2 ; Was '*', get next format byte 20$: clr r1 ; Length accumulator ; ; Loop to get explicit length, if any. ; 30$: mov r2,(sp) ; Get format byte sub #'0,(sp) ; To ascii integer value blt 40$ ; Exit if < '0' cmp (sp),#9. ; or if > '9' bgt 40$ ; Too big, exit. mov r1,r0 ; Save length asl r1 ; length = length * 10 asl r1 ; add r0,r1 ; asl r1 ; add (sp),r1 ; length = (length * 10) + (c - '0') movb (r4)+,r2 ; Get next character br 30$ ; and go for more length ; ; Got the length, if any. ; 40$: mov r1,length ; Store the length bne 50$ ; but, if it's zero, mov #32767.,length ; Store a really big length. ; ; Length is gotten, look at format byte ; 50$: cmpb r2,#'[ ; Class beq ccl ; Yep cmpb r2,#'L+040 ; "Long"? bne 60$ ; Nope movb (r4)+,r2 ; Yep, get format flavor cmpb r2,#'A+040 ; Is it lowercase? blt 60$ ; Not if it's < 'a' cmpb r2,#'Z+040 ; Nor if it's > 'z' bgt 60$ ; bic #040,r2 ; Uppercase it ; ; Can't be a character class ; 60$: tst r2 ; Still going strong? beq bug ; Nope. Just exit. cmpb r2,#'C+040 ; Yep, Characters? beq istext ; Character stuff cmpb r2,#'S+040 ; String? beq istext ; Character stuff call getnum ; Do a number br gotit ; Continue mainstream ; ; Compile character class ; ccl: clr r1 ; Assume "normal" class clr r0 ; r0 counts characters cmpb (r4),#'^ ; Inverse class? bne 10$ ; Br if not inc r4 ; Yes, eat the '^' tst (r1)+ ; and set the flag ; 10$: jsr pc,bisbic(r1) ; Set/clear the flag, inc r0 ; counting it. cmp r0,#128. ; Still in the table? blt 10$ ; Keep on going, then. neg r1 ; Flip the set/clear flag ; ; Loop through the format (until ] or end of string), updating the ; stop table. ; 20$: movb (r4)+,r0 ; Get the next byte bic #177600,r0 ; Erase parity bit cmp r0,#'] ; At the end? beq istext ; Yes, continue mainstream jsr pc,bisbic+2(r1) ; Clear/set the flag tstb -1(r4) ; True EOS (parity bit untouched) bne 20$ ; No, get another byte dec r4 ; Yes, format -> NULL ; istext: call gettxt ; Get textual stuff ; gotit: tst endflg ; End of file? back: beq loop ; No, go for next mov match,r0 ; Yes, get number matched bne done ; Got something dec r0 ; Nothing, return -1 br done ; ; ; Bit set or clear routine ; bisbic: br 10$ ; call bisbic+0 to set bit 2 bicb #2,ccltab(r0) ; call bisbic+2 to clear bit 2 return ; 10$: bisb #2,ccltab(r0) return ; ; ; Here for a text character within the format string ; text: mov fd,(sp) ; Here's the FILE call @func ; Get next character cmp r0,r2 ; Match? beq back ; Yes, continue cmp r0,#-1 ; No, EOF? beq done ; Yes, return same mov match,r0 ; No, return number gotten. ; ; Exit ; done: jmp cret$ ; All done for now. ; bug: mov #-2,r0 ; Illegal format br done ; Just exit ; ; Myget is called by invocations of sscanf() to return the next character ; from the text buffer. returns end of file. ; myget: movb @fd,r0 ; Get next byte beq 10$ ; End of file? inc fd ; No, point to next return ; and exit. ; 10$: dec r0 ; End of file, return -1 return ; Don't update pointer ; ; ; Whtest is called to test the byte (in r0) for whitespaceness. ; calling sequence: ; ; mov byte,r0 ; call whtest ; bne iswhite ; Note -- branch not equal. ; ; All registers preserved. The value in r0 will be mapped into the ; range 0..127 ; whtest: bic #^C0177,r0 ; Clear out the parity bit bitb #1,ccltab(r0) ; If whitespace bit is set return ; return with Z-bit cleared. ; ; Getnum is called by scanf to compile a real number ; ; On entrance: ; r2 Format type ( d o x D O X, e f E F ) ; r2, r3, r4 Preserved ; ; Also static stuff ; ; No interesting returned value ; ; Note: floating point causes errors ; ; ; Local storage: ; ; ; Note: to disable Floating-point code, set MAXN = 0 ; MAXN = 0 ; No floating point .IIF NDF MAXN MAXN = 64. ; Longest number EXPFLG = 0001 ; Exponent seen FLOAT = 0200 ; Floating point LONG = 0001 ; Long result NEGFLG = 0200 ; Negative result ; ; Note: ; tstb fflag tstb sflag ; bhi (EXPFLG set) bhi (LONG set) ; blos (EXPFLG clear) blos (LONG clear) ; bmi (FLOAT set) bmi (NEGFLG set) ; bpl (FLOAT clear) bmi (NEGFLG clear) ; flags = C$AUTO-2 ; Both flag bytes fflag = flags ; Float flags sflag = flags+1 ; Scan flags ndigit = C$AUTO-4 ; Number of digits found hval = C$AUTO-6 ; High integer lval = C$AUTO-10 ; Low integer nbrbuf = C$AUTO-10-MAXN ; Number buffer ; Note: nbrbuf(r5) == sp getnum: jsr r5,csv$ ; Linkage (to save registers) sub #MAXN+10,sp ; Get space for number buffer clr flags(r5) ; Clear flag bits clr hval(r5) ; Clear integer clr lval(r5) ; Buffer cmpb r2,#'A ; Uppercase blt 10$ ; means cmpb r2,#'Z ; it's bgt 10$ ; long incb sflag(r5) ; Set "long result" flag bis #040,r2 ; Convert to lowercase ; 10$: cmpb r2,#'E+040 ; 'e' beq 20$ ; Yes, cmpb r2,#'F+040 ; 'f' bne 30$ ; Nope ; 20$: .if ne MAXN bisb #FLOAT,fflag(r5) ; (FPU) set flag .iff CRASH ; No floating point .endc ; 30$: clr r3 ; r3 := jump index cmpb r2,#'X+040 ; Hexidecimal beq 40$ ; Yes, exit tst (r3)+ ; No, step flag cmpb r2,#'O+040 ; Octal beq 40$ ; Yes, exit sub #4,r3 ; No, gotta be decimal or floating ; ; r3 := -2/0/+2 depending on decimal/hex/octal ; r2 is no longer needed. ; 40$: .if ne MAXN mov sp,r4 ; r4 -> number buffer .iff clr r4 ; r4 counts characters read .endc ; ; Skip over leading whitespace ; 50$: mov fd,(sp) ; Get the next character call @func ; From defined function call whtest ; Whitespace? bne 50$ ; Loop if so. ; ; Negative? ; cmpb r0,#'- ; If first byte's a minus bne 60$ ; (nope) bisb #NEGFLG,sflag(r5) ; set flag and .if ne MAXN movb r0,(r4)+ ; store the byte and .iff inc r4 ; count the character and .endc br 170$ ; go for the next byte ; 60$: cmpb r0,#'+ ; (optional) plus sign bne 80$ ; nope, continue ; 170$: mov fd,(sp) ; Stuff the FILE pointer call @func ; + or -, get next byte dec length ; and drop the length count ; 80$: ; ; Main number loop -- the current byte is in r0 ; nloop: mov r0,r1 ; Save (for eof test) .if ne MAXN movb r0,(r4)+ ; Store the character away .iff inc r4 ; Count the character. .endc dec length ; More field to do? blt nloopx ; No, field is complete ; ; Note: Unix stdio allows '8' and '9' in octal strings for some ; strange reason. We don't. ; cmpb r0,#'0 ; Is it too low? blt notdig ; Sorry cmpb r0,digtab(r3) ; Is it in range? ble 10$ ; Yes, ok ; ; We can only come here if the character is greater than the highest digit. ; tst r3 ; Are we in Hex mode? bne notdig ; Sorry bic #040,r0 ; Convert 'a'..'z' to 'A'..'Z' cmpb r0,#'A ; Is it an OK alpha? blt notdig ; Sorry cmpb r0,#'F ; Is it really a hex alpha? bgt notdig ; Nope. sub #'A-<'9+1>,r0 ; Fix up the range ('A' follows '9') ; 10$: sub #'0,r0 ; R0 has binary value of the digit. ; ; Process the new digit ; inc ndigit(r5) ; Count that it's gotten mov #3,r1 ; Prime for octal jmp @20$(r3) ; Off to the races .word 30$ ; Multiply by 10 20$: .word 40$ ; Multiply by 16 .word 50$ ; Multiply by 8 ; ; Decimal multiply (a bit of a hack) ; 30$: mov hval(r5),r1 mov lval(r5),r2 asl r2 ; Multiply by 2 rol r1 ; asl r2 ; Multiply by 4 rol r1 ; add lval(r5),r2 ; Multiply by 5 adc r1 ; add hval(r5),r1 ; asl r2 ; Multiply by 10 rol r1 ; mov r2,lval(r5) ; Save in the mov r1,hval(r5) ; buffer br 60$ ; Continue 40$: inc r1 ; Gotta multiply by 16 ; 50$: asl lval(r5) ; Multiply by 2 rol hval(r5) ; dec r1 ; Count it bne 50$ ; And continue 60$: add r0,lval(r5) ; Add it the value adc hval(r5) ; all of it ; nnext: mov fd,(sp) ; Stuff the FILE pointer call @func ; Get next byte br nloop ; Go for it ; ; Not a digit ; notdig: .if ne MAXN cmpb r0,#'. ; Decimal point? bne 10$ ; Nope tstb fflag(r5) ; Float? bpl nloopx ; Nope, it terminates the field. inc ndigit(r5) ; Ok, keep it br nnext ; and continue ; ; Here to test for exponent part ; 10$: tstb fflag(r5) ; Exponent seen? bhi nloopx ; Yep, don't bother to test cmpb r0,#'E ; Exponent? beq 20$ ; Yep, continue cmpb r0,#'E+040 ; Another chance? bne nloopx ; Nothing left to do. ; 20$: incb fflag(r5) ; Set "exponent seen" flag mov fd,(sp) ; Stuff the FILE pointer call @func ; Get the next byte mov r0,r1 ; Save in case of exit movb r0,(r4)+ ; Store it away inc r4 ; Count it cmpb r0,#'+ ; Plus? beq nloop ; Wonderful cmpb r0,#'- ; Minus? beq nloop ; Marvelous cmpb r0,#'0 ; Decimal blt nloopx ; Sorry cmpb r0,#'9 ; Other decimal end? ble nloop ; Perfection .endc ; ; End of the "get a number loop". The last byte read is in r1 and -(r4) ; Clean up r4, returning the last byte to the file system. ; nloopx: .if ne MAXN clrb -(r4) ; Terminate the output .iff dec r4 ; Uncount the character .endc mov r1,r0 ; Backup wants the character call backup ; Unget the last character tstb sflag(r5) ; Negative? bpl 10$ ; Nope neg hval(r5) ; Yep, negate them neg lval(r5) ; Both halves sbc hval(r5) ; All 32 bit's worth ; 10$: mov argptr,r0 ; A real argument? beq 40$ ; Nope, exit .if ne MAXN cmp r4,sp ; At buffer start? .iff tst r4 ; Compiled zero characters? .endc beq 40$ ; Nothing was read if so inc match ; got one to store .if ne MAXN tstb fflag(r5) ; Floating point? bmi 30$ ; Yes, do it. .endc ; ; Output an integer ; tstb sflag(r5) ; Long? bhi 20$ ; Br if so mov lval(r5),(r0) ; Output it br 40$ ; and exit ; 20$: mov hval(r5),(r0)+ ; Output the long mov lval(r5),(r0) ; both halves .if ne MAXN br 40$ ; and exit ; ; Convert and output the floating point value ; ; (Someday) (This code should setup and call atof()). ; 30$: mov #err02,(sp) call error .endc ; 40$: jmp cret$ ; All done. ; ; Digit test vector: ; .word '9 ; Decimal digtab: .word '9 ; Hex .word '7 ; Octal ; ; Gettxt is called to read text until the break character ; ; On entrance: ; r2 Format code c s [ ; r2, r3, r4 Preserved ; ; Nothing returned ; gettxt: jsr r5,csv$ ; Linkage mov argptr,r4 ; R4 -> output buffer cmpb r2,#'C+040 ; Character type? bne 10$ ; Nope cmp length,#32767. ; Yep, default (zero) length? bne 10$ ; Nope, length was specified mov #1,length ; Only want one character ; 10$: clr r3 ; Stop code cmp r2,#'S+040 ; String type? bne 20$ ; Nope inc r3 ; Yep, stop code := 1 ; 20$: ; ; Initial skip over stuff we don't want ; 30$: mov fd,(sp) ; Stuff the FILE pointer call @func ; Get the next byte bitb ccltab(r0),r3 ; Match? beq 40$ ; No, exit this loop cmp r0,#-1 ; Yes, EOF? bne 30$ ; No, continue ; 40$: clr r3 ; Clear stop flag cmp r2,#'C+040 ; Character beq 50$ ; Yes, stop flag := 0 inc r3 ; No, assume a string cmp r2,#'S+040 ; Well, ... beq 50$ ; Ok inc r3 ; Nope, gotta be [ class ; 50$: ; ; Main loop for character getting (current char in r0) ; tloop: dec length ; Get another? blt 10$ ; No more tst r0 ; Real character? blt 10$ ; No (EOF) bitb ccltab(r0),r3 ; Yes, in stop table? bne 10$ ; Yes, exit. tst r4 ; Storing? beq 10$ ; No, continue movb r0,(r4)+ ; Yes, store it mov fd,(sp) ; Stuff the FILE pointer call @func ; and get another br tloop ; Loop some more ; 10$: call backup ; Unget the character tst r4 ; Really storing beq 20$ ; Nope cmp r4,argptr ; Actually stored something? beq 20$ ; Nope inc match ; Yes, count it cmpb r2,#'C+040 ; Character arg? beq 20$ ; Yes, don't terminate it clrb (r4) ; Terminate the string ; 20$: jmp cret$ ; Exit ; ; Backup the scan pointer -- called when the main scan a field loop ; terminates. ; ; On entrance: ; r0 := last character read (or EOF flag) ; ; Return: ; ; If r0 was not EOF, the character will be pushed back onto the input stream. ; backup: cmp r0,#-1 ; EOF? bne 10$ ; Nope inc endflg ; Yep, set flag return ; and exit ; 10$: cmp func,#myget ; Internal "get bne 20$ ; Nope, do it the hard way dec fd ; Backup the scan pointer return ; And exit ; 20$: mov fd,-(sp) ; ungetc(c, fd); mov r0,-(sp) ; call ungetc ; Do it cmp (sp)+,(sp)+ ; Clean the stack return ; Exit cleanup code .end