News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Read imput string routine

Started by falcon01, July 31, 2011, 06:06:20 PM

Previous topic - Next topic

falcon01

Hi I was assigned to this (MS DOS 16 bit :( )  exercise:
"Create a program which asks the user for a number (1-3 digit, range 0-255) and stores it in memory. Make a check on every error".

I decided to break the program in 2 routines: one just takes the string and another makes a check on the entered string.
Could you please take a look at the first one and tell me if it's ok?It's not very complicated/long:


;arguments: BX (contains memory base address where to store string)

PROC_READ_NUMBER proc near

            push AX
            push DI
                        xor     DI,DI
                mov AH,1
READ_CHAR: int 21h                   

            cmp  al,08h     ;verify if a backspace is pressed
            jne  STORE_CHAR
            cmp  DI,0         
            je   READ_CHAR
            dec  DI               ;if DI is not zero decrement it
            jmp  READ_CHAR
           
STORE_CHAR: mov  [BX+DI],al
            inc  DI
        cmp  al,0dh           ;if it isn't carrier (aka enter pressed) continue reading
        jne  READ_CHAR
       

          mov  [BX+DI],0Ah   ;put line feed   
          mov  [BX+DI+1],'$'  ;put end string terminator (for further visualization using dos output string function)
         
          mov al, 0Ah
        mov ah, 0eh     ;line feed on video, this way next string will go on next line
        int 10h
         
          pop  DI
        pop  ax
        ret

PROC_READ_NUMBER endp 



I think it's already good, the only problem is that when user press backspace the current char is not deleted. Furthermore if user presses backspace when SI=0 cursor overrides previous string (though SI=0 control makes sure I don't go in another memory area other than string reserved one).
For example if I print
QuoteEnter a number:
pressing too much times backspace makes cursor go on this string !

dedndave

                 mov  AH,1
READ_CHAR: int 21h     
             
ouch !!!!
whenever you jump back up to READ_CHAR, you should reload AH with 1
the contents of that register are likely destroyed
READ_CHAR:
        mov     AH,1
        int     21h     


with DOS interrupts, it is hard to know which registers are preserved and which are not - lol
it takes experience, is all
it varies widely from one function to the next
for a beginner, the best way to go is to assume that all registers are destroyed when using INT
unless you find info otherwise  :P

Ralf Brown keeps a list of DOS interrupts
but, i don't remember seeing to much info on register preservation in there

dedndave


falcon01

Yes I know it, but I already checked it out so other AL no other register is modified!

Can you help me with the rest of the function?The backspace thing is really irritating...how can I clean the characters when I press backspace?
For example if I write
Quote123
and press bs I'd like to have
Quote12
, not 123 with cursor on 3...and how can I deactivate bs when I'm at 1?If not the cursor will go on the previous string...

Code is really simple: I call the DOS print string function on a the string "Insert a number: $", then I call this function.

dedndave

well - the way i did it was to display a backspace, then a space, then another backspace
the first backspace and the space get rid of the old char
the second backspace is to move the cursor

as for backing up over previous text, not sure about that
i'd have to play with the code
could you post all of it so i don't have to write it ?

falcon01

Sure thing :


;----------------------------------------------------------------------------------------
SSEG SEGMENT PARA STACK 'STACK'
db 128 dup(?)
SSEG ENDS 

;-----------------------------------------------------------------------------------------
DSEG segment para public 'DATA'

FIRSTR: db  'First number: $'
SECSTR: db  'Second number: $'

n1      db  20 dup('$')                 ;area which will contain the first number
n2      db  20 dup('$')                 ;area for second number


DSEG ENDS

;-----------------------------------------------------------------------------------

CSEG SEGMENT PARA PUBLIC 'CODE'

;----------------------------------------------------------------------------------------------------------------------
; Main program
;----------------------------------------------------------------------------------------------------------------------
MAIN:
 
mov  ax,DSEG        
mov  ds,ax

mov dx, offset FIRSTR
call PROC_PRINT_STRING    
mov     bx, offset n1
call    PROC_READ_NUMBER

mov dx, offset SECSTR
call PROC_PRINT_STRING 

mov     bx, offset n2 
call    PROC_READ_NUMBER   


mov ah,4ch   
int 21h
       ; Exit program, return to OS             

;#######################################################################################
;prints the string which address is contained in DX

PROC_PRINT_STRING proc near

push ax
mov ah,9h            
int 21h
pop ax

ret
PROC_PRINT_STRING endp

;################################################################
; arguments: bx (area which will contain number)
PROC_READ_NUMBER proc near

            push ax
            push DI
           
        mov ah,1
READ_CHAR: int 21h                   

            cmp  al,08h     ;verify if a backspace was pressed
            jne  STORE_CHAR
            cmp  DI,0
            je   READ_CHAR
            dec  DI
            jmp  READ_CHAR
           
STORE_CHAR: mov  [BX+DI],al
            inc  DI
        cmp  al,0dh   
        jne  READ_CHAR
       

          mov  [BX+DI],0Ah   ;line feed   
          mov  [BX+DI+1],'$' 
         
          mov al, 0Ah
        mov ah, 0eh     ;line feed on video
        int 10h
         
          pop  DI
        pop  ax
        ret

