339 lines
12 KiB
NASM
339 lines
12 KiB
NASM
; 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 "\<ARG_NUM>"
|
|
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}} \<ARG_NUM>
|
|
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
|