News:

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

Macro brains needed

Started by hutch--, December 26, 2004, 01:59:17 PM

Previous topic - Next topic

hutch--

Having seen a bit to much php lately folowed by apache server settings etc ... I am a little off the pace with a macro design that would be useful if it could be got going.
Here is the play version so far, what I am trying to get is a "switch" type control block for matching words in a simple and convenient way. Here is the first attempt to test out the concept.

The macros.

    switch$ MACRO lpstring
      LOCAL lbl1
      retadr equ <lbl1>
      tstval equ <lpstring>
    ENDM

    case$ MACRO quoted_text
      .if FUNC(szCmp,tstval,chr$(quoted_text)) != 0
    ENDM

    break$ MACRO
      jmp retadr
      .endif
    ENDM

    endsw$ MACRO
      retadr:
    ENDM

Next is the crude test code.

    switch$ lpstr
      case$ "word1"
        nop
        nop
        break$
      case$ "word2"
        nop
        nop
        break$
      case$ "word3"
        nop
        nop
        break$
    endsw$

It is rough at the moment, cannot be nested or duplicated but the idea sems to be sound if it can be got to work properly.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

Ratch

hutch--,
     I used to write MACROs like that, duplicating IF-THEN-ELSE, REPEAT, and other flow control code sequences so dear to the hearts of programmers who like structured code.  Maybe someone else knows a better way, but I found it necessary to keep a global variable that is increased by one each time the MACRO is called.  By tacking this variable onto the labels needed within the MACRO, a unique label is formed.  Otherwise you will generate the same label for each MACRO CALL, causing a duplicate label warning.  Anyway, the MACROs start to become a work of art from all the housekeeping necessary, and probably only the author wants to/can support them.  Sometimes the flow control statements are such that a single jump to a group end would avoid a series of cascaded jumps.  The C compiler seems to be able to figure that out, but MASM MACROS do not have enough features to determine the end of a cascaded jump.  At least I never could do it.  Those structures still do not have the flexibility straight coding does, and I have tended not to use them.  They are a "poor man's version of C".  I prefer to minimize spagetti code, indent and annotate to show the "structured" flow, but that is me.  Others have to find their own best system.  Ratch

MichaelW

