Author Topic: Can you do Select Case in assembly?  (Read 4303 times)

Offline ben321

  • Full Member
  • **
  • Posts: 100
Can you do Select Case in assembly?
« on: March 27, 2015, 04:23:47 AM »
Is there an x86 mnemonic for Select Case, where you have multiple possible values that AX can be (maybe 10 different valid values) and each one should jump to a different piece of code?

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2359
  • Country: us
Re: Can you do Select Case in assembly?
« Reply #1 on: March 27, 2015, 05:51:39 AM »
Sure. When the question is: "can I do this in assembly?", the answer is always "yes". Do you imagine that HLLs have access to some instructions that the guys who wrote Nasm didn't know about?

There's no special mnemonic, though. You just use the regular ones.

Either:
Code: [Select]
cmp ax, 1
je loadpic1
cmp ax, 2
je loadpic2
...
...
cmp ax, 10
je loadpic10
; and you probably want a "default"
jmp ask_again
Or:
Code: [Select]
; 1 to 10 in ax
; we want index to start with 0
dec ax
mov bx, ax ; pesky 16-bit addressing modes
add bx, bx ; addresses are two bytes each
jmp [table + bx]
...
; possibly in a ".data" section
; outside your execution path, anyway!
table dw loadpic1, loadpic2, loadpic3, ... loadpic10

Easier in 32-bit code, with the more flexible addressing modes: jmp [mytable + eax * 4] (with the table being "dd" of course).

(I know this as "switch case", but I'm pretty sure we're talking about the same thing.)

Best,
Frank


Offline ben321

  • Full Member
  • **
  • Posts: 100
Re: Can you do Select Case in assembly?
« Reply #2 on: March 27, 2015, 06:48:44 AM »
What is the easiest way to do "jump if the value is between this high number, and that low number, inclusive on both high and low"? Is there an easy way to do this?

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2359
  • Country: us
Re: Can you do Select Case in assembly?
« Reply #3 on: March 27, 2015, 08:42:43 AM »
Code: [Select]
cmp al, low
jb not_this_one
cmp al, high
ja not_this_one
this_one:
ASSuming unsigned numbers...

Best,
Frank


Offline Logman

  • Jr. Member
  • *
  • Posts: 16
  • Country: us
Re: Can you do Select Case in assembly?
« Reply #4 on: July 27, 2015, 02:53:02 AM »
This may be a little belated, but I notice folks still preview this topic.

I put together some macros that simulate SWITCH-CASE-ENDCASE capability for a book I am writing about Inline Assembly Code using NASM.

Code: [Select]
REM -----------------------------------------------------------------------
REM Section 9.10.2 Demonstrate SWITCH-CASE Selection Statements
REM   PACKAGE: CSTRUCT.inc
REM -----------------------------------------------------------------------
_asm
extern _printf

;----------------------------------------------------------------------
; SWITCH-CASE directives provide multiple-selection capability
; General form:
;   SWITCH expression
;     CASE argument1
;       statements
;       BREAK
;     CASE argumentN
;       statements
;       BREAK
;     DEFAULT
;       statements
;   ENDSWITCH
;----------------------------------------------------------------------

;----------------------------------------------------------------------
; SWITCH expression
;   expression : DWORD integer control value or TYPE range with IS
; PURPOSE: Macro provides multiple-selection capability
; USAGE: SWITCH directive initializes the control value or detects that
;        a TYPE range is being used as signified by the IS keyword. IS
;        tells SWITCH to make sure literals are of the same TYPE.
;----------------------------------------------------------------------
%imacro SWITCH 1-2
  %push SWITCH                    ; Push context
  %assign %$case 1                ; Assign 1 to the nextcase index
  %if %0 = 1                          ; Check if only one argument passed
    mov eax, dword %1             ; One argument = non-enumerated type
  %else                                  ; Two arguments = enumerated type
    %ifidni %2, IS                     ; Check for correct keyword
      %define __TYPE %1.type    ; Set __TYPE flag with argument TYPE
      %define __ARG %1            ; Preserve argument
      mov eax, dword %1           ; Copy argument value into EAX
    %else
      %error SWITCH %1, %2 uses invalid keyword %2 instead of IS.
    %endif
  %endif
%endmacro

