|
|
@@ -0,0 +1,367 @@ |
|
|
|
|
|
|
|
; MIT License |
|
|
|
; |
|
|
|
; Copyright (c) 2018-2019 Eldred Habert |
|
|
|
; 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. |
|
|
|
|
|
|
|
|
|
|
|
; !!! WARNING ABOUT READABILITY OF THIS CODE !!! |
|
|
|
; |
|
|
|
; RGBDS, being the venerable/old/decrepit (pick on depending on mood) assembler that it is, requires |
|
|
|
; all label, variable etc. definitions to be on column 0. As in, no whitespace allowed (otherwise, syntax error) |
|
|
|
; Meanwhile, these macros tend to use a lot of nesting (requiring indenting for readability), |
|
|
|
; as well as variable definitions (requiring none to work). |
|
|
|
; As you can probably tell, those two conflict and result in very poor readability |
|
|
|
; Sadly, there is nothing I can do against that short of using a special preprocessor, |
|
|
|
; which I refuse to do for usability's sake. |
|
|
|
; You have all my apologies, how little they may matter, if you are trying to read this code |
|
|
|
; I still did my best to use explicit comments and variable names, hope they will help! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
; strreplace variable_name, original_char, new_char |
|
|
|
strreplace: MACRO |
|
|
|
DOT_POS = STRIN("{\1}", \2) |
|
|
|
IF DOT_POS != 0 |
|
|
|
TMP equs STRCAT(STRSUB("{\1}", 1, DOT_POS + (-1)), STRCAT(\3, STRSUB("{\1}", DOT_POS + 1, STRLEN("{\1}") - DOT_POS))) |
|
|
|
PURGE \1 |
|
|
|
\1 equs "{TMP}" |
|
|
|
PURGE TMP |
|
|
|
strreplace \1, \2, \3 |
|
|
|
ENDC |
|
|
|
IF DEF(DOT_POS) |
|
|
|
PURGE DOT_POS |
|
|
|
ENDC |
|
|
|
ENDM |
|
|
|
|
|
|
|
|
|
|
|
; rgbds_structs_version version_string |
|
|
|
; Call with the expected version string to ensure you're using a compatible version |
|
|
|
; Example: rgbds_structs_version 1.0.0 |
|
|
|
rgbds_structs_version: MACRO |
|
|
|
CURRENT_VERSION equs "1,2,1" |
|
|
|
EXPECTED_VERSION equs "\1" |
|
|
|
strreplace EXPECTED_VERSION, ".", "\," |
|
|
|
check_ver: MACRO |
|
|
|
IF \1 != \4 || \2 > \5 || \3 > \6 |
|
|
|
PURGE EXPECTED_VERSION |
|
|
|
ENDC |
|
|
|
ENDM |
|
|
|
|
|
|
|
CHECK_VER_CALL equs "check_ver {EXPECTED_VERSION},{CURRENT_VERSION}" |
|
|
|
CHECK_VER_CALL |
|
|
|
IF !DEF(EXPECTED_VERSION) |
|
|
|
strreplace CURRENT_VERSION, "\,", "." |
|
|
|
FAIL "RGBDS-structs version \1 is required, which is incompatible with current version {CURRENT_VERSION}" |
|
|
|
ENDC |
|
|
|
PURGE CHECK_VER_CALL |
|
|
|
PURGE check_ver |
|
|
|
PURGE CURRENT_VERSION |
|
|
|
PURGE EXPECTED_VERSION |
|
|
|
ENDM |
|
|
|
|
|
|
|
|
|
|
|
; struct struct_name |
|
|
|
; Begins a struct declaration |
|
|
|
struct: MACRO |
|
|
|
IF DEF(NB_FIELDS) |
|
|
|
FAIL "Please close struct definitions using `end_struct`" |
|
|
|
ENDC |
|
|
|
|
|
|
|
STRUCT_NAME equs "\1" |
|
|
|
|
|
|
|
NB_FIELDS = 0 |
|
|
|
RSRESET |
|
|
|
ENDM |
|
|
|
|
|
|
|
; end_struct |
|
|
|
; Ends a struct declaration |
|
|
|
end_struct: MACRO |
|
|
|
; Set nb of fields |
|
|
|
STRUCT_NB_FIELDS equs "{STRUCT_NAME}_nb_fields" |
|
|
|
STRUCT_NB_FIELDS = NB_FIELDS |
|
|
|
PURGE STRUCT_NB_FIELDS |
|
|
|
|
|
|
|
; Set size of struct |
|
|
|
STRUCT_SIZEOF equs "sizeof_{STRUCT_NAME}" |
|
|
|
STRUCT_SIZEOF RB 0 |
|
|
|
PURGE STRUCT_SIZEOF |
|
|
|
|
|
|
|
PURGE NB_FIELDS |
|
|
|
PURGE STRUCT_NAME |
|
|
|
ENDM |
|
|
|
|
|
|
|
|
|
|
|
; get_nth_field_info field_id |
|
|
|
; Defines EQUS strings pertaining to a struct's Nth field |
|
|
|
; For internal use, please do not use externally |
|
|
|
get_nth_field_info: MACRO |
|
|
|
; Field's name |
|
|
|
STRUCT_FIELD equs "{STRUCT_NAME}_field{d:\1}" |
|
|
|
STRUCT_FIELD_NAME equs "{STRUCT_FIELD}_name" |
|
|
|
STRUCT_FIELD_TYPE equs "{STRUCT_FIELD}_type" |
|
|
|
STRUCT_FIELD_NBEL equs "{STRUCT_FIELD}_nb_el" ; Number of elements |
|
|
|
STRUCT_FIELD_SIZE equs "{STRUCT_FIELD}_size" ; sizeof(type) * nb_el |
|
|
|
ENDM |
|
|
|
|
|
|
|
|
|
|
|
; new_field nb_elems, rs_type, field_name |
|
|
|
; For internal use, please do not use externally |
|
|
|
new_field: MACRO |
|
|
|
IF !DEF(STRUCT_NAME) || !DEF(NB_FIELDS) |
|
|
|
FAIL "Please start defining a struct, using `define_struct`" |
|
|
|
ENDC |
|
|
|
|
|
|
|
get_nth_field_info NB_FIELDS |
|
|
|
; Set field name (keep in mind `STRUCT_FIELD_NAME` is *itself* an EQUS!) |
|
|
|
STRUCT_FIELD_NAME equs "\"\3\"" |
|
|
|
PURGE STRUCT_FIELD_NAME |
|
|
|
|
|
|
|
; Set field offset |
|
|
|
STRUCT_FIELD \2 (\1) |
|
|
|
; Alias this in a human-comprehensive manner |
|
|
|
STRUCT_FIELD_NAME equs "{STRUCT_NAME}_\3" |
|
|
|
STRUCT_FIELD_NAME = STRUCT_FIELD |
|
|
|
|
|
|
|
; Compute field size |
|
|
|
CURRENT_RS RB 0 |
|
|
|
STRUCT_FIELD_SIZE = CURRENT_RS - STRUCT_FIELD |
|
|
|
|
|
|
|
; Set properties |
|
|
|
STRUCT_FIELD_NBEL = \1 |
|
|
|
STRUCT_FIELD_TYPE equs STRSUB("\2", 2, 1) |
|
|
|
|
|
|
|
PURGE STRUCT_FIELD |
|
|
|
PURGE STRUCT_FIELD_NAME |
|
|
|
PURGE STRUCT_FIELD_TYPE |
|
|
|
PURGE STRUCT_FIELD_NBEL |
|
|
|
PURGE STRUCT_FIELD_SIZE |
|
|
|
PURGE CURRENT_RS |
|
|
|
|
|
|
|
NB_FIELDS = NB_FIELDS + 1 |
|
|
|
ENDM |
|
|
|
|
|
|
|
; bytes nb_bytes, field_name |
|
|
|
; Defines a field of N bytes |
|
|
|
bytes: MACRO |
|
|
|
new_field \1, RB, \2 |
|
|
|
ENDM |
|
|
|
|
|
|
|
; words nb_words, field_name |
|
|
|
; Defines a field of N*2 bytes |
|
|
|
words: MACRO |
|
|
|
new_field \1, RW, \2 |
|
|
|
ENDM |
|
|
|
|
|
|
|
; longs nb_longs, field_name |
|
|
|
; Defines a field of N*4 bytes |
|
|
|
longs: MACRO |
|
|
|
new_field \1, RL, \2 |
|
|
|
ENDM |
|
|
|
|
|
|
|
|
|
|
|
; dstruct struct_type, INSTANCE_NAME[, ...] |
|
|
|
; Allocates space for a struct in memory |
|
|
|
; If no further arguments are supplied, the space is simply 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` would have four extra arguments, one byte then three words. |
|
|
|
dstruct: MACRO |
|
|
|
NB_FIELDS equs "\1_nb_fields" |
|
|
|
IF !DEF(NB_FIELDS) |
|
|
|
FAIL "Struct \1 isn't defined!" |
|
|
|
ELIF _NARG != 2 && _NARG != NB_FIELDS + 2 ; We must have either a RAM declaration (no data args) or a ROM one (RAM args + data args) |
|
|
|
EXPECTED_NARG = 2 + NB_FIELDS |
|
|
|
FAIL "Invalid number of arguments, expected 2 or {d:EXPECTED_NARG} but got {d:_NARG}" |
|
|
|
ENDC |
|
|
|
|
|
|
|
; Define the two fields required by `get_nth_field_info` |
|
|
|
STRUCT_NAME equs "\1" ; Which struct `get_nth_field_info` should pull info about |
|
|
|
INSTANCE_NAME equs "\2" ; The instance's base name |
|
|
|
|
|
|
|
|
|
|
|
; RGBASM always expands `\X` macro args, so `IF _NARG > 2 && STRIN("\3", "=")` will error out when there are only 2 args |
|
|
|
; Therefore, the condition is checked here (we can't nest the `IF`s over there because that doesn't translate well to `ELSE`) |
|
|
|
IS_NAMED_INVOCATION = 0 |
|
|
|
IF _NARG > 2 |
|
|
|
IF STRIN("\3", "=") |
|
|
|
IS_NAMED_INVOCATION = 1 |
|
|
|
ENDC |
|
|
|
ENDC |
|
|
|
|
|
|
|
IF IS_NAMED_INVOCATION |
|
|
|
; 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 anyways |
|
|
|
; And finally, it's better to re-use the existing code at the cost of a single nested macro, I believe |
|
|
|
MACRO_CALL equs "dstruct \1, \2" ; This will be used later, but define it now because `SHIFT` will be run |
|
|
|
; In practice `SHIFT` has no effect outside of one when invoked inside of a REPT block, but I hope this behavior is changed (causes a problem elsewhere) |
|
|
|
|
|
|
|
ARG_NUM = 3 |
|
|
|
REPT NB_FIELDS |
|
|
|
; Find out which argument the current one is |
|
|
|
CUR_ARG equs "\3" |
|
|
|
; Remove all whitespace to obtain something like ".name=value" (whitespace are unnecessary and complexify parsing) |
|
|
|
strreplace CUR_ARG, " ", "" |
|
|
|
strreplace CUR_ARG, "\t", "" |
|
|
|
|
|
|
|
EQUAL_POS = STRIN("{CUR_ARG}", "=") |
|
|
|
IF EQUAL_POS == 0 |
|
|
|
FAIL "Argument #{ARG_NUM} (\3) does not contain an equal sign in this named instantiation" |
|
|
|
ELIF STRCMP(STRSUB("{CUR_ARG}", 1, 1), ".") |
|
|
|
FAIL "Argument #{ARG_NUM} (\3) does not start with a period" |
|
|
|
ENDC |
|
|
|
|
|
|
|
FIELD_ID = -1 |
|
|
|
CUR_FIELD_ID = 0 |
|
|
|
REPT NB_FIELDS |
|
|
|
|
|
|
|
; Get the name of the Nth field and compare |
|
|
|
TMP equs "{STRUCT_NAME}_field{d:CUR_FIELD_ID}_name" |
|
|
|
CUR_FIELD_NAME equs TMP |
|
|
|
PURGE TMP |
|
|
|
|
|
|
|
IF !STRCMP(STRUPR("{CUR_FIELD_NAME}"), STRUPR(STRSUB("{CUR_ARG}", 2, EQUAL_POS - 2))) |
|
|
|
; Match found! |
|
|
|
IF FIELD_ID == -1 |
|
|
|
FIELD_ID = CUR_FIELD_ID |
|
|
|
ELSE |
|
|
|
TMP equs "{STRUCT_NAME}_field{d:CUR_FIELD_ID}_name" |
|
|
|
CONFLICTING_FIELD_NAME equs TMP |
|
|
|
PURGE TMP |
|
|
|
FAIL "Fields {CUR_FIELD_NAME} and {CONFLICTING_FIELD_NAME} have conflicting names (case-insensitive), cannot perform named instantiation" |
|
|
|
ENDC |
|
|
|
ENDC |
|
|
|
|
|
|
|
PURGE CUR_FIELD_NAME |
|
|
|
CUR_FIELD_ID = CUR_FIELD_ID + 1 |
|
|
|
ENDR |
|
|
|
PURGE CUR_FIELD_ID |
|
|
|
|
|
|
|
IF FIELD_ID == -1 |
|
|
|
FAIL "Argument #{d:ARG_NUM} (\3) does not match any field of the struct" |
|
|
|
ENDC |
|
|
|
|
|
|
|
INITIALIZER_NAME equs "FIELD_{d:FIELD_ID}_INITIALIZER" |
|
|
|
INITIALIZER_NAME equs STRSUB("{CUR_ARG}", EQUAL_POS + 1, STRLEN("{CUR_ARG}") - EQUAL_POS) |
|
|
|
PURGE INITIALIZER_NAME |
|
|
|
|
|
|
|
; Go to next arg |
|
|
|
ARG_NUM = ARG_NUM + 1 |
|
|
|
SHIFT |
|
|
|
PURGE CUR_ARG |
|
|
|
|
|
|
|
ENDR |
|
|
|
|
|
|
|
; Now that we matched each named initializer to their order, invoke the macro again but without names |
|
|
|
FIELD_ID = 0 |
|
|
|
REPT NB_FIELDS |
|
|
|
TMP equs "{MACRO_CALL}" |
|
|
|
PURGE MACRO_CALL |
|
|
|
INITIALIZER_VALUE equs "{FIELD_{d:FIELD_ID}_INITIALIZER}" |
|
|
|
DELETE_INITIALIZER equs "PURGE FIELD_{d:FIELD_ID}_INITIALIZER" |
|
|
|
DELETE_INITIALIZER |
|
|
|
PURGE DELETE_INITIALIZER |
|
|
|
MACRO_CALL equs "{TMP}, {INITIALIZER_VALUE}" |
|
|
|
PURGE TMP |
|
|
|
PURGE INITIALIZER_VALUE |
|
|
|
FIELD_ID = FIELD_ID + 1 |
|
|
|
ENDR |
|
|
|
|
|
|
|
PURGE FIELD_ID |
|
|
|
; Clean up vars for nested invocation, otherwise some `equs` will be expanded |
|
|
|
PURGE INSTANCE_NAME |
|
|
|
PURGE STRUCT_NAME |
|
|
|
PURGE IS_NAMED_INVOCATION |
|
|
|
PURGE NB_FIELDS |
|
|
|
|
|
|
|
MACRO_CALL ; Now do call the macro |
|
|
|
PURGE MACRO_CALL |
|
|
|
|
|
|
|
|
|
|
|
ELSE |
|
|
|
|
|
|
|
|
|
|
|
INSTANCE_NAME:: ; Declare the struct's root |
|
|
|
; Define instance's properties from struct's |
|
|
|
\2_nb_fields = NB_FIELDS |
|
|
|
sizeof_\2 = sizeof_\1 |
|
|
|
|
|
|
|
; Start defining fields |
|
|
|
FIELD_ID = 0 |
|
|
|
REPT NB_FIELDS |
|
|
|
|
|
|
|
get_nth_field_info FIELD_ID |
|
|
|
|
|
|
|
FIELD_NAME equs STRCAT("{INSTANCE_NAME}_", STRUCT_FIELD_NAME) |
|
|
|
FIELD_NAME:: |
|
|
|
|
|
|
|
; We have defined a label, but now we also need the data backing it |
|
|
|
; There are basically two options: |
|
|
|
IF _NARG == 2 ; RAM definition, no data |
|
|
|
ds STRUCT_FIELD_SIZE |
|
|
|
ELSE |
|
|
|
|
|
|
|
DATA_TYPE equs STRCAT("D", {{STRUCT_FIELD_TYPE}}) |
|
|
|
|
|
|
|
REPT STRUCT_FIELD_NBEL |
|
|
|
DATA_TYPE \3 |
|
|
|
SHIFT |
|
|
|
ENDR |
|
|
|
PURGE DATA_TYPE |
|
|
|
ENDC |
|
|
|
|
|
|
|
; Clean up vars for next iteration |
|
|
|
PURGE STRUCT_FIELD |
|
|
|
PURGE STRUCT_FIELD_NAME |
|
|
|
PURGE STRUCT_FIELD_TYPE |
|
|
|
PURGE STRUCT_FIELD_NBEL |
|
|
|
PURGE STRUCT_FIELD_SIZE |
|
|
|
PURGE FIELD_NAME |
|
|
|
|
|
|
|
FIELD_ID = FIELD_ID + 1 |
|
|
|
ENDR |
|
|
|
|
|
|
|
|
|
|
|
; Clean up |
|
|
|
PURGE FIELD_ID |
|
|
|
; Make sure to keep what's here in sync with cleanup at the end of a named invocation |
|
|
|
PURGE INSTANCE_NAME |
|
|
|
PURGE STRUCT_NAME |
|
|
|
PURGE IS_NAMED_INVOCATION |
|
|
|
PURGE NB_FIELDS |
|
|
|
ENDC |
|
|
|
ENDM |
|
|
|
|
|
|
|
|
|
|
|
; dstructs nb_structs, struct_type, INSTANCE_NAME |
|
|
|
; Allocates space for an array of structs in memory |
|
|
|
; Each struct will have the index appended to its name **as hex** |
|
|
|
; (for example: `dstructs 32, NPC, wNPC` will define wNPC0, wNPC1, and so on until wNPC1F) |
|
|
|
; This is a limitation because RGBASM does not provide an easy way to get the decimal representation of a number |
|
|
|
; Does not support data declarations because I think each struct should be defined individually for that purpose |
|
|
|
dstructs: MACRO |
|
|
|
STRUCT_ID = 0 |
|
|
|
REPT \1 |
|
|
|
dstruct \2, \3{X:STRUCT_ID} |
|
|
|
STRUCT_ID = STRUCT_ID + 1 |
|
|
|
ENDR |
|
|
|
|
|
|
|
PURGE STRUCT_ID |
|
|
|
ENDM |