Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enum macro #22

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,76 @@ This creates constants like `SaveFile_Player_Name`.

`extends` can be used as many times as you want, anywhere within the struct.

#### Declaring an enum

An enum is traditionally a list of constants assigned unique and contiguous values.
The `enum` macro may be used for this purpose:

```asm
enum Item
case Useless
case Healing
case Weapon
end_enum
```

This is equivalent to the following `def`s:

```asm
rsreset
def Item_Useless rb
def Item_Healing rb
def Item_Weapon rb
def maxof_Item rb
```

However, `enum` `case`s may also have structures associated with them.
This creates a [tagged union](https://en.wikipedia.org/wiki/Tagged_union),
in which each enum value represents a mutually exclusive set of inner fields.

```asm
enum Item
case Useless
case Healing
; The "body" of a case is just like any other `struct` declaration.
bytes 1, Amount
case Weapon
bytes 1, Damage
bytes 1, Durability
end_enum
```

This is equivalent to the following structure declarations:

```asm
rsreset
def Item_Useless rb
; A uselessly empty struct!
; Still exists for the sake of `sizeof`
struct Item_Useless
end_struct

def Item_Healing rb
struct Item_Healing
bytes 1, Amount
end_struct

def Item_Weapon rb
struct Item_Weapon
bytes 1, Damage
bytes 1, Durability
end_struct

def maxof_Item rb
struct Item
; This should be one of `Item_Useless`, `Item_Healing`, or `Item_Weapon`.
bytes 1, Discriminant
; `Contents` is the same size as the largest `case` of the enum.
; In this example, the largest case happens to be `Item_Weapon`.
bytes sizeof_Item_Weapon, Contents
end_struct
```

#### Defined constants

A struct's definition has one constant defined per member, which indicates its offset (in bytes) from the beginning of the struct.
Expand Down Expand Up @@ -187,6 +257,61 @@ It's possible to copy-paste a few calls to `dstruct` to create an array, but `ds
Its first argument is the number of structs to define, and the next two are passed as-is to `dstruct`, except that a decimal index is appended to the struct name.
`dstructs` does not support data arguments; make manual calls to `dstruct` for that—you would have to pass all the data arguments individually anyway.

#### Defining data from an enum

Much like `dstruct`, enums can be created using the `denum` macro:

```
; Define a useless item called "Stick"
denum Item, Useless, Stick
```

For a "unit" enum, this is equivalent to the value of the enum discriminant:

```
Stick::
db Item_Useless
```

However, enum variants that contain additional fields may initialize them via this macro,
using the same syntax as `dstructs`:

```
; Define a weapon item called "Sword"
denum Item, Weapon, Sword, .Damage=10, .Durability=100
```

This is equivalent to manually defining a discriminant followed by a `dstruct` invocation:

```
Weapon::
db Item_Weapon
dstruct Item_Weapon, _Weapon, .Damage=10, .Durability=100
```

Note that the `denum` macro automatically respects size of the enum's discriminant,
as described in the next section.

#### Changing the type of an enum's discriminant.

Sometimes you may need the enum's `Discriminant` field to be defined as something other than a single byte
(for example, to support more than 256 cases).
Should the need arise you can change the type of an enum's discriminant by passing `byte`, `word`, or `long` as the second argument to `enum`:

```asm
enum Number, word
case One
case Two
case Three
; ...
case TwoHundredAndFiftySix
case TwoHundredAndFiftySeven
; ...
case SixtyFiveThousandFiveHundredAndFiftyFive
case SixtyFiveThousandFiveHundredAndFiftySix
end_enum
```

## Credits

Written by [ISSOtm](https://github.com/ISSOtm) and [contributors](https://github.com/ISSOtm/rgbds-structs/graphs/contributors).
62 changes: 38 additions & 24 deletions examples/tests.asm
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,37 @@ INCLUDE "../structs.inc"

SECTION "Stats", ROM0

struct Stats
bytes 8, FirstName
bytes 8, LastName
bytes 1, HP
bytes 1, MaxHP
end_struct
struct Stats
bytes 8, FirstName
bytes 8, LastName
bytes 1, HP
bytes 1, MaxHP
end_struct

dstruct Stats, _PartyMember, "foo", "bar", 100, 100
dstruct Stats, _PartyMember2, .FirstName="foo", .LastName="bar", .HP=100, .MaxHP=100
dstruct Stats, _PartyMember3, .HP=100, .LastName="bar", .MaxHP=100, .FirstName="foo"
dstruct Stats, _PartyMember, "foo", "bar", 100, 100
dstruct Stats, _PartyMember2, .FirstName="foo", .LastName="bar", .HP=100, .MaxHP=100
dstruct Stats, _PartyMember3, .HP=100, .LastName="bar", .MaxHP=100, .FirstName="foo"


struct Multi
bytes 2, test
end_struct
struct Multi
bytes 2, test
end_struct

dstruct Multi, Anon, 1\, 2
dstruct Multi, Named, .test=1\, 2
dstruct Multi, Anon, 1\, 2
dstruct Multi, Named, .test=1\, 2

struct Extended
bytes 27, FirstField
extends Stats
extends Stats, Player
end_struct
struct Extended
bytes 27, FirstField
extends Stats
extends Stats, Player
end_struct

dstruct Extended, _MyExtendedStruct
dstruct Extended, _MyExtendedStruct2, "debug test", "foo", "bar", 100, 100, "foo", "bar", 100, 100
dstruct Extended, _MyExtendedStruct3, .FirstField="debug test", .HP=100, .LastName="bar", .MaxHP=100, .FirstName="foo", .Player_FirstName="foo", .Player_LastName="bar", .Player_HP=100, .Player_MaxHP=100
dstruct Extended, _MyExtendedStruct
dstruct Extended, _MyExtendedStruct2, "debug test", "foo", "bar", 100, 100, "foo", "bar", 100, 100
dstruct Extended, _MyExtendedStruct3, .FirstField="debug test", .HP=100, .LastName="bar", .MaxHP=100, .FirstName="foo", .Player_FirstName="foo", .Player_LastName="bar", .Player_HP=100, .Player_MaxHP=100

ASSERT Extended_FirstName == 27
ASSERT Extended_Player_FirstName == 27 + sizeof_Stats
ASSERT Extended_FirstName == 27
ASSERT Extended_Player_FirstName == 27 + sizeof_Stats

struct Actor
longs 0, Position
Expand All @@ -58,3 +58,17 @@ SECTION "Stats", ROM0

ASSERT _Test == _Test_Position
ASSERT _Test_Position == _Test_YPos

enum Item
case Useless
case Healing
; The "body" of a case is just like any other `struct` declaration.
bytes 1, Amount
case Weapon
bytes 1, Damage
bytes 1, Durability
end_enum

denum Item, Useless, Stick
denum Item, Healing, Potion, .Amount=10
denum Item, Weapon, Sword, .Damage=10, .Durability=100
70 changes: 70 additions & 0 deletions structs.inc
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,73 @@ MACRO dstructs ; nb_structs, struct_type, instance_name
ENDR
PURGE STRUCT_ID
ENDM

macro _maybe_close
if ENUM_DISCRIMINANT != 0
def OLD_STRUCT_NAME equs "{STRUCT_NAME}"
end_struct
if sizeof_{OLD_STRUCT_NAME} > sizeof_{ENUM_NAME}
def sizeof_{ENUM_NAME} = sizeof_{OLD_STRUCT_NAME}
endc
purge OLD_STRUCT_NAME
endc
endm

; Defines an enum from a list of `case`s.
;
; `Type` is an optional argument that determines what storage to use for enum discriminants.
; Valid values for this argument are: `byte`, `word`, `long`.
; When absent, it defaults to `byte`.
macro enum ; Name, [Type]
def ENUM_NAME equs "\1"
if _NARG > 1
if !STRCMP("\2", "byte")
def typeof_{ENUM_NAME} equs "b"
elif !STRCMP("\2", "word")
def typeof_{ENUM_NAME} equs "w"
elif !STRCMP("\2", "long")
def typeof_{ENUM_NAME} equs "l"
else
FAIL "Unrecognized discriminant type: \2"
endc
else
def typeof_{ENUM_NAME} equs "b"
endc
; We can't use RB because it'll be clobbered by `struct`
def ENUM_DISCRIMINANT = 0
def sizeof_\1 = 0
endm

macro case ; Name
_maybe_close
def {ENUM_NAME}_\1 equ ENUM_DISCRIMINANT
def ENUM_DISCRIMINANT += 1
struct {ENUM_NAME}_\1
endm

macro end_enum
_maybe_close
def maxof_{ENUM_NAME} equ ENUM_DISCRIMINANT
struct {ENUM_NAME}
new_field r{typeof_{ENUM_NAME}}, 1, Discriminant
if sizeof_{ENUM_NAME} > 0
bytes sizeof_{ENUM_NAME}, Content
endc
; `end_struct` will recreate this with the discriminant considered
purge sizeof_{ENUM_NAME}
end_struct
purge ENUM_NAME, ENUM_DISCRIMINANT
endm

macro denum
\3::
d{typeof_\1} \1_\2
def ENUM_NAME equs "\1_\2"
SHIFT 3
if _NARG == 0
dstruct {ENUM_NAME}, .contents
else
dstruct {ENUM_NAME}, .contents, \#
endc
purge ENUM_NAME
endm