;----------------------------------------------------------------------
; CASE argument
;   argument : comparison value, which must evaluate to an integer
; PURPOSE: Macro provides action statements if CASE statement is true
; USAGE: Compares CASE argument with SWITCH expression or TYPE range
;----------------------------------------------------------------------
%imacro CASE 1
  %ifctx SWITCH                   ; Verify context
    %$next%$case:
    %assign %$case %$case+1     ; Increment nextcase index value
    %ifdef __TYPE                 ; Check if TYPE flag is set with a type
      %assign _j __TYPE         ; Copy TYPE ID value to _j
      %ifn %1.type = _j         ; Verify literals are of the same TYPE
        %error __ARG and %1 literals are different TYPES.
      %endif
    %endif
    cmp eax, %1                     ; Compare SWITCH value with CASE value
    jne %$next%$case            ; Jump to next case if not equal
  %else
    %error "CASE is not allowed outside a SWITCH block."
  %endif
%endmacro

;----------------------------------------------------------------------
; DEFAULT (no parameters) (optional)
; PURPOSE: Macro provides default action statements when all CASE
;          statements fail. Use of DEFAULT case is optional.
; USAGE: Must be last selection statement before ENDSWITCH if used.
; WARNING: If the DEFAULT case is not used and none of the CASE
;          statements evaluate to true, then program flow passes to the
;          instruction following the ENDSWITCH directive.
;----------------------------------------------------------------------
%imacro DEFAULT 0
  %ifctx SWITCH                 ; Verify context
    %define __DEFAULT           ; Show DEFAULT case is present
    %$next%$case:
  %else
    %error "DEFAULT is not allowed outside a SWITCH block."
  %endif
%endmacro

;----------------------------------------------------------------------
; BREAK (no parameters)
; PURPOSE: Performs immediate exit from CASE statements
; USAGE: Used at the end of a CASE block when an early exit is desired.
;        There are no checks for missing BREAK directives.
;----------------------------------------------------------------------
%imacro BREAK 0
  %ifctx SWITCH                 ; Verify context
    jmp %$break                 ; Immediate exit from context block
  %elifctx IF
    jmp %$break
  %else
    %error "BREAK is not allowed outside a CASE block."
  %endif
%endmacro

;----------------------------------------------------------------------
; ENDSWITCH (no parameters)
; PURPOSE: Closes out SWITCH-CASE statement blocks and removes context
; USAGE: Must be used at the end of every SWITCH block. A missing
;        ENDSWITCH directive is indicated by a
;        '..@XX._endswitch undefined' error message
;----------------------------------------------------------------------
%imacro ENDSWITCH 0
  %ifctx SWITCH                 ; Verify context
    %$break:                    ; Jump here from a BREAK directive
    %ifndef __DEFAULT           ; Check if DEFAULT
      %$next%$case:             ; If not, make ENDSWITCH the nextcase
    %endif
    %undef __DEFAULT            ; Undefine __DEFAULT if defined
    %undef __TYPE               ; Undefine __TYPE if defined
    %undef __ARG
    %pop SWITCH                 ; Remove context from context stack
  %else
    %error "ENDSWITCH is not allowed outside a SWITCH block."
  %endif
%endmacro

;----------------------------------------------------------------------
; Supporting Macros
;----------------------------------------------------------------------
%imacro output.int 1
  push dword %1
  push fmtsint
  call _printf
  add esp, 8
%endmacro
;----------------------------------------------------------------------
section .data
  value dd 500
  orange dd 500
  fmtsint db "%lu", 10, 0
  red equ 100
  white equ 200
  blue equ 300
  true equ 1
  false equ 0
  Monday equ 1
  Tuesday equ 2
  Wednesday equ 3
  Thursday equ 4
  Friday equ 5
  Today equ 3
  Today.type equ 100
  Monday.type equ 100
  Wednesday.type equ 100

section .text
SWITCH 200          ; Control value = 200
CASE red            ; Evaluates to false
  output.int red
  BREAK
CASE white          ; Evaluates to true because white = 200
  output.int white  ; Print 200
  BREAK
CASE blue
  output.int blue
  BREAK
ENDSWITCH

SWITCH [value]      ; Control value = 500
  CASE red          ; These 3 CASE statements evaluate to false
  CASE white
  CASE blue
    output.int false
    BREAK
  CASE [orange]     ; Evaluates to true because orange = 500
    output.int true ; Print 1 (true)
    BREAK
  DEFAULT
    output.int false
ENDSWITCH

SWITCH Today, IS    ; Today = 3
  CASE Monday
    output.int Monday
    BREAK
  CASE Wednesday
    output.int Wednesday ; Print 3
    BREAK
  DEFAULT
    output.int 0
ENDSWITCH
_endasm

Don't worry about the "IS" keyword. I included this as part of the code to create and operate on enumeration types such as found in C or Ada. You can remove code dealing with this topic. Enumeration types and type checking are implemented using another set of macros. Note that I did not replicate a CONTINUE macro as I have really found no use for it in any of my assembly code that utilizes the SWITCH-CASE construct.

Logman
« Last Edit: July 27, 2015, 02:59:51 AM by Logman »