0

At the moment, I have a program created that allows me to input a string and reverse it. It does what I want it to and I think it's the best way. Now I'd like to reverse the case meaning when someone enters Hello World, it would output DLORw OLLEh

I'm not sure what the best way to approach this is. I've taken a look at other solutions on the platform, but have had some difficulty in trying to make everything organized. I'd also appreciate any suggestions to improve my current code if there are any

Below is the code I have so far:

include 'emu8086.inc'
 
org 100h  


CALL STRING_INPUT 
RET

STRING_INPUT PROC
     
    PRINT "Enter a string to reverse it: "   
    LEA DI, buffer ; put string inside so we can read it later
    MOV DX, buffSize

    CALL GET_STRING
    PRINTN  
   
    MOV SI, DI 
  
    MOV CX, 0h ; character count of string
    
STRING_INPUT ENDP 

STRING_REVERSE PROC 
    ReadInput:
        ;check for last character 
        MOV AX, [SI]  
        CMP AL, 0
        JE CreateReverse  
     
        PUSH [SI] ; push inside stack 
      
        ; count each character
        INC SI  
        INC CX    
        JMP ReadInput  
         
    CreateReverse: ; 
        MOV SI, DI ; set input again 
        MOV AX, CX ; store length of input
      
        BuildString:    
            CMP CX,0  
            JE PrintReversedInput  
      
            POP DX    
            MOV DH, 0  
            MOV [SI], DX ;set si to the reversed string character
            INC SI       
            DEC CX  
            JMP BuildString
                   
    PrintReversedInput:  
        MOV SI, DI
        PRINT "Reversed String: "  
        CALL PRINT_STRING    
        RET
    
STRING_REVERSE ENDP 
 
buffer DB 20 DUP (?)  ;set input max size for get_string
buffSize = $-buffer

DEFINE_GET_STRING
DEFINE_PRINT_STRING

