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 175 forks source link

[Feature request] Single-line macro syntax #902

Closed Rangi42 closed 1 month ago

Rangi42 commented 3 years ago

Macros need at least three lines, since the MACRO and ENDM have to be on their own. This is kind of verbose for small macros, and the documentation even suggests using EQUS instead for brevity. However, that's "cheating" since it's not really a macro invocation; it can't take arguments, and relies on EQUS expansion (arguably a misfeature).

I propose a single-line DEF mac MACRO ... syntax for cases like this. After eating the MACRO token, the parser would call a lexer function to capture everything up to the next newline/end-of-file as a string. This would only allow single-statement macros, but would combine neatly with #901 for multiple statements.

Some examples:

DEF lb MACRO ld \1, (\2) << 8 | (\3)

; instead of pokecrystal's `hlcoord EQUS "coord hl,"`
DEF hlcoord MACRO coord hl, \#

; from rgbds-structs
DEF bytes MACRO new_field \1, RB, \2

; the "small one-line macro" example from the docs, with '.' as a statement separator
DEF pusha MACRO push af . push bc . push de . push hl

; or '\' as a statement separator
DEF wait_vblank MACRO : \ ldh a, [rSTAT] \ and STATF_BUSY \ jr nz, :-

Basically these would be equivalent:

DEF mac MACRO ...

MACRO mac
    ...
ENDM
aaaaaa123456789 commented 3 years ago

Throwing this out there early: how would this interact with REDEF and traditional macro definitions?

Rangi42 commented 3 years ago

You can PURGE and redefine macros, so REDEF mac MACRO ... should work like that. Just like the other REDEFs, it would only work for an undefined symbol or one that's already a macro. (REDEF can't change types.)

ISSOtm commented 3 years ago

I don't think the added complexity is worth saving two lines per such macro.

aaaaaa123456789 commented 3 years ago

This feature came up in a debate about replacing current uses of EQUS, so keep that in mind.

Rangi42 commented 3 years ago

It wouldn't really be complex at all to implement. The parser rule is trivial:

def_macro   : def_id T_POP_MACRO {
            // The `endofline` is handled by `lexer_CaptureDefMacroLine`
            if (lexer_CaptureDefMacroLine(&captureBody))
                sym_AddMacro($1, captureBody.lineNo, captureBody.body,
                         captureBody.size);
        }
;

And the lexer function is a simplified lexer_CaptureMacroBody:

bool lexer_CaptureDefMacroLine(struct CaptureBody *capture)
{
    startCapture(capture);

    /* If the file is `mmap`ed, we need not to unmap it to keep access to the macro */
    if (lexerState->isMmapped)
        lexerState->isReferenced = true;

    int c = EOF;

    /* Capture up to a newline or EOF */
    do {
        c = nextChar();
    } while (c != '\n' && c != '\r' && c != EOF);

    if (c == '\n' || c == '\r') {
        /* The newline has been captured, but we don't want it! */
        if (c == '\r' && peek() == '\n') {
            shiftChar();
            lexerState->captureSize--;
        }
        lexerState->captureSize--;
    }

    endCapture(capture);
    /* A newline puts us at the start of the line */
    if (c != EOF)
        lexerState->atLineStart = true;

    /* Returns true if a newline terminated the body, false if it reached EOF first */
    return c != EOF;
}

(Haven't tested that, but I think it would work.)

ISSOtm commented 3 years ago

That's still some additional maintaining complexity due to more code, which I believe is not worth the associated feature. EQUS is not meant to be an alternative to macros, it's only a more efficient shorthand whenever things line up right. (Hell, I'm pretty sure you could leverage old-style macro definition and ONE_LINER equs "MACRO\n" to squeeze out the first line.)

Proper "in-line" macros are separate feature, which have been discussed, and I don't think EQUS is close to a good fit for them.

Rangi42 commented 3 years ago

I agree that EQUS is a poor substitute for short macros, but rgbasm(5) specifically recommends it for "small one-line macros", and some projects do use it that way (like text EQUS "db 0,"). At the very least the documentation should be updated to advise against this: its own example, DEF pusha EQUS "push af\npush bc\npush de\npush hl\n", ought to be a macro with a four-line body. Maybe it could recommend an inline usage like DEF tiles EQUS "* 16" (as used in both pret and gb-open-world) until user-defined functions can do that better.

ISSOtm commented 3 years ago

You're right that the documentation definitely needs updating. Using multiple lines in an EQUS is largely an "advanced" feature, which interacts with the lexer and parser in non-obvious ways, so the documentation especially should not promote it. (Though very few people seem to read the documentation, lol.)

aaaaaa123456789 commented 3 years ago

That's still some additional maintaining complexity due to more code, which I believe is not worth the associated feature. EQUS is not meant to be an alternative to macros, it's only a more efficient shorthand whenever things line up right. (Hell, I'm pretty sure you could leverage old-style macro definition and ONE_LINER equs "MACRO\n" to squeeze out the first line.)

Proper "in-line" macros are separate feature, which have been discussed, and I don't think EQUS is close to a good fit for them.

Whether a feature was meant to be used for a purpose and whether it is used for that purpose are completely separate matters. When you pull the rug under projects by removing a feature because you don't like what it is being used for and don't add a replacement, you create a problem for everyone. (This is why I mentioned this feature was brought up in the context of #905; it isn't really that interesting unless that is being planned.)

This file is a perfectly good example of why single-line macros would be desirable; you can easily imagine that file becoming completely unmanageable if each EQUS was replaced by a three-line macro expansion. #901 might have helped in that regard, but I can't even imagine how that would interact with macro definitions in any sane way at all, and I'm pretty sure the only sane way to handle regular macros would be to require that ENDM appears on a line by itself.

All that being said, this is pretty much a non-issue unless #905 is being considered. As long as that's not on the radar, EQUS covers this use case pretty well.

ISSOtm commented 3 years ago

We will get to alternatives to EQUS when we get there. Since nobody so far has had the meantime to draft a replacement to EQUS, it's here to stay.

We will not remove a feature until an acceptable alternative exists for its use cases. I don't know how many times I will have to emphasize this so people will stop complaining about us "pulling the rug"—we aren't, and we won't.

Rangi42 commented 9 months ago

Oh hey, Ruby has something like this: https://zverok.space/blog/2023-12-01-syntax-sugar5-endless-methods.html

(And this combines well with multiple instructions per line: DEF pusha MACRO push af :: push bc :: push de :: push hl.)

Rangi42 commented 1 month ago

With unlimited-length strings, we could do this:


MACRO DEF_MACRO
    REDEF mac_name EQUS "\1"
    shift
    REDEF mac_body EQUS "\#"
    REDEF mac_inner EQUS "MACRO {mac_name}\n\t{mac_body}\nENDM"
    {mac_inner}
ENDM

    DEF_MACRO text, "db \"<TEXT>\", "
    DEF_MACRO pushall, push af :: push bc :: push de :: push hl
    DEF_MACRO faceperson, faceobject
Rangi42 commented 1 month ago

Until/unless we remove EQUS expansion (#905), there's no real need for single-line macro syntax; and by the time we do that, we'll probably have better ideas for how to do this. (By then we may also have named macro args, or other additions that wouldn't work well with this DEF name MACRO body proposal.) So I'm closing this.