I think I finally got this worked out, but it needs some more testing.

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    .486                       ; create 32 bit code
    .model flat, stdcall       ; 32 bit memory model
    option casemap :none       ; case sensitive

    include \masm32\include\windows.inc
    include \masm32\include\masm32.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc

    includelib \masm32\lib\masm32.lib
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

    include \masm32\macros\macros.asm

    printv MACRO varname:VARARG
      FOR arg, <varname>
        invoke StdOut, reparg(arg)
      ENDM
    ENDM
    NL equ SADD(13,10)

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros for storing and retrieving text macros, based on
; the $casstk code from Greg Falen's Switch/Case macros.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $text_stack$ equ <#>

    pushtext MACRO name:req
        $text_stack$ CATSTR <name>, <#>, $text_stack$
    ENDM

    poptext MACRO name:req
        LOCAL pos
        pos INSTR $text_stack$, <#>
        name SUBSTR $text_stack$, 1, pos-1
        $text_stack$ SUBSTR $text_stack$, pos+1
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros to implement a string-comparison specific
; Switch/Case construct. Multiple instances and
; nesting supported.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $tstval$ equ <>
    $end_sw$ equ <>
    _sw_cnt_ = 0

    switch$ MACRO lpstring:REQ
        ; Preserve values for previous Switch/Case.
        pushtext $tstval$
        pushtext $end_sw$

        $tstval$ equ <lpstring>
        $tstval$ CATSTR <ADDR >, $tstval$

        ; Generate unique label and preserve it.
        _sw_cnt_ = _sw_cnt_ + 1
        $end_sw$ CATSTR <end_sw>, %_sw_cnt_
        pushtext $end_sw$
    ENDM

    case$ MACRO quoted_text:REQ
      .IF FUNC(szCmp, $tstval$, chr$(quoted_text)) != 0
    ENDM

    break$ MACRO
        ; Because there could have been an intervening
        ; Switch/Case, we need to recover the correct
        ; label for this Switch/Case.
        poptext $end_sw$
        pushtext $end_sw$

        jmp   $end_sw$

      .ENDIF
    ENDM

    endsw$ MACRO
        ; Remove label from stack.
        poptext $end_sw$

      $end_sw$:

        ; Recover values for previous Switch/Case.     
        poptext $end_sw$
        poptext $tstval$
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    .data
        buffer  db 30 dup(0)
        str1    db "str1",0
        str2    db "str2",0
        str3    db "str3",0
        str4    db "str4",0
        str5    db "str5",0
    .code

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    switch$ str1
      case$ "str1"
        printv chr$("switch$ str1, str1"), NL
        switch$ str2
          case$ "str1"
            printv chr$("switch$ str2, str1"), NL
            break$
          case$ "str2"
            printv chr$("switch$ str2, str2"), NL
            switch$ str3
              case$ "str1"
                printv chr$("switch$ str3, str1"), NL
                break$
              case$ "str2"
                printv chr$("switch$ str3, str2"), NL
                break$
              case$ "str3"
                printv chr$("switch$ str3, str3"), NL
                switch$ str4
                  case$ "str1"
                    printv chr$("switch$ str4, str1"), NL
                    break$
                  case$ "str2"
                    printv chr$("switch$ str4, str2"), NL
                    break$
                  case$ "str3"
                    printv chr$("switch$ str4, str3"), NL
                    break$
                  case$ "str4"
                    printv chr$("switch$ str4, str4"), NL
                    break$
                endsw$ ; str4
                break$
              case$ "str4"
                printv chr$("switch$ str3, str4"), NL
                break$
            endsw$ ;str3
            break$
          case$ "str3"
            printv chr$("switch$ str2, str3"), NL
            break$
          case$ "str4"
            printv chr$("switch$ str2, str4"), NL
            break$
        endsw$ ; str2
        break$
      case$ "str2"
        printv chr$("switch$ str1, str2"), NL
        break$
      case$ "str3"
        printv chr$("switch$ str1, str3"), NL
        break$
      case$ "str4"
        printv chr$("switch$ str1, str4"), NL
        break$
    endsw$ ; str1

    switch$ str5
      case$ "str1"
        printv chr$("switch$ str5, str1"), NL
        break$
      case$ "str2"
        printv chr$("switch$ str5, str2"), NL
        break$
      case$ "str3"
        printv chr$("switch$ str5, str3"), NL
        break$
      case$ "str4"
        printv chr$("switch$ str5, str4"), NL
        break$
      case$ "str5"
        printv chr$("switch$ str5, str5"), NL
        break$
    endsw$ ;str5

    printv NL, chr$("Press enter to exit..."), NL
    invoke StdIn, addr buffer, 1
    exit

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start



I was hoping to use .IF/.ELSEIF/.ENDIF and eliminate the break statement, but I could find no way to make it work. Basically, the problem was that szCmp must be called for each case, and the generated jump for the else condition always bypassed the call. It would seem that MASM is determining the proper location for the jump destination by looking for the condition test code.
eschew obfuscation

hutch--

#3
Michael,

This looks good, I will try and get a bit more brain running at the moment to have a play with it. Have you given it a decent hammering as far as testing ?

LATER: I removed the line,

        ;; $tstval$ CATSTR <ADDR >, $tstval$    ; <<<<<<<<<<<< disabled


From the switch$ macro so it would take a raw address and it works perfectly. About all we need now is an "else$" for default processing and the macro is up and running. I put it in a test piece but the forum upgrade will not allow file attachments at the moment so I cannot post it. This is the test procedure.


tstsw proc lpstr:DWORD

    switch$ lpstr
      case$ "word1"
        fn MessageBox,hWnd,"word1","Title",MB_OK
        break$
      case$ "word2"
        fn MessageBox,hWnd,"word2","Title",MB_OK
        break$
      case$ "word3"
        fn MessageBox,hWnd,"word3","Title",MB_OK
        break$
    endsw$

    ret

tstsw endp
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

hutch--

Easy  :P


    else$ MACRO
      ;; do nothing
    ENDM

Add the else as follows,

tstsw proc lpstr:DWORD

    switch$ lpstr
      case$ "word1"
        fn MessageBox,hWnd,"word1","Title",MB_OK
        break$
      case$ "word2"
        fn MessageBox,hWnd,"word2","Title",MB_OK
        break$
      case$ "word3"
        fn MessageBox,hWnd,"word3","Title",MB_OK
        break$
      else$
        fn MessageBox,hWnd,"Default","Title",MB_OK
    endsw$

    ret

tstsw endp

