News:

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

Console Menus

Started by Neil, August 29, 2008, 04:25:24 PM

Previous topic - Next topic

Neil

I have a console program with a number of menus & I use the following method to select a menu item :-

                        getkey
                        .if al==1
                         do something
                             .elseif al==2
                              do something else
                              etc
                              etc
                         .endif

Having read through some of the earlier posts it seems that the 'C' style case (which I'm not familiar with) is the preferred method of dealing with a menu. Now is this just a matter of personal style or is there some advantage in using case.

Mirno

Case statements under C (and I assume other languages) can be optimised by the compiler.
Basically you can think of them as if/else trees, but in reality the compiler will do things like balance the if/else tree, or build a lookup table (or both).

It's one of the things compilers are very good at, the task boils down to rules very easily (size and speed of the various options are easily computed, and the best can be picked fairly simply). Also it's something the human mind isn't so good at optimising because it's very dull!

Fortunately for you, the case (excuse the pun) you're dealing with is almost certainly best off using a lookup table (unless your menu only has 1 or two items). You have n options running from 0 to n-1, so the LUT is fully populated (no pesky gaps wasting space). Just build it as a jump table use the index of the menu item as the index of the LUT, and jump.
If you're not worried about size, then an if/else tree is probably the most readable though.

Mirno

Mark Jones

Also, if you're processing many options, (and especially if you want them to be case-insenstive) then consider a lookup table. Use the maketbl.exe tool included with MASM32 to create the table, and edit that data accordingly. i.e. (GoASM Format):


...
; option found, set boolean and convert pointer to -1 as a flag
; that it has been handled...
mov ebx,addr tb ; preset xlat table offset
ot: mov d[edi],-1 ; mark pointer as "handled"
add esi,2 ; point to next option char (unicode)
movzx eax,b[esi] ; load it into al
cmp al,0 ; at the end of these options?
je < ; restart at next arg
xlatb ; then translate it

cmp al,-1 ; unknown option
jne >
fk: ; output "unknown option" message
jmp <ot
: cmp al,"C" ; option C
jne >
mov b[OptionC],1 ; set option byte
#ifdef debug
; output "Option C selected" message
#endif
jmp <ot
: cmp al,"D" ; option D
jne >
mov b[OptionD],1
#ifdef debug
; output "Option D selected" message
#endif
jmp <ot
: cmp al,"H" ; option: help
jne >
jmp >showhelp
:
...
ret
align 4
tb: db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ; xlat table (full ascii)
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db 0,1,2,3,4,5,6,7,8,9,58,-1,-1,61,-1,-1 ; 0-9, :, =
db -1,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 ; 67 = C
db 80,81,82,83,84,85,86,87,88,89,90,-1,-1,-1,-1,-1
db -1,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 ; 99 = c
db 80,81,82,83,84,85,86,87,88,89,90,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ; all returned
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ; letters caps
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
db -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1


The CMP/JNE code could be replaced with IF/THEN or SWITCH/CASE in MASM32. This code is part of a much larger section which has stored the offsets to option chars as an array of pointers. Each pointer is loaded and dereferenced, and then byte zero-extended (MOVZX) into AL (so that only AL contains the source char.) The table offset is then loaded as EBX, and then XLATB returns in AL the byte at offset [tb + AL]. Thus, since capital bytes are returned for both capital and lower-case source bytes, this effectively creates case-insensitivity. (Also note it returns mathematical values 0-9 for ASCII values "0"-"9".)

Of course, another way of accomplishing this would be to test the value of the byte and see if it is between a certain range, and add or subtract a static value to bring the character into the range of the other letter case. Lookup tables have the advantage of being very fast and more versatile.
"To deny our impulses... foolish; to revel in them, chaos." MCJ 2003.08

Neil

Thanks Mirno & Mark Jones, You gave me a lot to think about & it appears to me that it all boils down to efficient code as against readable code. I appreciate that a jump table is the most efficient way of doing things but it is also the most unreadable way of doing it. So if 'case' does create a jump table & 'case' is just as readable as an .if .elseif tree then that seems to be the way to go. My menus contain no more than 9 items so that I can use the digits 1 to 9, so a jump table maybe slightly over the top in this instance, correct me if I'm wrong, as I said I'm not familiar with 'case' so maybe I'm jumping to conclusions.

hutch--

Neil,

How many options are you talking about ? I seriously doubt that a set of options displayed at the console that wais for a keystroke is a time critical task, the normal MASM .IF block will do the job fine and be easier to come back to later if you want to change it.

For what its worth the MASM32 MACRO system easily emulates a "switch" block which is a reduced form of the .IF block and it uses the .IF block mechanism to do so.

If you are doing truly speed critical work with a numerical input that is within a range with no gaps you can drop the branch time to one jump with a table of addresses but that is somewat different to waiting for a keystroke.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Neil

Thanks Hutch, no my application is not time criitcal. I use menus (as I did in my DOS days) to access various parts of my program so most of the time it's waiting for a keypress. I'm quite happy with the .If .ELSEIF tree, it's readable & does the job much better than doing a compare & jump (as in DOS). I  was just curious as to why other posts (I think one of them was yours) suggested using a switch block.

jj2007

Quote from: Neil on August 30, 2008, 07:34:57 AM
I  was just curious as to why other posts (I think one of them was yours) suggested using a switch block.

Readability :P

include \masm32\include\masm32rt.inc

.code
start:
print "Press any key, and Escape to exit", 13, 10

.While 1

getkey
or eax, 32 ; make lowercase, case-insensitive

SWITCH eax
CASE "a"
print "You pressed a or A", 13, 10
CASE "b"
print "You pressed b or B", 13, 10
CASE "5"
print "You pressed 5", 13, 10
CASE 27 or 32
.break
DEFAULT
push eax
print "You pressed another key: "
pop eax
print str$(eax), 13, 10
ENDSW

.Endw
exit
end start


Neil

Thanks jj2007, I'll try it out in my program :thumbu

jj2007

Quote from: Neil on August 30, 2008, 10:09:50 AM
Thanks jj2007, I'll try it out in my program :thumbu

Have a look at the resulting exe with OllyDbg - you will be surprised how efficient the code produced by that macro is.

@Hutch: The SWITCH macro creates mov eax, eax. Since Switch eax is a frequent case, it might deserve the little change in blue.

    switch macro _var:req, _reg:=<eax>
      ifdifi <eax>, <_var>
              mov _reg, _var
      endif

hutch--

JJ,

It would save a reg to reg copy but I wonder whether it matters much.

In Neil's case if you were fussy you would code,


switch rv(getkey)
  case ???
  etc ....
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jj2007

Quote from: hutch-- on August 30, 2008, 12:36:28 PM
JJ,

It would save a reg to reg copy but I wonder whether it matters much.

Well, it saves an evil destructive and absolutely useless mov eax, eax at no extra cost :wink

Quote
In Neil's case if you were fussy you would code,


switch rv(getkey)
  case ???
  etc ....


Did you test this? For rv(getkey) I get error A2148:invalid symbol type in expression : getkey

Can't possibly work:

    getkey MACRO
      call ret_key
    ENDM

    rv MACRO FuncName:REQ,args:VARARG
      arg equ <invoke FuncName>         ;; construct invoke and function name
      FOR var,<args>                    ;; loop through all arguments
        arg CATSTR arg,<,reparg(var)>   ;; replace quotes and append arg
      ENDM
      arg                               ;; write the invoke macro
      EXITM <eax>                       ;; EAX as the return value
    ENDM

hutch--

:bg

> Did you test this?

Evidently not, it should have been the procedure form, not a macro.


; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
    include \masm32\include\masm32rt.inc
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

comment * -----------------------------------------------------
                        Build this  template with
                       "CONSOLE ASSEMBLE AND LINK"
        ----------------------------------------------------- *

    .data?
      value dd ?

    .data
      item dd 0

    .code

start:
   
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    call main
    inkey
    exit

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

main proc

    print "Press space or ESC",13,10

    switch rv(ret_key)
      case 27
        print "ESC",13,10
        ret
      case 32
        print "space",13,10
        ret
    endsw

    ret

main endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

end start


> Well, it saves an evil destructive and absolutely useless mov eax, eax at no extra cost wink

You are probably correct here but you would be the only person on the planet who could type fast enough to notice it.  :bg
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

BlackVortex

@ hutch
Ok, noone can notice in runtime, but the few bytes filesize difference can be seen !

So, it's a "noticeable" difference     :cheekygreen:

jj2007

Quote from: BlackVortex on August 30, 2008, 03:27:29 PM
So, it's a "noticeable" difference     :cheekygreen:

Apart from the tremendous waste of precious CPU resources, mov eax, eax is the kind of little nuisance that would be readily used as proof that C compilers are stupid, or, more likely in this forum, as proof that macros are no good, especially for noobs, intermediate and seasoned programmers. So let's show our intelligence and eliminate it  :8)

      ifdifi <eax>, <_var>
              mov _reg, _var
      endif

By the way: What about a specialised macro sub-forum?

hutch--

BlackVortex,

You must type as fast as JJ to notice it.  :bg

JJ,

The Lab IS the place for specialised macros.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php