gbdev / rgbds

Rednex Game Boy Development System - An assembly toolchain for the Nintendo Game Boy and Game Boy Color
https://rgbds.gbdev.io
MIT License
1.33k stars 176 forks source link

Lists/arrays #67

Open Sanqui opened 9 years ago

Sanqui commented 9 years ago
MOVES EQU [$24, $3a]

    db LENGTH(MOVES)
    db MOVES[0], MOVES[1]

Is there any chance for something like this to happen at all? Is it even a good idea?

Should be made to work for strings too.

POKEMON_NAMES EQU ["BULBASAUR", "IVYSAUR"]

    db "So you want ", POKEMON_NAMES[STARTER1], "?@"
Rangi42 commented 3 years ago

An alternative with post-0.4.2 {interpolation} is to define many symbols with the "array index" as a suffix.

list_equ: MACRO
x EQUS "\1"
i = 0
SHIFT
REPT _NARG
; define `list_equs` too with EQUS here
{x}#{d:i} EQU \1 ; could have used '_' but '#' is valid in names
i = i + 1
SHIFT
ENDR
LENGTH_{x} EQU i
PURGE x
ENDM

    list_equ MOVES, $24, $3a

    db LENGTH_MOVES
    db MOVES#0, MOVES#1

    list_equs POKEMON_NAMES, "BULBASAUR", "IVYSAUR"

    db "So you want {POKEMON_NAMES#{d:STARTER1}}?@"

pokered's macros/scripts/maps.asm works somewhat like this, e.g. def_warps followed by many warps, and then def_warps_to iterates over all the defined warps. (Those macros will be simplified with the next rgbds release.)

def_warps: MACRO
REDEF _NUM_WARPS EQUS "_NUM_WARPS_\@"
    db _NUM_WARPS
_NUM_WARPS = 0
ENDM

warp: MACRO
    db \2, \1, \3, \4
REDEF _WARP_TO_NUM_{d:{_NUM_WARPS}} EQUS "warp_to \1, \2, _WARP_TO_WIDTH"
_NUM_WARPS = _NUM_WARPS + 1
ENDM

def_warps_to: MACRO
_WARP_TO_WIDTH = \1_WIDTH
    FOR N, _NUM_WARPS
        _WARP_TO_NUM_{d:N}
    ENDR
ENDM

    def_warps
    warp 14,  0, 5, LAST_MAP
    warp 14,  2, 1, SS_ANNE_1F

    def_warps_to VERMILION_DOCK
Rangi42 commented 3 years ago

Here are some macros for working with lists (using features of post-0.5.0 rgbasm master): numeric lists https://pastebin.com/Ucngn8Pt and string lists https://pastebin.com/WMrJdSKk

    list MOVES, $24, $3a
    db LENGTH_MOVES
    db MOVES#1, MOVES#2

    slist POKEMON_NAMES, "BULBASAUR", "IVYSAUR"
    db "So you want {POKEMON_NAMES#{d:STARTER1}}?@"
    slist MONS
    slist_item "Squirtle"
    slist_item "Bulbasaur"
    slist_item "Charmander"
    slist_sort MONS
    slist_println MONS ; ["Bulbasaur", "Charmander", "Squirtle"]
    println LENGTH_MONS ; $3

    slist_copy STARTERS, MONS
    slist_append STARTERS, "Pikachu", "Eevee"
    slist_println STARTERS ; ["Bulbasaur", "Charmander", "Squirtle", "Pikachu", "Eevee"]
    slist_replace STARTERS, "Pikachu", "Raichu"
    println "{STARTERS#4}" ; Raichu

    slist GEN2MONS, "Chikorita", "Cyndaquil", "Totodile"
    slist_delete STARTERS, 4
    slist_remove STARTERS, "Eevee"
    slist_extend STARTERS, GEN2MONS
    slist_purge GEN2MONS
    slist_println STARTERS ; ["Bulbasaur", "Charmander", "Squirtle", "Chikorita", "Cyndaquil", "Totodile"]

    slist_purge MONS
    assert !DEF(LENGTH_MONS)
    slist MONS, "Rattata", "Pidgey", "Pidgey", "Spearow", "Pidgey", "Spearow"
    slist_find N, MONS, "Pidgey"
    println N ; $2
    slist_rfind N, MONS, "Pidgey"
    println N ; $5
    slist_count N, MONS, "Pidgey"
    println N ; $3
    slist_remove_all MONS, "Pidgey"
    slist_set MONS, 2, "Fearow"
    slist_insert MONS, 3, "Raticate"
    slist_reverse MONS
    slist_println MONS ; ["Spearow", "Raticate", "Fearow", "Rattata"]
Rangi42 commented 3 years ago

That actually demonstrates a possible use case for REDEF EQU versus using SET: it would prevent the user from directly changing a constant, while letting the appropriate macros conveniently do it.

__len = LENGTH_\1
PURGE LENGTH_\1
LENGTH_\1 EQU __len + 1
PURGE __len

; vs

REDEF LENGTH_\1 EQU LENGTH_\1 + 1
ISSOtm commented 3 years ago

Bump @Sanqui, what do you think of Rangi's suggested alternatives?

Sanqui commented 3 years ago

Well, it's sort of hacky, would have been nice to have some syntactical sugar for this whole thing, but it would work for me, I think. I can't help but wonder if dictionaries would be possible with this approach too, to possibly enable loading entire JSON-like structures.

ISSOtm commented 3 years ago

