diff --git a/README.md b/README.md index 9cd88be..f681cc0 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,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). 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 c75fdbf..68f0690 100644 --- a/structs.inc +++ b/structs.inc @@ -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