From 37490fa3b11d82c9cdd9a7d66a902c56b6f47095 Mon Sep 17 00:00:00 2001 From: Evie <14899090+evie-calico@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:47:41 -0400 Subject: [PATCH 1/4] Add `enum` macro --- structs.inc | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/structs.inc b/structs.inc index c75fdbf..fa9d478 100644 --- a/structs.inc +++ b/structs.inc @@ -345,3 +345,48 @@ MACRO dstructs ; nb_structs, struct_type, instance_name ENDR PURGE STRUCT_ID ENDM + +macro _maybe_close + if ENUM_DISCRIMINANT != 0 + if sizeof_{STRUCT_NAME} > sizeof_{ENUM_NAME} + def sizeof_{ENUM_NAME} = sizeof_{STRUCT_NAME} + endc + end_struct + 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. +; This is passed to the internal `struct` invocation, so any alias of `new_field` will work (`bytes`, `words`, `longs`). +; When absent, it defaults to `bytes`. +macro enum ; Name, [Type] + def ENUM_NAME equs "\1" + if _NARGS > 1 + def ENUM_TYPE equs "\2" + else + def ENUM_TYPE equs "bytes" + 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} + {ENUM_TYPE} 1, Discriminant + if sizeof_{ENUM_NAME} > 0 + bytes sizeof_{ENUM_NAME}, Content + endc + endc + purge ENUM_NAME, ENUM_DISCRIMINANT +endm From 160dcad245397f9148ecc87b0f84668831fd38b5 Mon Sep 17 00:00:00 2001 From: Evie <14899090+evie-calico@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:15:30 -0400 Subject: [PATCH 2/4] Document `enum` --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/README.md b/README.md index 9cd88be..39b6957 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,96 @@ 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. +#### 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. + + +```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 +``` + +#### Changing the type of an enum's discriminant. + +Sometimes you may need the enum's `Discriminant` field to be defined as something other than `bytes` +(for example, to support more than 256 cases). +Should the need arise, +you can change the type of an enum's discriminant by passing a new type as the second argument to `enum`: + +```asm + enum Number, words + 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). From b911c3788d335fab10eb9b31cb8dc799da775cf3 Mon Sep 17 00:00:00 2001 From: Evie <14899090+evie-calico@users.noreply.github.com> Date: Sun, 13 Oct 2024 13:54:04 -0400 Subject: [PATCH 3/4] Reorganize README.md and document denum --- README.md | 151 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 39b6957..358dcc1 100644 --- a/README.md +++ b/README.md @@ -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. @@ -187,84 +257,45 @@ 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. -#### Declaring an enum +#### Defining data from an enum -An enum is traditionally a list of constants assigned unique and contiguous values. -The `enum` macro may be used for this purpose: +Much like `dstruct`, enums can be created using the `denum` macro: -```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 + denum Item, Useless ``` -However, `enum` `case`s may also have structures associated with them. - +For a "unit" enum, this is equivalent to the value of the enum discriminant: -```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 +``` + db Item_Useless ``` -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 +However, enum variants that contain additional fields may initialize them via this macro, +using the same syntax as `dstructs`: -def Item_Healing rb - struct Item_Healing - bytes 1, Amount - end_struct +``` + denum Item, Weapon, .Damage=10, .Durability=100 +``` -def Item_Weapon rb - struct Item_Weapon - bytes 1, Damage - bytes 1, Durability - end_struct +This is equivalent to manually defining a discriminant followed by a `dstruct` invocation: -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 ``` + db Item_Weapon + dstruct Item_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 `bytes` +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 a new type as the second argument to `enum`: +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, words + enum Number, word case One case Two case Three From 70c3ca37e7db27bcd2a45986c041a6e1175faa21 Mon Sep 17 00:00:00 2001 From: Evie <14899090+evie-calico@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:26:22 -0400 Subject: [PATCH 4/4] Add `denum` --- README.md | 10 ++++-- examples/tests.asm | 62 +++++++++++++++++++++-------------- structs.inc | 81 ++++++++++++++++++++++++++++++---------------- 3 files changed, 98 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 358dcc1..f681cc0 100644 --- a/README.md +++ b/README.md @@ -262,12 +262,14 @@ Its first argument is the number of structs to define, and the next two are pass Much like `dstruct`, enums can be created using the `denum` macro: ``` - denum Item, Useless + ; 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 ``` @@ -275,14 +277,16 @@ However, enum variants that contain additional fields may initialize them via th using the same syntax as `dstructs`: ``` - denum Item, Weapon, .Damage=10, .Durability=100 + ; 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, .Damage=10, .Durability=100 + dstruct Item_Weapon, _Weapon, .Damage=10, .Durability=100 ``` Note that the `denum` macro automatically respects size of the enum's discriminant, diff --git a/examples/tests.asm b/examples/tests.asm index 8243b60..3556deb 100644 --- a/examples/tests.asm +++ b/examples/tests.asm @@ -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 @@ -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 diff --git a/structs.inc b/structs.inc index fa9d478..68f0690 100644 --- a/structs.inc +++ b/structs.inc @@ -347,46 +347,71 @@ MACRO dstructs ; nb_structs, struct_type, instance_name ENDM macro _maybe_close - if ENUM_DISCRIMINANT != 0 - if sizeof_{STRUCT_NAME} > sizeof_{ENUM_NAME} - def sizeof_{ENUM_NAME} = sizeof_{STRUCT_NAME} + 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 - end_struct - 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. -; This is passed to the internal `struct` invocation, so any alias of `new_field` will work (`bytes`, `words`, `longs`). -; When absent, it defaults to `bytes`. +; 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 _NARGS > 1 - def ENUM_TYPE equs "\2" - else - def ENUM_TYPE equs "bytes" - endc - ; We can't use RB because it'll be clobbered by `struct` - def ENUM_DISCRIMINANT = 0 - def sizeof_\1 = 0 + 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 + _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} - {ENUM_TYPE} 1, Discriminant - if sizeof_{ENUM_NAME} > 0 - bytes sizeof_{ENUM_NAME}, Content + _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 - endc - purge ENUM_NAME, ENUM_DISCRIMINANT + purge ENUM_NAME endm