Struct support has been requested (#98), is shimmed, but has native support planned because rgbds-structs is a big pile of spaghetti hacks.

Rangi42 commented 2 years ago

We were discussing native arrays in #rgbds. They would be useful as a return value from a hypothetical READBIN function to read the bytes of a file (as opposed to a READFILE reading the contents as a string, since the functions dealing with strings expect UTF-8 and would terminate on $00 bytes.)

Some possible syntaxes for array literals:

db, dw, and dl should work with arrays just like with strings, applying to each element of the array.

Most string functions would usefully have array counterparts, though I don't know if they should start with "ARRAY" or just "ARR" (I think "ARRAY" is more readable):

Macros like in list.asm could take care of more advanced array manipulation, like counting a value, removing/replacing the first/last/all of a value, sorting, reversing, etc. If any of them are found to be particularly useful, they can always be added in a later release. (Even the ARRAYIN/ARRAYRIN functions could be omitted, since for loops are sufficient and they might be rarely used.)

A question: how to assign an identifier to an array? DEF arr EQUA [1, 2, 3]?

There's a notable difference between arrays and strings. If you have DEF s EQUS "hello", you can't do STRLEN(s) because of string expansion; you have to do STRLEN("{s}"). This involves more typing but prevents you at the grammar level from saying STRLEN(x) for any identifier x which could be a number, label, undefined, etc. On the other hand, if you've defined arr as an array (somehow), I'm not sure how it should behave:

This proposal also doesn't address arrays of strings, which could be at least as useful. Example: have an array of all monster names, and in texts discussing MON_FOO, concatenate the value of the MON_FOOth entry of MON_NAMES. And some table of monster names would just be for i, ARRAYLEN(MON_NAMES) / db ARRAYVAL(MON_NAMES, i+1) / endr.

aaaaaa123456789 commented 2 years ago

A few unsorted comments on your comment:

Rangi42 commented 2 years ago
aaaaaa123456789 commented 2 years ago

It's true that plenty of languages use 1-based indexing. It's also true that languages are nearly universally hated by programmers for doing so, and the only reason they do it is because they are math- or science-oriented and scientists and mathematicians tend to count from 1. String indexing is a relatively rare operation, while array indexing is the only reason you'd ever use arrays in the first place, so getting it right for arrays is a lot more important, to the point it probably trumps the need for consistency.

Rangi42 commented 2 years ago

ARRAYCAT is potentially redundant, if we allow the ARRAY "constructor" to automatically flatten arrays. So you have DEF a1 EQUA ARRAY(1,2,3), then DEF a2 EQUA ARRAY(a1,4,5,6,ARRAY(7,8,9),10), and then a2 is ARRAY(1,2,3,4,5,6,7,8,9,10).

Rangi42 commented 2 years ago

A less serious but not entirely joking suggestion: once we have user-defined functions, we could add ARRAYMAP(arr, fn) to apply fn to each element of arr, and ARRAYFILTER(arr, fn) to select only the elements of arr for which fn returns nonzero/true. Or even ARRAYREDUCE(arr, fn, init=0) to apply a reducing function (e.g. if DEF plus(x, y) = x + y, then ARRAYREDUCE([1,2,3], plus) == 6, and ARRAYREDUCE([], plus, 42) == 42).

aaaaaa123456789 commented 2 years ago

A less serious but not entirely joking suggestion: once we have user-defined functions, we could add ARRAYMAP(arr, fn) to apply fn to each element of arr, and ARRAYFILTER(arr, fn) to select only the elements of arr for which fn returns nonzero/true. Or even ARRAYREDUCE(arr, fn, start=0) to apply a reducing function (e.g. if DEF plus(x, y) = x + y, then ARRAYREDUCE([1,2,3], plus) == 6).

Definitely useful.

(Ed: I removed the email chains from these comments. --Rangi)

Rangi42 commented 2 weeks ago

Updated syntax for the very basic arrays in the original feature request:

MACRO def_array
    def \1#LEN equ _NARG - 1
    for idx, \1#LEN
        def argi = idx + 2
        def \1#{d:idx} equ \<argi>
    endr
ENDM

    def_array MOVES, $24, $3a
    db MOVES#LEN
    db MOVES#0, MOVES#1

MACRO def_str_array
    def \1#LEN equ _NARG - 1
    for idx, \1#LEN
        def argi = idx + 2
        def \1#{d:idx} equs \<argi>
    endr
ENDM

    def_str_array POKEMON_NAMES, "BULBASAUR", "IVYSAUR"
    def STARTER1 equ 0
    db "So you want {POKEMON_NAMES#{d:STARTER1}}?@"

Extensions like appending to an array are also pretty easy to implement:

MACRO append_array
    def \1#{d:\1#LEN} equ \2
    redef \1#LEN equ \1#LEN + 1
ENDM

    append_array MOVES, $f0
    assert MOVES#LEN == 3
    assert MOVES#2 == $f0

MACRO append_str_array
    def \1#{d:\1#LEN} equs \2
    redef \1#LEN equ \1#LEN + 1
ENDM

    append_str_array POKEMON_NAMES, "VENUSAUR"
    assert POKEMON_NAMES#LEN == 3
    assert !strcmp("{POKEMON_NAMES#2}", "VENUSAUR")

(The possibilities for advanced features -- concatenating, searching, sorting, reversing, function-mapping, shuffling -- get increasingly more like a "real programming language" than "genuinely useful utilities for assembly metaprogramming", and I'm not sure it's worth dedicating language syntax/code/testing/maintenance to them.)

Rangi42 commented 2 weeks ago

Are there any examples of lists/arrays in other assemblers? Prior art that we could get inspiration and use cases from?