Rule authors want to write age>=18=>allow, no spaces and no parentheses. Tiny change, visually. Which layer gets to decide what counts as a token?
In Racket the reader builds the initial syntax representation. Read normally, that fragment can arrive as one symbol rather than the operators its author pictures. A macro could pick the symbol's spelling apart, but that's a little parser inside the notation, and it still can't redefine the lexical conventions around the macro. A custom reader fits when the language truly needs different token boundaries or delimiter rules.
Expansion does other work. It can reject malformed rule forms, set up bindings and translate a rule into calls. Runtime still has to get an age value, handle its absence and perform any permitted action. If an attempt form shows up later, expansion can arrange a call into a retry engine. Whether that engine repeats a test, an action or both is a runtime promise the notation must spell out.
A named language can package the reader and expander entry points when the choices hang together, at the price of documentation and tooling work. If the request means only calling an existing comparison and returning a decision, a plain library call may already say it clearly. The notation earns its place only if readers can still find their way.