END
  • 2
    ASCII is arranged so that the upper and lower case alphabets differ by 32 positions. Hence you can reverse the case of a letter by xor'ing it with the constant 32. Note you will probably want to check first that the character is in fact an alphabet letter. – Nate Eldredge Dec 04 '20 at 02:28
  • 2
    The simplest way is probably using a lookup table, partly because the code can treat all characters the same (without caring if characters are letters or not). – Brendan Dec 04 '20 at 03:36
  • @Brendan Are there any resources out that show how to use a lookup table? Can't seem to find any resources online for x86 –  Dec 04 '20 at 03:49
  • @NateEldredge Is it necessary to check if the character is an alphabet letter? For example, if there way spaces in the user's input, that means the character count would be xor'd with the constant 32 too? I'm ignoring the fact that a user could enter a number, let's just assume the user is trustworthy haha –  Dec 04 '20 at 03:52
  • Yes, it's necessary. For example, if there's a space, xor'ing with 32 will change it to a NUL, which certainly won't print as a space. – Nate Eldredge Dec 04 '20 at 03:59
  • @NateEldredge Yep makes sense. So I've been spending the past bit trying to get `xor si,32` to work, but for some reason it's outputting "String:" for Reversed String: Any idea what's going on there? I'm doing the reverse case stuff inside BuildString. Not sure if I'm misunderstanding, this is new to me –  Dec 04 '20 at 04:52
  • @ilovehatecoding: A lookup table could just be like `movzx bx,al` then `mov al,[table+bx]`; or alternatively (much less common) could be like `mov bx,table` then `xlatb`. The basic idea is simple - read a byte/character (into `al`) from an address that depends on the original byte/character. Of course you'd also need a table; maybe like `table: db 0x00,0x01,0x02, ...` (but with 256 entries). – Brendan Dec 04 '20 at 04:58
  • Not the simplest, but fairly efficient: It only takes a few instructions to detect alphabetic characters and case flip them, leaving others unchanged: [How to access a char array and change lower case letters to upper case, and vice versa](https://stackoverflow.com/a/35936844). Also [What is the idea behind ^= 32, that converts lowercase letters to upper and vice versa?](https://stackoverflow.com/a/54585515) – Peter Cordes Dec 04 '20 at 15:34

1 Answers1

0

I'd also appreciate any suggestions to improve my current code if there are any

Let's start with this.

  • Why do you let your proc's fall through into each other? Either make 2 call's or else create a single proc.
  • Why push from a memory location if the datum is already available from a register? In code: change push [si] into push ax
  • Why do you write a word when a character is stored in a byte? In code: change mov [si], dx into mov [si], dl
  • Why do you allow a macro invokation between setting an argument register and the call to PRINT_STRING? Do you know for a fact that that PRINT macro doesn't destroy SI?

Next is my rewrite that remedies the above and also removes some redundant instructions and more...

include 'emu8086.inc'
 
org 100h  

CALL STRING_INPUT 
CALL STRING_REVERSE
PRINT "Reversed String: "
LEA  SI, buffer
CALL PRINT_STRING
RET                      ; Exit to DOS

STRING_INPUT PROC
    PRINT "Enter a string to reverse it: "   
    LEA  DI, buffer
    MOV  DX, buffSize
    CALL GET_STRING
    PRINTN
    RET
STRING_INPUT ENDP

STRING_REVERSE PROC
    XOR  CX, CX          ; character count of string = 0
    LEA  SI, buffer
  ReadInput:
    MOV  AL, [SI]
    CMP  AL, 0
    JE   CreateReverse  
    PUSH AX              ; Don't care about garbage in AH
    INC  SI
    INC  CX
    JMP  ReadInput
  CreateReverse:
    JCXZ DoneReversing
    LEA  SI, buffer
  BuildString:
    POP  DX
    MOV  [SI], DL
    INC  SI
    DEC  CX
    JNZ  BuildString
  DoneReversing:        ; There's no need to zero terminated the string manually
    RET                 ; because Input and Output have the same length and the
STRING_REVERSE ENDP     ; original zero-terminator is still there.
 
buffer DB 20 DUP (?)
buffSize = $-buffer

DEFINE_GET_STRING
DEFINE_PRINT_STRING

END

Now I'd like to reverse the case meaning when someone enters Hello World, it would output DLROw OLLEh

You have a choice to make this conversion either in ReadInput or else in BuildString. I prefer the latter.

The code:

  BuildString:
    POP  DX              ; OriginalChar

    mov  al, dl          ; All verification is done on a copy of the character!
    or   al, 32          ; [A,Z] become [a,z], [a,z] stays [a,z], others don't care
    sub  al, 'a'         ; If [a,z] it will become [0,25]
    cmp  al, 26
    jnb  Other
    xor  dl, 32          ; Change the case on the OriginalChar: [A,Z] -> [a,z]
  Other:                 ;                                      [a,z] -> [A,Z]

    MOV  [SI], DL
    INC  SI
    DEC  CX
    JNZ  BuildString

You wanted the 'simplest way'. Well that is very personal.

  • My implementation of the solution suggested by @Nate Eldredge only requires but a few simple instructions and nothing more.
  • The lookup table solution suggested by @Brendan requires even fewer instructions but the mandatory 256 bytes ASCII table is not to be neglected.

As @Peter Cordes points out in his comment, you can write this reversing without using the stack in this manner. What you do need is a second buffer bufferB that you start filling from the end. This time the STRING_REVERSE proc ends with in the SI register the address of the resulting string, ready for printing!

...
PRINT "Reversed String: "
CALL STRING_REVERSE    ; -> SI
CALL PRINT_STRING
...

...
STRING_REVERSE PROC
    LEA  DI, buffer
    LEA  SI, bufferB + 19
    MOV  byte [SI], 0        ; Zero-terminate the output buffer
  ReadInput:
    MOV  DL, [DI]
    CMP  DL, 0
    JE   DoneReversing

    mov  al, dl          ; All verification is done on a copy of the character!
    or   al, 32          ; [A,Z] become [a,z], [a,z] stays [a,z], others don't care
    sub  al, 'a'         ; If [a,z] it will become [0,25]
    cmp  al, 26
    jnb  Other
    xor  dl, 32          ; Change the case on the OriginalChar: [A,Z] -> [a,z]
  Other:                 ;                                      [a,z] -> [A,Z]

    DEC  SI
    MOV  [SI], DL
    INC  DI
    JMP  ReadInput
  DoneReversing:
    RET
STRING_REVERSE ENDP
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • What is up with people using `push` / `pop` to reverse things? If you're reading input 1 byte at a time, you can just store it in descending order starting at the end of the array. You're going to just print it anyway (i.e. copy it somewhere else) so it doesn't matter where in the buffer the string starts, as long as you have a pointer to that start. (Or if your read function returns the length, you can start there and leave the result at the start of the buffer). To be fair, the OP already had this code, but I would have at least mentioned that the whole thing only needs 1 pass. – Peter Cordes Dec 04 '20 at 19:57
  • @PeterCordes The *GET_STRING* that the OP uses retrieves the input in one go and the procedure does not deliver a count. I did add an alternative version using a second buffer. – Sep Roland Dec 04 '20 at 22:54
  • Oh right, yeah that would need another buffer, unless you `strlen` it so you can reverse in-place, doing a pair of characters at a time. (replicating the case-flip code). Read functions that don't return a length are dumb. – Peter Cordes Dec 04 '20 at 23:16