"Incomplete expression." in ASM6 macro

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
"Incomplete expression." in ASM6 macro
by on (#236786)
I'm building an audio driver in ASM6, and I'm up to where I build a table of sound effect addresses and lengths. But when I try to express lookup tables using macros, I get "Incomplete expression" errors.

When I assemble this source file and provide -L to expand macros in the listing file:
Code:
macro sfxdef name, baseaddr, length, period, channel
name = <((* - sfx_table) / 4)
dw baseaddr
db (channel<<2)|((period - 1)<<4)
db length
endm

base $C000
sfx_table:
sfxdef SFX_foo, foo_data, 1, 1, 0
sfxdef SFX_bar, bar_data, 1, 1, 0

foo_data: db $89,$10
bar_data: db $84,$20


This listing is produced:
Code:
                                macro sfxdef name, baseaddr, length, period, channel
                                name = <((* - sfx_table) / 4)
                                dw baseaddr
                                db (channel<<2)|((period - 1)<<4)
                                db length
                                endm
                               
                                base $C000
0C000                           sfx_table:
0C000                           sfxdef SFX_foo, foo_data, 1, 1, 0
0C000                           SFX_foo = <((* - sfx_table) / 4)
*** Incomplete expression.
0C000 08 C0                     dw foo_data
0C002 00                        db (0<<2)|((1 - 1)<<4)
0C003 01                        db 1
0C004                           sfxdef SFX_bar, bar_data, 1, 1, 0
0C004                           SFX_bar = <((* - sfx_table) / 4)
*** Incomplete expression.
0C004 0A C0                     dw bar_data
0C006 00                        db (0<<2)|((1 - 1)<<4)
0C007 01                        db 1
0C008                           
0C008 89 10                     foo_data: db $89,$10
0C00A 84 20                     bar_data: db $84,$20


What is causing these errors, and what should I understand in order to figure out how to fix them?
Re: "Incomplete expression." in ASM6 macro
by on (#236788)
Isn't this because the * is being interpreted as the multiplication operator, since ASM6 uses $ for the PC?
Re: "Incomplete expression." in ASM6 macro
by on (#236789)
Thanks for help. Now I have to figure out how to programmatically distinguish the two in the ca65 code I'm converting without having to hand-edit both copies every time I change something.
Re: "Incomplete expression." in ASM6 macro
by on (#236790)
Well, you could use ca65's dollar_is_pc feature and start using $ all around...
Re: "Incomplete expression." in ASM6 macro
by on (#236791)
A new question

Translating * to $ at the start or end of an expression or parenthesized subexpression fixes one layer of the above MCVE but changes the error message I get when I assemble the whole project. If I try to translate the ca65 idiom for an include guard to ASM6, it ends up not recognizing the macro at all, instead producing "Illegal instruction" but only for the first use of each such macro.

Source:
Code:
ifndef INCLUDE_GUARD
INCLUDE_GUARD = 1

macro sfxdef name, baseaddr, length, period, channel
name = <(($ - sfx_table) / 4)
dw baseaddr
db (channel<<2)|((period - 1)<<4)
db length
endm

endif

base $C000
sfx_table:
sfxdef SFX_foo, foo_data, 1, 1, 0
sfxdef SFX_bar, bar_data, 1, 1, 0

foo_data: db $89,$10
bar_data: db $84,$20

Listing:
Code:
                                ifndef INCLUDE_GUARD
                                INCLUDE_GUARD = 1
                                macro sfxdef name, baseaddr, length, period, channel
                                name = <(($ - sfx_table) / 4)
                                dw baseaddr
                                db (channel<<2)|((period - 1)<<4)
                                db length
                                endm
                                endif
                               
                                base $C000
0C000                           sfx_table:
0C000                           sfxdef SFX_foo, foo_data, 1, 1, 0
*** Illegal instruction.
0C000                           sfxdef SFX_bar, bar_data, 1, 1, 0
0C000                           SFX_bar = <(($ - sfx_table) / 4)
0C000 0A C0                     dw bar_data
0C002 00                        db (0<<2)|((1 - 1)<<4)
0C003 01                        db 1
0C004                           
0C004 89 10                     foo_data: db $89,$10
0C006 84 20                     bar_data: db $84,$20
Re: "Incomplete expression." in ASM6 macro
by on (#236868)
The macro does different things (both wrong) on different invocations. The first time it says "Illegal instruction" and the second time it generates incorrect code. This makes me think I'm hitting an ASM6 bug. I decided to work around it in the translator by detecting whether a particular .ifndef is an include guard and changing it to a .if 1 if so.
Re: "Incomplete expression." in ASM6 macro
by on (#236881)
Maybe that "*** Illegal Instruction" is attempting to say that your dw baseaddr and db length are confusing for asm6 bc you haven't defined the values of baseaddr and length? :)
Re: "Incomplete expression." in ASM6 macro
by on (#236882)
unregistered wrote:
Maybe that "*** Illegal Instruction" is attempting to say that your dw baseaddr and db length are confusing for asm6 bc you haven't defined the values of baseaddr and length? :)

Sorry tepples, I just noticed that you do define the values in your macro calls... maybe this error is created bc you db length before you call the macro? I think tokumaru taught me that the macro has to be defined before it can be called... maybe the same logic applies to whatever baseaddr and length are. :)
Re: "Incomplete expression." in ASM6 macro
by on (#236883)
Sounds like a bug indeed.
Re: "Incomplete expression." in ASM6 macro
by on (#236901)
In any case, I've reduced another test case.

It seems like most actual bugs in ASM6 disappear once a forward reference is made no longer forward.
Re: "Incomplete expression." in ASM6 macro
by on (#237289)
Are you trying to use include guards because multiple source files include the same stuff?
Re: "Incomplete expression." in ASM6 macro
by on (#237291)
doppelganger wrote:
Are you trying to use include guards because multiple source files include the same stuff?

Yes. Both the driver and the instrument/sequence data include one of the header files, and I can't guarantee which gets included first because ASM6 requires them to be included in output file order.
Re: "Incomplete expression." in ASM6 macro
by on (#237295)
So both the driver and instrument/sequence data are assembled together in one unit?
Re: "Incomplete expression." in ASM6 macro
by on (#237296)
ASM6 normally assembles everything as one translation unit, so yes, driver and data are assembled together. It's possible though tricky to assemble something as multiple translation units with ASM6. If so, the separately assembled translation unit has to have entry points at fixed addresses, such as $8000, $8003, $8006, $8009, etc.
Re: "Incomplete expression." in ASM6 macro
by on (#237311)
The problem with the traditional .ifndef type include guard is that ASM6 is a multiple-pass assembler, and anything inside an .ifndef with a following .define inside it will get processed on the first pass, but not on the second. The ASM6 only states that .ifdef and .ifndef operate depending on whether the operand symbol is defined/not defined, without any mention as to whether the definition is supposed to occur prior to the directive or not, so it's implied then that forward references are included as part of .ifdef/.ifndef's operation.

The fallout, of course, is that when ASM6 gets to the second pass, the included file's contents end up not getting output at all.

What you obviously wanted was the first include, and ONLY the first include, to occur. After thinking it over, I may have an ASM6-specific workaround.

Let's say you have your driver in driver.asm and instruments/sequence data in song.asm, and they both include a file called foo.asm for whatever reason. Now, since ASM6 is assembling these as one translation unit, it follows that there must be a top-level source file for the driver and data to go into, assuming one of them isn't the top-level file (I don't know enough about your code to make too many assumptions here).

Now, you obviously don't want foo.asm included twice. My idea is this: define a symbol in the top-level file that precedes the driver and data files that matches whatever symbol you'd have used for foo.asm's include guard:

Code:
FOO_ASM = 1


Then, at the beginning of foo.asm, put this in:

Code:
.if FOO_ASM
FOO_ASM = 0
;insert contents of foo.asm here
.endif


And hopefully that should cause only the very first .include of foo.asm to have any effect on the unit's output, since FOO_ASM will be set to nonzero on the first include, but zero on any subsequent includes, until the next pass.
Re: "Incomplete expression." in ASM6 macro
by on (#241055)
ASM6 is still drunk.

I'm developing a library where features can be turned on or off at build time. The user edits a config file to set constants to zero or nonzero. Then after the config file is processed, another include file tests whether each constant has been set: if ifndef returns that the constant hasn't been set yet, it gives the constant a reasonable value. But when I was introducing support for prefixed constant and macro names in this library, ASM6's macro system started acting up.

Steps to reproduce:
Code:
asm6 -L wrongmacro.asm wrongmacro.bin && tail -n21 wrongmacro.lst


Expected behavior:
The PP_ name, transposition, and PI_ name in each db statement in the listing correspond to the macro call right above it.

Actual behavior:
The PP_ name, transposition, and PI_ name in each db statement stop corresponding to the preceding macro call soon after PSDAT_arp_waltz.

Code:
0806F                           PSDAT_arp_waltz:  ; title: Arpeggio Waltz
0806F                           PENTLY_playPatSq2 PP_arp_waltz_melody, 24, PI_stringlead
0806F 01 0C 18 01               db PENTLY_CON_PLAYPAT|1, PP_arp_waltz_melody, 24, PI_stringlead
08073                           PENTLY_playPatSq1 PP_arp_waltz_bass1, 1, PI_toot0
08073 00 03 23 01               db PENTLY_CON_PLAYPAT|0, PP_bf98_melodyB1, 35, PI_bf98_flute
08077                           PENTLY_waitRows 39  ; end at 0:17.00
08077 20 26                     db PENTLY_CON_WAITROWS, (39)-1
08079                           PENTLY_playPatSq1 PP_arp_waltz_bass2, 3, PI_toot0
08079 00 05 12 00               db PENTLY_CON_PLAYPAT|0, PP_bf98_bassB1, 18, PI_bass
0807D                           PENTLY_waitRows 9  ; end at 0:20.00
0807D 20 3E                     db PENTLY_CON_WAITROWS, (63)-1
0807F                           PENTLY_playPatSq1 PP_arp_waltz_bass1, 1, PI_toot0
0807F 00 00 01 03               db PENTLY_CON_PLAYPAT|0, PP_bf98_drums_tripletfill, 1, PI_toot0
08083                           PENTLY_waitRows 39  ; end at 0:33.00
08083 20 08                     db PENTLY_CON_WAITROWS, (9)-1
08085                           PENTLY_playPatSq1 PP_arp_waltz_bass2, 3, PI_toot0
08085 00 00 03 03               db PENTLY_CON_PLAYPAT|0, PP_bf98_drums, 3, PI_toot0
08089                           PENTLY_waitRows 9  ; end at 0:36.00
08089 20 11                     db PENTLY_CON_WAITROWS, (18)-1
0808B                           PENTLY_dalSegno
0808B 23                        db PENTLY_CON_DALSEGNO


I guess I could find some other way to provide defaults.