This could probably do with some tidying up but it seems to work OK.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

I added the "$tstval$ CATSTR <ADDR >, $tstval$" as a quick adaptation to make the macro work in my test program, and then amid all the changes I forgot to remove it and rework the program code.

My function tests consisted mostly of variations of the test code I posted. These tests were sufficient to spot problems in all of the various arrangements I tried before I arrived at this one. In the course of diagnosing the problems I used the .ERR directive, first-pass listings, and disassembled listings, but once I had a version that preformed correctly in the tests I did not use these methods to verify that it was in fact working as I intended.

There are probably multiple ways to misuse these macros, and beyond the required parameters I made no attempt to detect programmer errors. For the pushtext and poptext macros I initially included code to check for a stack empty condition, but I eventually decided that any programmer who might need such a feature would have larger problems than just applying these macros. I think MASM will detect most of the likely sequencing errors (case$ w/o switch$, endsw$ w/o switch$, case$ w/o break$, etc).

You could achieve the same effect as else$ by just placing the else condition code between the last break$ and the endsw$, but making it obvious with an empty else$ macro is a better design.

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    .486                       ; create 32 bit code
    .model flat, stdcall       ; 32 bit memory model
    option casemap :none       ; case sensitive

    include \masm32\include\windows.inc
    include \masm32\include\masm32.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc

    includelib \masm32\lib\masm32.lib
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

    include \masm32\macros\macros.asm

    printv MACRO varname:VARARG
      FOR arg, <varname>
        invoke StdOut, reparg(arg)
      ENDM
    ENDM
    NL equ SADD(13,10)

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros for storing and retrieving text macros, based on
; the $casstk code from Greg Falen's Switch/Case macros.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $text_stack$ equ <#>

    pushtext MACRO name:req
        $text_stack$ CATSTR <name>, <#>, $text_stack$
    ENDM

    poptext MACRO name:req
        LOCAL pos
        pos INSTR $text_stack$, <#>
        name SUBSTR $text_stack$, 1, pos-1
        $text_stack$ SUBSTR $text_stack$, pos+1
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros to implement a string-comparison specific
; Switch/Case construct. Multiple instances and
; nesting supported.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $tstval$ equ <>
    $end_sw$ equ <>
    _sw_cnt_ = 0

    switch$ MACRO lpstring:REQ
        ;; Preserve values for previous Switch/Case.
        pushtext $tstval$
        pushtext $end_sw$

        $tstval$ equ <lpstring>

        ;; Generate unique label and preserve it.
        _sw_cnt_ = _sw_cnt_ + 1
        $end_sw$ CATSTR <end_sw>, %_sw_cnt_
        pushtext $end_sw$
    ENDM

    case$ MACRO quoted_text:REQ
      .IF FUNC(szCmp, $tstval$, chr$(quoted_text)) != 0
    ENDM

    break$ MACRO
        ;; Because there could have been an intervening
        ;; Switch/Case we need to recover the correct
        ;; label for this Switch/Case.
        poptext $end_sw$
        pushtext $end_sw$

        jmp   $end_sw$

      .ENDIF
    ENDM

    else$ MACRO
        ;; Do nothing.
    ENDM

    endsw$ MACRO
        ;; Remove label from stack.
        poptext $end_sw$

      $end_sw$:

        ;; Recover values for previous Switch/Case.     
        poptext $end_sw$
        poptext $tstval$
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    .data
        buffer  db 30 dup(0)
        str1    db "str1",0
        str2    db "str2",0
        str3    db "str3",0
        str4    db "str4",0
        str5    db "str5",0
        str6    db "xxxx",0
    .code

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    switch$ ADDR str1
      case$ "str1"
        printv chr$("switch$ str1, str1"), NL
        switch$ ADDR str2
          case$ "str1"
            printv chr$("switch$ str2, str1"), NL
            break$
          case$ "str2"
            printv chr$("switch$ str2, str2"), NL
            switch$ ADDR str3
              case$ "str1"
                printv chr$("switch$ str3, str1"), NL
                break$
              case$ "str2"
                printv chr$("switch$ str3, str2"), NL
                break$
              case$ "str3"
                printv chr$("switch$ str3, str3"), NL
                switch$ ADDR str4
                  case$ "str1"
                    printv chr$("switch$ str4, str1"), NL
                    break$
                  case$ "str2"
                    printv chr$("switch$ str4, str2"), NL
                    break$
                  case$ "str3"
                    printv chr$("switch$ str4, str3"), NL
                    break$
                  case$ "str4"
                    printv chr$("switch$ str4, str4"), NL
                    break$
                endsw$ ; str4
                break$
              case$ "str4"
                printv chr$("switch$ str3, str4"), NL
                break$
            endsw$ ;str3
            break$
          case$ "str3"
            printv chr$("switch$ str2, str3"), NL
            break$
          case$ "str4"
            printv chr$("switch$ str2, str4"), NL
            break$
        endsw$ ; str2
        break$
      case$ "str2"
        printv chr$("switch$ str1, str2"), NL
        break$
      case$ "str3"
        printv chr$("switch$ str1, str3"), NL
        break$
      case$ "str4"
        printv chr$("switch$ str1, str4"), NL
        break$
    endsw$ ; str1

    switch$ ADDR str5
      case$ "str1"
        printv chr$("switch$ str5, str1"), NL
        break$
      case$ "str2"
        printv chr$("switch$ str5, str2"), NL
        break$
      case$ "str3"
        printv chr$("switch$ str5, str3"), NL
        break$
      case$ "str4"
        printv chr$("switch$ str5, str4"), NL
        break$
      case$ "str5"
        printv chr$("switch$ str5, str5"), NL
        break$
    endsw$ ;str5

    switch$ ADDR str6
      case$ "str1"
        printv chr$("switch$ str6, str1"), NL
        break$
      case$ "str2"
        printv chr$("switch$ str6, str2"), NL
        break$
      case$ "str3"
        printv chr$("switch$ str6, str3"), NL
        break$
      case$ "str4"
        printv chr$("switch$ str6, str4"), NL
        break$
      case$ "str5"
        printv chr$("switch$ str6, str5"), NL
        break$
      else$
        printv chr$("switch$ str6, else"), NL
    endsw$ ;str6

    printv NL, chr$("Press enter to exit..."), NL
    invoke StdIn, addr buffer, 1
    exit

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start


; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; This is a test of the pushtext and poptext macros.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    .486                       ; create 32 bit code
    .model flat, stdcall       ; 32 bit memory model
    option casemap :none       ; case sensitive
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $text_stack$ equ <#>

    pushtext MACRO name:req
      $text_stack$ CATSTR <name>, <#>, $text_stack$
    ENDM

    poptext MACRO name:req
      LOCAL pos
      pos INSTR $text_stack$, <#>
      name SUBSTR $text_stack$, 1, pos-1
      $text_stack$ SUBSTR $text_stack$, pos+1
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    .data
    .code
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    text1 equ <aa>
    text2 equ <bb>
    text3 equ <cc>
    text4 equ <> 

    .err $text_stack$
    pushtext text1
    .err $text_stack$
    pushtext text2
    .err $text_stack$
    pushtext text3
    .err $text_stack$
    poptext text4
    .err text4
    .err $text_stack$
    poptext text4
    .err text4
    .err $text_stack$
    poptext text4
    .err text4
    .err $text_stack$

    ; Stack is empty
    ; "error A2091: index value past end of string"
    poptext junk

    pushtext <byob>
    .err $text_stack$
    poptext junk
    .err $text_stack$
    .err junk

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start

eschew obfuscation

hutch--

Michael,

I am really pleased with the result, its clear to read and the underlying code generation is good as well. Now I wonder if its worth the effort to remove the break$ macro by changing the case$ macro to place an ".endif" before every ".if" except for the first one. It would have the logic as follows.


; ----------------
.if condition      ; 1st instance
; ----------------

; ----------------
.endif
.if condition      ; following instance(s)
; ----------------

; ----------------
.endif              ; last instance
; ----------------


Let me know what you think here as the stuff you have done so far is great.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

