; MIT License ; ; Copyright (c) 2018-2022 Eldred Habert and contributors ; Originally hosted at https://github.com/ISSOtm/rgbds-structs ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in all ; copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ; SOFTWARE. DEF STRUCTS_VERSION equs "3.0.1" MACRO structs_assert assert (\1), "rgbds-structs {STRUCTS_VERSION} bug. Please report at https://github.com/ISSOtm/rgbds-structs, and share the above stack trace *and* your code there!" ENDM ; Call with the expected RGBDS-structs version string to ensure your code ; is compatible with the INCLUDEd version of RGBDS-structs. ; Example: `rgbds_structs_version 2.0.0` MACRO rgbds_structs_version ; version_string DEF CURRENT_VERSION EQUS STRRPL("{STRUCTS_VERSION}", ".", ",") ; Undefine `EXPECTED_VERSION` if it does not match `CURRENT_VERSION` DEF EXPECTED_VERSION EQUS STRRPL("\1", ".", ",") check_ver {EXPECTED_VERSION}, {CURRENT_VERSION} IF !DEF(EXPECTED_VERSION) FAIL "rgbds-structs version \1 is required, which is incompatible with current version {STRUCTS_VERSION}" ENDC PURGE CURRENT_VERSION, EXPECTED_VERSION ENDM ; Checks whether trios of version components match. ; Used internally by `rgbds_structs_version`. MACRO check_ver ; expected major, minor, patch, current major, minor, patch IF (\1) != (\4) || (\2) > (\5) || (\3) > (\6) PURGE EXPECTED_VERSION ENDC ENDM ; Begins a struct declaration. MACRO struct ; struct_name IF DEF(STRUCT_NAME) || DEF(NB_FIELDS) FAIL "Please close struct definitions using `end_struct`" ENDC ; Define two internal variables for field definitions DEF STRUCT_NAME EQUS "\1" DEF NB_FIELDS = 0 DEF NB_NONALIASES = 0 ; Initialize _RS to 0 for defining offset constants RSRESET ENDM ; Ends a struct declaration. MACRO end_struct ; Define the number of fields and size in bytes DEF {STRUCT_NAME}_nb_fields EQU NB_FIELDS DEF {STRUCT_NAME}_nb_nonaliases EQU NB_NONALIASES DEF sizeof_{STRUCT_NAME} EQU _RS IF DEF(STRUCTS_EXPORT_CONSTANTS) EXPORT {STRUCT_NAME}_nb_fields, sizeof_{STRUCT_NAME} ENDC ; Purge the internal variables defined by `struct` PURGE STRUCT_NAME, NB_FIELDS, NB_NONALIASES ENDM ; Defines a field of N bytes. DEF bytes equs "new_field rb," DEF words equs "new_field rw," DEF longs equs "new_field rl," DEF alias equs "new_field rb, 0," ; Extends a new struct by an existing struct, effectively cloning its fields. MACRO extends ; struct_type[, sub_struct_name] IF !DEF(\1_nb_fields) FAIL "Struct \1 isn't defined!" ENDC IF _NARG != 1 && _NARG != 2 FAIL "Invalid number of arguments, expected 1 or 2" ENDC FOR FIELD_ID, \1_nb_fields DEF EXTENDS_FIELD EQUS "\1_field{d:FIELD_ID}" get_nth_field_info {STRUCT_NAME}, NB_FIELDS IF _NARG == 1 DEF {STRUCT_FIELD_NAME} EQUS "{{EXTENDS_FIELD}_name}" ELSE DEF {STRUCT_FIELD_NAME} EQUS "\2_{{EXTENDS_FIELD}_name}" ENDC DEF {STRUCT_FIELD} RB {EXTENDS_FIELD}_size IF DEF(STRUCTS_EXPORT_CONSTANTS) EXPORT {STRUCT_FIELD} ENDC DEF {STRUCT_NAME}_{{STRUCT_FIELD_NAME}} EQU {STRUCT_FIELD} DEF {STRUCT_FIELD_SIZE} EQU {EXTENDS_FIELD}_size DEF {STRUCT_FIELD_TYPE} EQUS "{{EXTENDS_FIELD}_type}" purge_nth_field_info DEF NB_FIELDS += 1 IF {EXTENDS_FIELD}_size != 0 DEF NB_NONALIASES += 1 ENDC PURGE EXTENDS_FIELD ENDR ENDM ; Defines EQUS strings pertaining to a struct's Nth field. ; Used internally by `new_field` and `dstruct`. MACRO get_nth_field_info ; struct_name, field_id DEF STRUCT_FIELD EQUS "\1_field{d:\2}" ; prefix for other EQUS DEF STRUCT_FIELD_NAME EQUS "{STRUCT_FIELD}_name" ; field's name DEF STRUCT_FIELD_TYPE EQUS "{STRUCT_FIELD}_type" ; type ("b", "l", or "l") DEF STRUCT_FIELD_SIZE EQUS "{STRUCT_FIELD}_size" ; sizeof(type) * nb_el ENDM ; Purges the variables defined by `get_nth_field_info`. ; Used internally by `new_field` and `dstruct`. DEF purge_nth_field_info equs "PURGE STRUCT_FIELD, STRUCT_FIELD_NAME, STRUCT_FIELD_TYPE, STRUCT_FIELD_SIZE" ; Defines a field with a given RS type (`rb`, `rw`, or `rl`). ; Used internally by `bytes`, `words`, and `longs`. MACRO new_field ; rs_type, nb_elems, field_name IF !DEF(STRUCT_NAME) || !DEF(NB_FIELDS) FAIL "Please start defining a struct, using `struct`" ENDC get_nth_field_info {STRUCT_NAME}, NB_FIELDS ; Set field name DEF {STRUCT_FIELD_NAME} EQUS "\3" ; Set field offset DEF {STRUCT_FIELD} \1 (\2) IF DEF(STRUCTS_EXPORT_CONSTANTS) EXPORT {STRUCT_FIELD} ENDC ; Alias this in a human-comprehensible manner DEF {STRUCT_NAME}_\3 EQU {STRUCT_FIELD} ; Compute field size DEF {STRUCT_FIELD_SIZE} EQU _RS - {STRUCT_FIELD} ; Set properties DEF {STRUCT_FIELD_TYPE} EQUS STRSUB("\1", 2, 1) purge_nth_field_info DEF NB_FIELDS += 1 IF \2 != 0 DEF NB_NONALIASES += 1 ENDC ENDM ; Strips whitespace from the left of a string. ; Used internally by `dstruct`. MACRO lstrip ; string_variable FOR START_POS, 1, STRLEN("{\1}") + 1 IF !STRIN(" \t", STRSUB("{\1}", START_POS, 1)) BREAK ENDC ENDR REDEF \1 EQUS STRSUB("{\1}", START_POS) PURGE START_POS ENDM ; Allocates space for a struct in memory. ; If no further arguments are supplied, the space is allocated using `ds`. ; Otherwise, the data is written to memory using the appropriate types. ; For example, a struct defined with `bytes 1, Field1` and `words 3, Field2` ; could take four extra arguments, one byte then three words. ; Each such argument would have an equal sign between the name and value. MACRO dstruct ; struct_type, instance_name[, ...] IF !DEF(\1_nb_fields) FAIL "Struct \1 isn't defined!" ELIF _NARG != 2 && _NARG != 2 + \1_nb_nonaliases ; We must have either a RAM declaration (no data args) ; or a ROM one (RAM args + data args) FAIL STRFMT("Expected 2 or %u args to `dstruct`, but got {d:_NARG}", 2 + \1_nb_nonaliases) ENDC ; RGBASM always expands macro args, so `IF _NARG > 2 && STRIN("\3", "=")` ; would error out when there are only two args. ; Therefore, the condition is checked here (we can't nest the `IF`s over ; there because that would require a duplicated `ELSE`). DEF IS_NAMED_INSTANTIATION = 0 IF _NARG > 2 REDEF IS_NAMED_INSTANTIATION = STRIN("\3", "=") ENDC IF IS_NAMED_INSTANTIATION ; This is a named instantiation; translate that to an ordered one. ; This is needed because data has to be laid out in order, so some translation is needed anyway. ; And finally, I believe it's better to re-use the existing code at the cost of a single nested macro. FOR ARG_NUM, 3, _NARG + 1 ; Remove leading whitespace to obtain something like ".name=value" ; (this enables a simple check for starting with a period) REDEF CUR_ARG EQUS "\" lstrip CUR_ARG ; Ensure that the argument has a name and a value, ; separated by an equal sign DEF EQUAL_POS = STRIN("{CUR_ARG}", "=") IF !EQUAL_POS FAIL "\"{CUR_ARG}\" is not a named initializer!" ELIF STRCMP(STRSUB("{CUR_ARG}", 1, 1), ".") FAIL "\"{CUR_ARG}\" does not start with a period!" ENDC ; Find out which field the current argument is FOR FIELD_ID, \1_nb_fields IF !STRCMP(STRSUB("{CUR_ARG}", 2, EQUAL_POS - 2), "{\1_field{d:FIELD_ID}_name}") IF \1_field{d:FIELD_ID}_size == 0 FAIL "Cannot initialize an alias" ENDC BREAK ; Match found! ENDC ENDR IF FIELD_ID == \1_nb_fields FAIL "\"{CUR_ARG}\" does not match any member of \1" ELIF DEF(FIELD_{d:FIELD_ID}_INITIALIZER) FAIL "\"{CUR_ARG}\" conflicts with \"{FIELD_{d:FIELD_ID}_ARG}\"" ENDC ; Save the argument to report in case a later argument conflicts with it DEF FIELD_{d:FIELD_ID}_ARG EQUS "{CUR_ARG}" ; Escape any commas in a multi-byte argument initializer so it can ; be passed as one argument to the nested ordered instantiation DEF FIELD_{d:FIELD_ID}_INITIALIZER EQUS STRRPL(STRSUB("{CUR_ARG}", EQUAL_POS + 1), ",", "\\,") ENDR PURGE ARG_NUM, CUR_ARG ; Now that we matched each named initializer to their order, ; invoke the macro again but without names DEF ORDERED_ARGS EQUS "\1, \2" FOR FIELD_ID, \1_nb_fields IF \1_field{d:FIELD_ID}_size != 0 REDEF ORDERED_ARGS EQUS "{ORDERED_ARGS}, {FIELD_{d:FIELD_ID}_INITIALIZER}" PURGE FIELD_{d:FIELD_ID}_ARG, FIELD_{d:FIELD_ID}_INITIALIZER ENDC ENDR PURGE FIELD_ID ; Do the nested ordered instantiation dstruct {ORDERED_ARGS} ; purges IS_NAMED_INSTANTIATION PURGE ORDERED_ARGS ELSE ; This is an ordered instantiation, not a named one. ; Define the struct's root label \2:: IF DEF(STRUCT_SEPARATOR) DEF DSTRUCT_SEPARATOR equs "{STRUCT_SEPARATOR}" ELSE DEF DSTRUCT_SEPARATOR equs "_" ENDC ; Define each field DEF ARG_NUM = 3 FOR FIELD_ID, \1_nb_fields get_nth_field_info \1, FIELD_ID ; Define the label for the field \2_{{STRUCT_FIELD_NAME}}:: IF STRUCT_FIELD_SIZE != 0 ; Skip aliases ; Declare the space for the field IF ARG_NUM <= _NARG ; ROM declaration; use `db`, `dw`, or `dl` d{{STRUCT_FIELD_TYPE}} \ REDEF ARG_NUM = ARG_NUM + 1 ENDC ; Add padding as necessary after the provided initializer ; (possibly all of it, especially for RAM use) IF {STRUCT_FIELD_SIZE} < @ - \2_{{STRUCT_FIELD_NAME}} FAIL STRFMT("Initializer for %s is %u bytes, expected %u at most", "\2_{{STRUCT_FIELD_NAME}}", @ - \2_{{STRUCT_FIELD_NAME}}, {STRUCT_FIELD_SIZE}) ENDC ds {STRUCT_FIELD_SIZE} - (@ - \2_{{STRUCT_FIELD_NAME}}) ENDC purge_nth_field_info ENDR PURGE FIELD_ID, ARG_NUM, DSTRUCT_SEPARATOR ; Define instance's properties from struct's DEF \2_nb_fields EQU \1_nb_fields DEF sizeof_\2 EQU @ - (\2) structs_assert sizeof_\1 == sizeof_\2 IF DEF(STRUCTS_EXPORT_CONSTANTS) EXPORT \2_nb_fields, sizeof_\2 ENDC PURGE IS_NAMED_INSTANTIATION ENDC ENDM ; Allocates space for an array of structs in memory. ; Each struct will have the index appended to its name **as decimal**. ; For example: `dstructs 32, NPC, wNPC` will define `wNPC0`, `wNPC1`, and so on until `wNPC31`. ; This is a change from the previous version of rgbds-structs, where the index was uppercase hexadecimal. ; Does not support data declarations because I think each struct should be defined individually for that purpose. MACRO dstructs ; nb_structs, struct_type, instance_name IF _NARG != 3 FAIL "`dstructs` only takes 3 arguments!" ENDC FOR STRUCT_ID, \1 dstruct \2, \3{d:STRUCT_ID} ENDR PURGE STRUCT_ID ENDM