Dotneteer / spectnetide

ZX Spectrum IDE with Visual Studio 2017 and 2019 integration
MIT License
206 stars 27 forks source link

[BUG / FEATURE?] Macro parameters referencing later-defined labels not being fixed up.. #187

Closed nww02 closed 4 years ago

nww02 commented 4 years ago

Sample Code to print "Hi Istvan" at 5,5 in red and blue.

   print_string(0x0505,str_to_pr,ink(1) | paper(2))
   ret
   str_to_pr: defn "Hi Istvan"

This breaks because the str_to_pr label is defined after the macro call.

Severity    Code    Description Project File    Line    Suppression State
Error       Z0201: This expression cannot be evaluated promptly, it may refer to one or more undefined symbols (str_to_pr)  SpectrumCodeDiscovery4  D:\Development\Speccy\Projects\SpectrumCodeDiscovery4\SpectrumCodeDiscovery4\Z80CodeFiles\Code.z80asm   21  

If I put the label before the macro that makes it work.

                jp      Print

str_to_pr: defn "Hi Istvan"

Print:   print_string(0x0505,str_to_pr,ink(1) | paper(2))
      ret

Also, if I put the str_to_pr parameters in double-quotes, the parser catches the address properly on the second pass, and that also works.

   print_string(0x0505,"str_to_pr",ink(1) | paper(2))
   ret
   str_to_pr: defn "Hi Istvan"

Please can macro parameters be added to the fixup logic?

Dotneteer commented 4 years ago

Please, send me the source with the macro so that I can see it in its entirety!

nww02 commented 4 years ago

Here ya go :)

If you move the str_to_pr and str_loc labels above the macro call, it works.

.model Spectrum48

.ent Start
.xent Start

.org 8000h
Start:
    call    Main
    ret

get_attr_address:
.macro(XYCoordinate)
.if isreg16({{XYCoordinate}})
                    push    {{XYCoordinate}}
                    pop     de
.else
                    ld      de,{{XYCoordinate}}
.endif            

                    ld      a,d
                    sra     a
                    sra     a
                    sra     a
                    add     a,58h
                    ld      h,a   
                    ld      a,d
                    and     7
                    rrca
                    rrca
                    rrca
                    add     a,e
                    ld      l,a
.endm
                ret

get_char_address:
.macro(XYCoordinate)
.if isreg16({{XYCoordinate}})
                        push    {{XYCoordinate}}
                        pop     de
.else
                        ld      de,{{XYCoordinate}}
.endif            
                        ld      a,d    
                        and     0f8h   
                        add     a,40h  
                        ld      h,a    
                        ld      a,d   
                        and     7     
                        rrca         
                        rrca          
                        rrca         
                        add     a,e    
                        ld      l,a    
.endm
                ret
                halt

print_string:
.macro(XYCoordinate,StringLocation,Colour)           
.if def({{StringLocation}})
    .if isreg16({{StringLocation}})
                push    {{StringLocation}}  ; get the address onto the stack
    .endif
.endif

.if def({{XYCoordinate}})
    .if isreg16({{XYCoordinate}})
                push    {{XYCoordinate}}     ; get the coords onto the stack
    .endif
.endif

.if def({{Colour}})
    .if isreg8({{Colour}})
                ld      a,{{Colour}}        ; get the colour into 'a'.
    .endif
    .if isexpr({{Colour}})
                ld      a,{{Colour}}         ; same action
    .endif
.else
                ld      a,(paramb1)          ; colour attribute
.endif

.if def({{XYCoordinate}})
    .if isreg16({{XYCoordinate}})
                pop     de
    .endif
    .if isexpr({{XYCoordinate}})
            .if {{XYCoordinate}} > 0x3fff
                ld      de,({{XYCoordinate}})
            .else
                ld      de,{{XYCoordinate}}
            .endif
    .endif
.else
                ld      de,(paramw1)
.endif

                ld      iyh,a                ; store attrib in iyh
                get_char_address(de)    ; Calculate the screen address (in DE)
                push    hl
                get_attr_address(de)    ; Calculate the attribute address (in DE)
                push    hl
                pop     ix
                pop     de
.if def({{StringLocation}})
    .if isreg16({{StringLocation}})
                pop     hl                  ; get the address into 'hl'
    .endif
    .if isexpr({{StringLocation}})
                ld      hl,{{StringLocation}}
    .endif
.else
                ld      hl,(paramw2)         ; get address of string
.endif
                jp      ps_nxtchar
.mend
   ps_nxtchar:  ld      a,(hl)               ; Fetch the character to print
                inc     hl                   ; Increase HL to the next character
                cp      0x00                 ; Compare with 0x00
                ret     z                    ; \0 terminates the string
                push    hl                   ; Push HL on stack 
                call    INT_print_char       ; Print the character
                ld      a,iyh                ; get attib
                ld      (ix),a               ; set the attib
                inc     ix                   ; go right one
                pop     hl                   ; Retrieve HL back off the stack
                inc     e                    ; Go to the next screen address
                jr      ps_nxtchar           ; Loop back to print next character
       ps_end:  ret
           halt