OK, I think I have a good implementation without break$. I looked into adding code that would generate meaningful error messages for the most likely syntax errors, but I decided that it would not be worth the substantial effort that would be required. The functionality, macro expansions, and generated code all look OK to me, but sometimes I can be blind to very obvious errors.

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    .486                       ; create 32 bit code
    .model flat, stdcall       ; 32 bit memory model
    option casemap :none       ; case sensitive

    .nolist

    include \masm32\include\windows.inc
    include \masm32\include\masm32.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc

    includelib \masm32\lib\masm32.lib
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib

    include \masm32\macros\macros.asm

    printv MACRO varname:VARARG
      FOR arg, <varname>
        invoke StdOut, reparg(arg)
      ENDM
    ENDM
    NL equ SADD(13,10)

    .listall

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros for storing and retrieving text macros, based on
; the $casstk code from Greg Falen's Switch/Case macros.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $text_stack$ equ <#>

    pushtext MACRO name:req
        $text_stack$ CATSTR <name>, <#>, $text_stack$
    ENDM

    poptext MACRO name:req
        LOCAL pos
        pos INSTR $text_stack$, <#>
        name SUBSTR $text_stack$, 1, pos-1
        $text_stack$ SUBSTR $text_stack$, pos+1
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros to implement a string-comparison specific
; Switch/Case construct. Multiple instances and
; nesting supported.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $test_val$ equ <>
    $end_sw$ equ <>
    $sw_state$ equ <>
    _sw_cnt_ = 0

    switch$ MACRO lpstring:REQ

        ;; Preserve globals for previous Switch/Case.
        pushtext $test_val$
        pushtext $sw_state$
        pushtext $end_sw$

        ;; Copy string address for this Select/Case
        ;; to global so case$ can access it.
        $test_val$ equ <lpstring>
               
        ;; Set state global to starting value.
        $sw_state$ equ <>

        ;; Generate a unique exit label for this
        ;; Select/Case and preserve it.
        _sw_cnt_ = _sw_cnt_ + 1
        $end_sw$ CATSTR <end_sw>, %_sw_cnt_
        pushtext $end_sw$
    ENDM

    case$ MACRO quoted_text:REQ
        ;; The case statements will be any statements
        ;; between the case$ and the following case$,
        ;; else$, or endsw$.
        ;;
        ;; If this is a following case$, emit a jump
        ;; to the exit label for this Select/Case and
        ;; terminate the .IF block.       
        IFIDN $sw_state$, <if>

          ;; Because there could have been an intervening
          ;; Switch/Case we need to recover the correct
          ;; exit label for this Switch/Case.
          poptext $end_sw$
          pushtext $end_sw$

          jmp   $end_sw$

          .ENDIF
        ENDIF

        ; Start a new .IF block and update the state global.
        .IF FUNC(szCmp, $test_val$, chr$(quoted_text)) != 0
        $sw_state$ equ <if>
    ENDM

    else$ MACRO
        ;; If following a case$, terminate the .IF block.
        ;; The state global must be updated to stop the
        ;; endsw$ from terminatinmg the .IF block.
        IFIDN $sw_state$, <if>
          .ENDIF
          $sw_state$ equ <>
        ENDIF
    ENDM

    endsw$ MACRO
        ; If following a case$, terminate the .IF block.
        IFIDN $sw_state$, <if>
          .ENDIF
        ENDIF

        ;; Remove the exit label from the stack.
        poptext $end_sw$

      $end_sw$:

        ;; Recover gobals for previous Switch/Case.
        poptext $end_sw$
        poptext $sw_state$
        poptext $test_val$
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    .data
        buffer  db 30 dup(0)
        str1    db "str1",0
        str2    db "str2",0
        str3    db "str3",0
        str4    db "str4",0
        str5    db "str5",0
        str6    db "xxxx",0
    .code

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    switch$ ADDR str1
      case$ "str1"
        printv chr$("switch$ str1, str1"), NL
        switch$ ADDR str2
          case$ "str1"
            printv chr$("switch$ str2, str1"), NL
          case$ "str2"
            printv chr$("switch$ str2, str2"), NL
            switch$ ADDR str3
              case$ "str1"
                printv chr$("switch$ str3, str1"), NL
              case$ "str2"
                printv chr$("switch$ str3, str2"), NL
              case$ "str3"
                printv chr$("switch$ str3, str3"), NL
                switch$ ADDR str4
                  case$ "str1"
                    printv chr$("switch$ str4, str1"), NL
                  case$ "str2"
                    printv chr$("switch$ str4, str2"), NL
                  case$ "str3"
                    printv chr$("switch$ str4, str3"), NL
                  case$ "str4"
                    printv chr$("switch$ str4, str4"), NL
                endsw$ ; str4
              case$ "str4"
                printv chr$("switch$ str3, str4"), NL
            endsw$ ;str3
          case$ "str3"
            printv chr$("switch$ str2, str3"), NL
          case$ "str4"
            printv chr$("switch$ str2, str4"), NL
        endsw$ ; str2
      case$ "str2"
        printv chr$("switch$ str1, str2"), NL
      case$ "str3"
        printv chr$("switch$ str1, str3"), NL
      case$ "str4"
        printv chr$("switch$ str1, str4"), NL
    endsw$ ; str1

    switch$ ADDR str5
      case$ "str1"
        printv chr$("switch$ str5, str1"), NL
      case$ "str2"
        printv chr$("switch$ str5, str2"), NL
      case$ "str3"
        printv chr$("switch$ str5, str3"), NL
      case$ "str4"
        printv chr$("switch$ str5, str4"), NL
      case$ "str5"
        printv chr$("switch$ str5, str5"), NL
    endsw$ ;str5

    switch$ ADDR str6
      case$ "str1"
        printv chr$("switch$ str6, str1"), NL
      case$ "str2"
        printv chr$("switch$ str6, str2"), NL
      case$ "str3"
        printv chr$("switch$ str6, str3"), NL
      case$ "str4"
        printv chr$("switch$ str6, str4"), NL
      case$ "str5"
        printv chr$("switch$ str6, str5"), NL
      else$
        printv chr$("switch$ str6, else"), NL
    endsw$ ;str6

    printv NL, chr$("Press enter to exit..."), NL
    invoke StdIn, addr buffer, 1
    exit

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start