PROC_READ_NUMBER endp 

CSEG ends     
;---------------------------------------------------------------------------------------
        ASSUME CS:CSEG,DS:DSEG,SS:SSEG
end MAIN



dedndave

hang on - there is no check for buffer overflow   :P

dedndave

the buffers are 20 bytes long
that means you can fit 17 characters, 1 carriage return, 1 line feed, and 1 terminator into the buffer
normally, we would write the routine so the buffer could be variable length
then, we would pass the length
you did not write it that way, so i limit it to 17 characters

here we go...
        .MODEL  Small

;----------------------------------------------------------------------------------------

SSEG    SEGMENT PARA STACK 'STACK'
        dw      256 dup(?)
SSEG    ENDS

;-----------------------------------------------------------------------------------------

DSEG    SEGMENT PARA PUBLIC 'DATA'

FIRSTR  db  'First number: $'
SECSTR  db  'Second number: $'

n1      db  20 dup('$')                 ;area which will contain the first number
n2      db  20 dup('$')                 ;area for second number

DSEG    ENDS

;-----------------------------------------------------------------------------------

CSEG    SEGMENT PARA PUBLIC 'CODE'
        ASSUME  CS:CSEG,DS:DSEG,SS:SSEG

;----------------------------------------------------------------------------------------------------------------------
; Main program
;----------------------------------------------------------------------------------------------------------------------

MAIN:
        mov     ax,DSEG
        mov     ds,ax

        mov     dx,offset FIRSTR
        call    PROC_PRINT_STRING

        mov     bx,offset n1
        call    PROC_READ_NUMBER

        mov     dx,offset SECSTR
        call    PROC_PRINT_STRING

        mov     bx,offset n2
        call    PROC_READ_NUMBER

        mov     ax,4C00h
        int     21h                   ;Exit program, return to OS

;#######################################################################################
;prints the string which address is contained in DX

PROC_PRINT_STRING proc near

        push    ax
        mov     ah,9
        int     21h
        pop     ax
        ret

PROC_PRINT_STRING endp

;################################################################
; arguments: bx (area which will contain number)
PROC_READ_NUMBER proc near

        push    ax
        push    DI
        mov     DI,0

READ_CHAR:
        mov     ah,1
        int     21h

        cmp     al,8                 ;verify if a backspace was pressed
        jne     STORE_CHAR

        cmp     DI,0
        jne     BACKSPACE_CHAR

        push    BX
        mov     bh,0
        mov     ah,8                 ;get character at cursor
        int     10h
        pop     BX

        mov     ah,0Eh               ;re-display the character
        int     10h
        jmp     READ_CHAR

BACKSPACE_CHAR:
        dec     DI
        mov     ax,0E20h             ;display a space
        int     10h
        mov     ax,0E08h             ;display a backspace
        int     10h
        jmp     READ_CHAR
           
STORE_CHAR:
        cmp     al,0Dh
        je      SAVE_CHAR

        cmp     DI,17
        jb      SAVE_CHAR

        mov     ax,0E08h             ;display a backspace
        int     10h
        mov     ax,0E20h             ;display a space
        int     10h
        mov     ax,0E08h             ;display a backspace
        int     10h
        jmp     READ_CHAR

SAVE_CHAR:
        mov     [BX+DI],al
        inc     DI
        cmp     al,0Dh
        jne     READ_CHAR

        mov word ptr [BX+DI],240Ah   ;line feed and terminator

        mov     ax,0E0Ah             ;line feed on video
        int     10h

        pop     DI
        pop     ax
        ret

PROC_READ_NUMBER endp

CSEG    ENDS

;---------------------------------------------------------------------------------------

        END     MAIN


there are many ways to improve on this code
you should look into the STOSB instruction, for example
also, there are shortcuts that can be used so you do not have to open and close all those segments
but, i didn't want to make 100 changes
the code would not be recognizable   :P
as you become more proficient, you will learn those things
the 16-bit sub-forum has many code examples to learn from

falcon01

Thanks a bunch dave you really saved my day!

By the way,as in the exercise is asked to have a 3-digit number (in ascii format, so each digit is a letter), I think I could modify the buffer overflow check with 3 instead of 17 (using a buffer of 6, 3 for the number +0D+0A+$), what do you think?
Moreover some little questions ^^
1)
Quotemov word ptr [BX+DI],240Ah   ;line feed and terminator
this was because 8086 is little endian?I would've put 0A24 instead
2)Stosb isn't made to set a memory area to the same constant value?I read it's used to initialize, for example, with zeros...so it's not for this case...am I wrong?
3)I like writing every segment...it mades my mind clearer about what I am doing...is there a valid reason to use specific assembler directives?


Neil

STOSB puts the value in AL into the address at which DI points to, then increments DI by one. You are talking about filling a memory area with a value (Usually but not always zero) contained in al. This is achieved by placing REP in front of STOSB & a count in CX of the number of bytes to be written by that value.

FORTRANS

Hi,

   1)  Right.  Little endian.  The bytes in registers are "numbered"
