rgbds icon indicating copy to clipboard operation
rgbds copied to clipboard

[Feature request] Single-line macro syntax

Open Rangi42 opened this issue 4 years ago • 11 comments

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

Rangi42 avatar Jul 02 '21 16:07 Rangi42

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

aaaaaa123456789 avatar Jul 02 '21 17:07 aaaaaa123456789

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.)

Rangi42 avatar Jul 02 '21 18:07 Rangi42

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

ISSOtm avatar Jul 04 '21 10:07 ISSOtm

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

aaaaaa123456789 avatar Jul 04 '21 11:07 aaaaaa123456789

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.)

Rangi42 avatar Jul 04 '21 17:07 Rangi42

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.

ISSOtm avatar Jul 04 '21 22:07 ISSOtm

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.

Rangi42 avatar Jul 04 '21 22:07 Rangi42

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.)

ISSOtm avatar Jul 04 '21 22:07 ISSOtm

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.

aaaaaa123456789 avatar Jul 05 '21 09:07 aaaaaa123456789

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.

ISSOtm avatar Jul 05 '21 10:07 ISSOtm

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 avatar Dec 02 '23 02:12 Rangi42