eschew obfuscation

hutch--

 :bg

I think this one has found the Holy Grail. All I did was to copy and paste the code for the jump from the "case$" macro into the "else$" macro and its working perfectly so far. Code generation is correct and the output looks very efficient.

Now that I have some new smilies to give cheek with, this new set of macros is   :U :dance::clap:  TRULY GREAT :clap: :dance: :U

Let me know if you are happy with the small mod and I think its ready to rock 'n roll.

[attachment deleted by admin]
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

hutch--

Here is the code generation for the above macros.


  fn_004015C1:
    push ebp
    mov ebp, esp

    push 403096h
    push DWORD PTR [ebp+8]
    call fn_00401620        ; case$ 1
    or eax, eax
    jz lbl0
    nop
    nop
    jmp lbl4

  lbl0:
    push 40309Ch
    push DWORD PTR [ebp+8]
    call fn_00401620        ; case$ 2
    or eax, eax
    jz lbl1
    nop
    nop
    jmp lbl4

  lbl1:
    push 4030A2h
    push DWORD PTR [ebp+8]
    call fn_00401620        ; case$ 3
    or eax, eax
    jz lbl2
    nop
    nop
    jmp lbl4

  lbl2:
    push 4030A8h
    push DWORD PTR [ebp+8]
    call fn_00401620        ; case$ 4
    or eax, eax
    jz lbl3
    nop
    nop
    jmp lbl4

  lbl3:                     ; else$
    nop
    nop

  lbl4:
    leave
    ret 4
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

As I stated, sometimes I can be blind to very obvious errors. I should have tested the else$ macro under conditions where the preceding case was true. I'll blame it on the slight hangover I had this morning :green

For consistency I moved the jump into the IFIDN block, and doing so had the (probably useless) side effect of allowing a switch/else/endsw block to function correctly.

    else$ MACRO
        ;; If following a case$, emit a jump to the exit
        ;; label for this Select/Case and terminate the .IF
        ;; block. The jump is necessary, whenever the case
        ;; for the .IF block being terminated is true, to
        ;; bypass the else statements that follow.
        IFIDN $sw_state$, <if>
          poptext $end_sw$
          pushtext $end_sw$
          jmp   $end_sw$
          .ENDIF

          ;; The state global must be updated to stop the
          ;; endsw$ from terminatinmg the .IF block.       
          $sw_state$ equ <>
        ENDIF
    ENDM

eschew obfuscation

hutch--

I wish I still had the luxury but my days of hangovers are over.  :P

I have encorporated the change, changed some of the formatting so the macros are a bit easier to read, put the credit at the top and a syntax example on how it is to be used. Code is as follows. Give it the nod and it goes into "macros.inc" so that everyone else can use it.

comment * ------------------------------------------------
        The following macro system for a string comparison
        switch block was designed by Michael Webster.