; Print Zero-terminated string data
; parameters:   w1 = location (hi byte = x, lo byte = y - so if pre-loading from 16-bit reg, start out with them 
;                               as YX   e.g  ld hl,1005#  for at x=5, y=10)
;               w2 = memory location of string
;               b1 = colour attribute
; NOTE:         This program will use the CHARS variable to choose the font.
;               So, use the set_font command to choose a different font if you need to.
    print_string_colour:
                ld      l,a

; A:  Character to print
; DE: Screen address to print character at
;
INT_print_char:             
                ld      hl,(0x5c36)         ; get the font.
                ld      b,0                 ; Set BC to A
                ld      c,a
                and     0xff                ; Clear the carry bit
                rl      c                   ; Multiply BC by 8 (shift left 3 times)
                rl      b
                rl      c    
                rl      b
                rl      c    
                rl      b
                add     hl,bc               ; Get the character address in HL
                ld      c,8                 ; Loop counter
                push    de
pc_nxtline:     ld      a,(hl)              ; Get the byte from the ROM into A
                ld      (de),a              ; Stick A onto the screen
                inc     d                   ; Goto next line on screen
                inc     l                   ; Goto next byte of character
                dec     c                   ; Decrease the loop counter
                jr      nz,pc_nxtline       ; Loop around whilst it is Not Zero (NZ)
                pop     de
            ret                             ; Return from the subroutine

Main:
    print_string(str_loc,str_to_pr,ink(1) | paper(2))
ret

str_loc:      defw 0x0a0a
str_to_pr: defn "Hi Istvan!"
Dotneteer commented 4 years ago

Thanks, I will check it!

Dotneteer commented 4 years ago

I checked this issue. Unfortunately, it is a feature. You can pass only known (evaluable immediately) values to macro parameters. The reason is this: When you pass a value to a macro, you may use it in conditional expressions within the macro, and conditions should be immediately evaluated. I know, with some analysis, the compiler could check if any conditional expression or statement is used. If not, such a construct (late evaluation) should be enabled. With the current compiler architecture, such an implementation is massive, nonetheless, I'll think of some quick solutions.

nww02 commented 4 years ago

OK, I think I understand. I was wondering how I could do some "forward defines", so that I could have my data at the end of the program block and still use the macros... But everything I think of just ends up being more convoluted :)

I'll go back to putting all my defines data at the top of the program in a #included data.z80asm. That keeps it neat and out of the way :)

Thanks for looking! :)

Dotneteer commented 4 years ago

I have good news for you :-). I managed to apply a little change that allows prolonging macro argument evaluation until the macro's body is compiled. It means, that you can pass future-evaluated expressions to you macros, just like in this sample:

.model Spectrum48
.ent Start
.xent Start
.org 8000h

Start:
  call  Main
  ret

; Non-functional macro
print_string:
  .macro(loc, ptr, value)
    ld hl,{{loc}}
    ld de,{{ptr}}
    ld a,{{value}}
  .endm

Main:
  ; Now, this works!
  print_string(str_loc, str_to_pr, ink(1) | paper(2))
ret
str_loc: defw 0x0a0a
str_to_pr: defn "Hi Istvan!"

Of course, conditional statements still require immediately-evaluable expressions, so this still won't work:

.model Spectrum48
.ent Start
.xent Start
.org 8000h

Start:
  call  Main
  ret

; Non-functional macro
print_string:
  .macro(loc, ptr, value)
    .if ({{loc}} > 0x3fff) ; This line will raise an error
      ld hl,{{loc}}
    .endif
    ld de,{{ptr}}
    ld a,{{value}}
  .endm

Main:
  ; Now, this works!
  print_string(str_loc, str_to_pr, ink(1) | paper(2))
ret
str_loc: defw 0x0a0a
str_to_pr: defn "Hi Istvan!"

Here is the private build with this fix:

Spect.Net.VsPackage.zip

I hope, it seems ok for you. If everything's ok, tomorrow I will release Preview 6.

nww02 commented 4 years ago

I'll try to get it installed ASAP and give it a try :)

Thank you! You've been so busy with all these fixes and updates! I'm really enjoying working with SpectNetIDE.

nww02 commented 4 years ago

Ah yes, I see now. Interesting that it cannot run the test on the late-bound variable. I suppose the compiler would need a further pass after the late macro binding to perform the if tests on the fixed up variable. OK, no problem, I can take a 15-or-so T-state hit in the setup to test it using code, or I can write two differently named entrypoints and jr into the main body... The test below works, so I'll use this for now :)

  .if isexpr({{XYCoordinate}})

                ld de,{{XYCoordinate}}
                ld a,d
                cp 0x40
                jr c,ps_proceed
                ld de,({{XYCoordinate}})
    .endif
ps_proceed:     ld      iyh,a                ; store attrib in iyh
                get_char_address(de)    ; Calculate the screen address

Thanks for this fix! It'll solve a lot of headaches with ordering #includes and will help with avoiding unncessary jumps to skip data blocks..

:)

Dotneteer commented 4 years ago

Released in v2 Preview 6.

nww02 commented 4 years ago

Thank you :)