right to left (sort of).  Most memory display programs go left
to right.  So using words and double words to make a string
of bytes takes some getting used to.

   2)  The string instruction STOSB works along the following
lines.


; Use string instuction.
        MOV     DI,OFFSET Destination
        MOV     AL,Value
        STOSB

; Use other instuctions to do the same thing.
        MOV     DI,OFFSET Destination
        MOV     AL,Value
        MOV     [Value],AL
        INC     DI

; Use string instuction to clear memory.
        MOV     DI,OFFSET Destination
        MOV     CX,Count
        MOV     AL,0
    REP STOSB

; Use other instuctions to do the same thing.
        MOV     DI,OFFSET Destination
        MOV     CX,Count
        MOV     AL,0
Label_1:
        MOV     [Value],AL
        INC     DI
        LOOP    Label_1:


   For STOSB it's not a big deal, but CMPS and MOVS are a
bit more complex.

3)  You can use the .MODEL directive or not.  Don't know why
Dave put one in as you had everything defined already.  The
basic idea seems to be less typing and standardized results.
And of course personal preferences.

Regards,

Steve N.

dedndave

here is an example with some of the changes we mentioned
for the keyboard entry routine, you must now pass the length of the buffer in CX
the address of the buffer is passed in DI
also, the routine returns the number of characters entered by the user in CX
STOSB is used
simple segments are used
the buffers are uninitialized
AX is not preserved

        .MODEL  Small
        .STACK  512

;-----------------------------------------------------------------------------------

        .DATA

FIRSTR  db  'First number: $'
SECSTR  db  'Second number: $'

;-----------------------------------------------------------------------------------

        .DATA?

n1      db  20 dup(?)                 ;area which will contain the first number
n2      db  20 dup(?)                 ;area for second number

;-----------------------------------------------------------------------------------

        .CODE
        ASSUME  DS:DGROUP

MAIN    PROC    FAR

        mov     ax,@DATA
        mov     ds,ax
        mov     es,ax

        mov     dx,offset FIRSTR
        call    PROC_PRINT_STRING

        mov     di,offset n1
        mov     cx,sizeof n1
        call    PROC_READ_NUMBER

        mov     dx,offset SECSTR
        call    PROC_PRINT_STRING

        mov     di,offset n2
        mov     cx,sizeof n2
        call    PROC_READ_NUMBER

        mov     ax,4C00h
        int     21h                   ;Exit program, return to OS

MAIN    ENDP

;-----------------------------------------------------------------------------------

PROC_PRINT_STRING PROC NEAR

;prints the string which address is contained in DX

        mov     ah,9
        int     21h
        ret

PROC_PRINT_STRING ENDP

;-----------------------------------------------------------------------------------

PROC_READ_NUMBER PROC NEAR

;arguments: DI (area which will contain number)
;CX = buffer length
;
;returns: CX = number of characters entered (not including cr/lf/term)
;         DI preserved

        sub     cx,3
        jnc     BUFF_OK

        ret

BUFF_OK:
        push    di
        push    bp
        mov     bp,sp
        push    di                   ;[BP-2] = buffer start
        add     cx,di
        push    cx                   ;[BP-4] = buffer end

READ_CHAR:
        mov     ah,1
        int     21h
        cmp     al,8                 ;verify if a backspace was pressed
        jnz     STORE_CHAR

        cmp     di,[bp-2]            ;[BP-2] = buffer start
        ja      BACKSPACE_CHAR

        mov     bh,0
        mov     ah,8                 ;get character at cursor
        int     10h
        mov     ah,0Eh               ;re-display the character
        int     10h
        jmp     READ_CHAR

BACKSPACE_CHAR:
        dec     di
        mov     ax,0E20h             ;display a space
        int     10h
        mov     ax,0E08h             ;display a backspace
        int     10h
        jmp     READ_CHAR
           
STORE_CHAR:
        cmp     al,0Dh
        jz      SAVE_CHAR

        cmp     di,[bp-4]            ;[BP-4] = buffer end
        jb      SAVE_CHAR

        mov     ax,0E08h             ;display a backspace
        int     10h
        mov     ax,0E20h             ;display a space
        int     10h
        mov     ax,0E08h             ;display a backspace
        int     10h
        jmp     READ_CHAR

SAVE_CHAR:
        stosb
        cmp     al,0Dh
        jnz     READ_CHAR

        mov word ptr [di],240Ah      ;line feed and terminator
        mov     ax,0E0Ah             ;display line feed
        int     10h

        dec     di
        pop     ax                   ;discard buffer end
        pop     cx                   ;CX = buffer start
        sub     di,cx
        mov     cx,di                ;CX = user string length
        pop     bp
        pop     di
        ret

PROC_READ_NUMBER ENDP

;-----------------------------------------------------------------------------------

        END     MAIN

falcon01

Mmm..it does pretty much the same thing, I don't see any advantage using stsob in this case...

dedndave

well - it simplifies register use
notice that BX is not used as a base
[BX+DI] is not necessary
we don't have to preserve BX across the INT 10h, AH=8 call
there is some added code to use the stack frame
but, we also added functionality in the use of variable-length buffers

falcon01

Ah that's right!! Thank you again!