SYNTAX:

    switch$ string_address          ; adress of zero terminated string

      case$ "quoted text"           ; first string to test against
        ; your code here

      case$ "another quoted text"   ; optional additional quoted text
        ; your code here

      else$                         ; optional default processing
        ; default code here

    endsw$

        ------------------------------------------------ *

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros for storing and retrieving text macros, based on
; the $casstk code from Greg Falen's Switch/Case macros.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $text_stack$ equ <#>

    pushtext MACRO name:req
        $text_stack$ CATSTR <name>, <#>, $text_stack$
    ENDM

    poptext MACRO name:req
        LOCAL pos
        pos INSTR $text_stack$, <#>
        name SUBSTR $text_stack$, 1, pos-1
        $text_stack$ SUBSTR $text_stack$, pos+1
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
; Macros to implement a string-comparison specific
; Switch/Case construct. Multiple instances and
; nesting supported.
; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

    $test_val$ equ <>
    $end_sw$ equ <>
    $sw_state$ equ <>
    _sw_cnt_ = 0

    switch$ MACRO lpstring:REQ
        pushtext $test_val$                 ;; Preserve globals for previous Switch/Case.
        pushtext $sw_state$
        pushtext $end_sw$

        $test_val$ equ <lpstring>           ;; Copy string address for this Select/Case
                                            ;; to global so case$ can access it.             
        $sw_state$ equ <>                   ;; Set state global to starting value.
        _sw_cnt_ = _sw_cnt_ + 1             ;; Generate a unique exit label for this
        $end_sw$ CATSTR <end_sw>, %_sw_cnt_ ;; Select/Case and preserve it.
        pushtext $end_sw$
    ENDM

    case$ MACRO quoted_text:REQ
        ;; The case statements will be any statements between the case$ and the following case$,
        ;; else$, or endsw$.
        ;;
        ;; If this is a following case$, emit a jump to the exit label for this Select/Case and
        ;; terminate the .IF block.
        ;; --------------------------------
        IFIDN $sw_state$, <if>
          poptext $end_sw$                  ;; Because there could have been an intervening
          pushtext $end_sw$                 ;; Switch/Case we need to recover the correct
          jmp   $end_sw$                    ;; exit label for this Switch/Case.
          .ENDIF
        ENDIF
        ;; --------------------------------
        ;; Start a new .IF block and update the state global.
        .IF FUNC(szCmp, $test_val$, chr$(quoted_text)) != 0
        $sw_state$ equ <if>
    ENDM

    else$ MACRO
        IFIDN $sw_state$, <if>              ;; If following a case$, emit a jump to the exit
          poptext $end_sw$                  ;; label for this Select/Case and terminate the .IF
          pushtext $end_sw$                 ;; block. The jump is necessary, whenever the case
          jmp   $end_sw$                    ;; for the .IF block being terminated is true, to
          .ENDIF                            ;; bypass the else statements that follow.
          $sw_state$ equ <>                 ;; The state global must be updated to stop the
        ENDIF                               ;; endsw$ from terminatinmg the .IF block.
    ENDM

    endsw$ MACRO
        IFIDN $sw_state$, <if>              ;; If following a case$, terminate the .IF block.
          .ENDIF
        ENDIF

        poptext $end_sw$                    ;; Remove the exit label from the stack.

      $end_sw$:

        poptext $end_sw$                    ;; Recover gobals for previous Switch/Case.
        poptext $sw_state$
        poptext $test_val$
    ENDM

; ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MichaelW

#12
It looks OK to me, Hutch, except the syntax example doesn't make it clear that you can use an unlimited number of case clauses.

I have now tested case statements following an imbedded switch$, a switch$ embedded in an else clause, and an .IF/ENDIF block in a case clause, and everything worked OK.
eschew obfuscation

Randall Hyde

Hi Hutch,
Be sure to download the "UCR Standard Library v2.0" code from Webster.
Though there is no "case/switch" statement there, your macro development is proceeding along a similar path that my experiments took in the middle 1990's. Though I eventually gave up (and wrote HLA to get what I really wanted), there are still a lot of good ideas in that code. Too bad it broke when MASM v6.11d came along (it was written for v6.11a), but many of the ideas are still valid.  I mention this, because I notice that the techniques you're using to "push and pop labels" to achieve nesting is virtually identical to what I did 10 years ago.
Cheers,
Randy Hyde

hutch--

Randy,

Thanks for the info, its always useful to tread a path that someone else has done before as it saves some of the hard work yet I must say I am very pleased with the latest macro from Michael, its testing up very well, I like the code output quality and it makes a particular common task a lot easier and cleaner to code.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php