Open PikalaxALT opened 7 years ago
I think that you can pretty much get the same effect with macros.
ELEMENT : MACRO
\1::
DB \2
DW \3
ENDM
ELEMENT element1, 1, 1234
ELEMENT element2, 1, 1234
ld a,[element1+0] ; etc
Yes, sure, it is not very convenient to get the value of a specific field, but that's how I usually do this.
Does ISSOtm/rgbds-structs do what you want?
Here's a rough first draft for how structs might be implemented in rgbasm. I'm trying to reuse existing keywords and keep the syntax similar to what's already there. Suggestions welcome.
struct Foo
begins defining a structure's members; endstruct
stops that. Inside, you declare labels and allocate space with db
/dw
/dl
/ds
as if you're in a RAM section; union
/nextu
/endu
would also be supported. This would define offset constants, rsset
-style (although without affecting the value of _RS
). No labels are defined and no space is allocated yet though.
Inside a RAM section, you can use dstruct Foo
like the db
/dw
/dl
/ds
directives. It will then define the struct's member labels and allocate space, prefixing each label with the closest defined label above (which could be a .
local one). If that above label is ::
exported, so will be all the members.
struct
can take a second argument like struct Foo, _
or struct Bar, _m
for the separator between its prefix and each member suffix. This would make it compatible with different projects' styles of labeling.
I'm not sure about how sizeof
should work yet. TBH I'd be fine without it; you can define a member at the very end of your struct for that, and then do either e.g. ld bc, NPC_Sizeof
or ld bc, Player_Sizeof - Player
.
Another problem: it would be nice to allow .
in member names for creating local member labels (e.g. person.x
, person.y
, person.z
), but a .
isn't allowed in constant names for the offset constants.
Example 1:
struct Color
R: db
G: db
B: db ; should not get confused with register B
End:
endstruct
wColorData:
dstruct Color
.two:
ds 1 ; padding
dstruct Color
This would act like:
ColorR EQU 0
ColorG EQU 1
ColorB EQU 2
; TODO: SIZEOF_Color ? #Color ? function-style SIZEOF(Color) ?
wColorData:
wColorDataR: db
wColorDataG: db
wColorDataB: db
wColorDataEnd:
.two:
ds 1 ; padding
.twoR: db
.twoG: db
.twoB: db
.twoEnd:
Example 2:
struct NPC, _
YPos: dw
XPos: dw
YBox: db
XBox: db
GfxID: db 2
union
MovementData: dl 2
nextu
AnimData: dl 1
PalData: dstruct Color
endu
endstruct
wMapID:: ds 2
wTileset:: db
wPlayer:: dstruct NPC
wRival: dstruct NPC
This would act like:
wMapID:: ds 2
wTileset:: db
wPlayer::
wPlayer_YPos:: dw
wPlayer_XPos:: dw
wPlayer_YBox:: db
wPlayer_XBox:: db
wPlayer_GfxID:: db 2
UNION
wPlayer_MovementData:: dl 2
NEXTU
wPlayer_AnimData:: dl 1
wPlayer_PalData::
wPlayer_PalDataR:: db
wPlayer_PalDataG:: db
wPlayer_PalDataB:: db
wPlayer_PalDataEnd::
ENDU
wRival:
wRival_YPos: dw
wRival_XPos: dw
wRival_YBox: db
wRival_XBox: db
wRival_GfxID: db 2
UNION
wRival_MovementData: dl 2
NEXTU
wRival_AnimData: dl 1
wRival_PalData:
wRival_PalDataR: db
wRival_PalDataG: db
wRival_PalDataB: db
wRival_PalDataEnd:
ENDU
Honestly, end<thing>
to end blocks is getting kinda ridiculous. Given that this isn't "flow" syntax, I think using something closer to real blocks would be better—if only due to less global state?
Braces are obviously ineligible, but (single) brackets are fair game. I know it would feel very different from the rest of the syntax, but it's also, in concept, nothing like it anyway.
There's if/endc
, rept/endr
, macro/endm
, union/endu
, and load/endl
. Other pairs are pushc/popc
, pushs/pops
, and pusho/popo
. I would suggest ends
instead of endstruct
, but the word "ends" seems likely to be used as a name by someone already.
[[
Double brackets ]]
are tentatively going to be for inline fragments, which on the one hand is unusual for multi-line constructs, but on the other hand it goes with inline [
dereferencing ]
or (
grouping )
.
I don't think a new block construct like this should use brace-style delimiters unless the others are being changed to do the same, like rept 42 [[ ... ]]
or macro mac { ... }
. Which would be a major version update.
"nothing like it anyway": eh, it reminds me of load/endl
blocks, where the labels are declared in one spot but end up going in another.
I prefer RGBDS Structs' use of bytes
, words
, and longs
over db. A C-like syntax would be ideal either way
struct Structure [
byte FieldByte
; or
byte[2], FieldWord
]
Then you could do something like this:
u8 EQU byte
struct Enemy [
u8 health
]
Or just byte[2] FieldWord
.
Something like struct Enemy [ byte[2] FieldWord ]
looks way too different from typical rgbasm syntax to me.
I'd appreciate if someone could refactor existing WRAM labels into a struct with minimal editing. Like turning this from pokecrystal:
wPlayerSubStatus1:: db
wPlayerSubStatus2:: db
wPlayerSubStatus3:: db
wPlayerSubStatus4:: db
wPlayerSubStatus5:: db
to this:
struct Substatus
SubStatus1:: db
SubStatus2:: db
SubStatus3:: db
SubStatus4:: db
SubStatus5:: db
endstruct
wPlayer:: dstruct Substatus
(That does raise the issue of, "wPlayer
is a pretty generic label to define there, people might want it as the prefix to more than one struct." For that the rgbds-structs syntax of dstruct Substatus, wPlayer
is better, and there's a parallel between "struct Name, Infix
" and "dstruct Name, Prefix
".)
I'm worried about the readability of that. There isn't much point in implementing structs if they're going to be that similar, since you might as well just use macros.
Our goal should be to break the mold a bit, since RGBDS Structs already does most of this.
If #635 is merged, this would look fine:
struct Substatus [
SubStatus1:: db
SubStatus2:: db
SubStatus3:: db
SubStatus4:: db
SubStatus5:: db
]
wPlayer:: dstruct Substatus
That looks alright to me. Maybe use double brackets so they stand out more? Presumably it would support union
inside. I'd also go with rgbds-structs' dstruct Substatus, wPlayer
as said above.
I don't like that syntax because it fails to convey which is the type and which is the name.
Besides, we'd probably also want initializer syntax for ROM data.
PlayerData: struct Substatus [
0, 1, 2, 3, 4, ; Allow trailing commas
]
EnemyData: struct Substatus [
Substatus1 = 8, Substatus2 = 7, Substatus3 = 6, Substatus4 = 5, Substatus5 = 4
]
PeonData: struct Substatus [
Substatus4 = NPC_SUBSTAT4 ; Commas or newlines, or both
Substatus2 = HIGH(PEON_CONST), Substatus3 = LOW(PEON_CONST),
... = 0 ; Syntax for default init...
Substatus1 = 69 ; ...not necessarily last
]
(RGBDS-structs has a more clunky syntax for initializers, since it's constrained by macro syntax.)
(I edited those wLabels
to LabelData
, it was confusing at first because they're not WRAM.)
Agreed that a ROM initializer syntax would be useful too. Although I think dstruct
should be used for that, so struct
defines a new kind of structure, and dstruct
uses an existing structure whether in ROM or in RAM.
This was being discussed again in the discord so I figured I'd contribute my own idea for syntax:
STRUCT NPC
Name: ds 16 ; Allocate 16 bytes for a name string
Health: db 10 ; Default of 10 health, allocate one byte
Logic: far_pointer $0000 ; Supports macros too!
ENDS
SECTION "ROM definition", ROM0
NewNPC: object NPC [
Name: "<=16 Letter Name" ; Even if this string is <16 characters, the ds will pad it.
; Health is left out, so it just defaults to 10
]
BrokenNPC: object NPC [
Name: "This name is way too long!!!" ; Error/Warning because the string is too large
]
SECTION "RAM definition", WRAM0
RamNPC: object NPC
; Nothing further is needed, RAM will just go off of the default values.
This will allow the use of macros and doesn't require the user to initialize every value if they don't need to.
I like that syntax; struct ... ends
for definition, struct [[ ... ]]
(or object
or dstruct
) for declaration.
Syntax from WLA-DX for comparison:
.STRUCT water
name ds 8
age db
weight dw
.ENDST
.DSTRUCT waterdrop INSTANCEOF water VALUES
name: .db "tingle"
age: .db 40
weight: .dw 120
.ENDST
.STRUCT drop_pair
waterdrops: instanceof water 2
.ENDST
.DSTRUCT drops INSTANCEOF drop_pair VALUES
waterdrops.1: .db "qwertyui" 40
.dw 120
waterdrops.2.name: .db "tingle"
waterdrops.2.age: .db 40
waterdrops.2.weight: .dw 12
.ENDST
And syntax from NASM:
struc mytype
mt_long: resd 1
mt_word: resw 1
mt_byte: resb 1
mt_str: resb 32
endstruc
mystruc:
istruc mytype
at mt_long, dd 123456
at mt_word, dw 1024
at mt_byte, db 'x'
at mt_str, db 'hello, world', 13, 10, 0
iend
And from ca65:
.struct Point
xcoord .word
ycoord .word
.endstruct
P1: .tag Point
P2: .tag Point
I should probably comment at this point that struc
, at
, istruc
and the like are all macros in NASM :P
("What do they do", I hear? struc
is the easiest to explain: it creates a discarded non-output section with an offset of zero.)
If we come across a third use case, maybe I'll push to actually implement DISCARD
.
Edit: Hmm. I think even with DISCARD
able sections, we couldn't write a macro to work that way: it can't iterate over all the labels in a section and use them as locals inside another label (e.g. you type field: ds 3
after a struct NPC
, then when you do dstruct Jane, NPC
it would define Jane.field
). You would have to run macros for each label at the point of definition, and then the invisible section isn't actually gaining you anything since you can't use label syntax (have to do bytes 3, field
as rgbds-structs already does).
Definition:
foo_struct: STRUCT foo, bar
Declaration:
foo_struct Name, $01, $2345
Use:
dbw Name.foo, Name.bar