> Macros do not have to deal with syntax at the level of "how does this sequence of tokens reshape into a tree"

That's not syntax. Syntax is concerned whether a sequence of words are valid expressions in a language and determines syntactic categories for these. You can lookup a better definition of syntax, I'm too lazy.

These are all DEFUN forms. Some are valid Lisp, some are not.

(defun foo () ()) is valid

(defun () foo ()) is invalid

(defun () () foo) is invalid

(defun (foo) () foo) is invalid

(defun foo () foo) is valid

(defun () foo foo) is invalid

(defun foo foo ()) is invalid

(defun (setf foo) (setf foo) foo) is valid

(defun foo (setf foo) (setf foo)) is invalid

(defun (setf foo) foo (setf foo)) is invalid

Just by changing the order of subforms in a macro form we can produce valid Lisp and invalid Lisp forms.

(defmacro defun (name args &body forms) ...)

Does not tell you that. You need to implement that logic in the macro somewhere.

> sentence-macro doesn't have guess what part of the utterance is the object and which is the verb. The verb is the third argument, and that is that.

Macros implement more complex syntax. Check the ANSI CL specification and its EBNF syntax declarations some time.

> When we say that Lisp has no syntax

Then it's just wrong and misleading.

Take the DEFUN macro:

the EBNF (extended backus naur form) syntax definition for DEFUN is:

defun function-name lambda-list [[declaration* | documentation]] form*

Is

(defun (bla blub) (&rest foo &optional bar)
(declare fixnum) "oops" ((((fooo))))))

a valid defun expression ????????????????

The macro has to check that. It better rejects invalid expressions. It also has to look at the elements of the form to destructure them in the right way, so that it can process them and create a new valid form.

The Lisp implementation provides for the implementation of DEFUN as much as:

(defmacro defun (spec args &body body ...) ...)

The macro language does not allow further specifications of the name the arglist or the body in the macro interface. All it gets are spec, args and body. Now the macro has to implement the syntax for spec, args and body.

Questions the macro has to answer:

* is the function name a symbol or a list of the form (setf foo)
* is the arglist a valid lambda-list? Now check the EBNF syntax for lambda lists with whole/optional/key/rest options with default values and what have you.
* now it has to parse the body:
* is the declaration valid? Now check the EBNF syntax for declarations to see what needs to be done.
* is the documentation a string at the right position?
* is the body a sequence of forms? Now check the EBNF syntax for FORM.

This all has to be backed into the DEFUN macro somehow or checked from there.
And not all implementations are good at it.

Various syntax errors:

The name is not valid:

CL-USER 14 > (defun (foo bar) baz (list))

Error: (FOO BAR) is neither of type SYMBOL nor a list of the form (SETF SYMBOL).
1 (abort) Return to level 0.
2 Return to top loop level 0.

A symbol is not a valid lambda list:

CL-USER 16 > (defun (setf bar) baz (list))
(SETF BAR)

CL-USER 17 > (compile '(setf bar))

Error: Invalid lambda list: BAZ

Keyword argument is wrong:

CL-USER 19 > (defun foo (&key ((aa))) (list))
FOO

CL-USER 20 > (compile 'foo)

Error: Malformed (keyword variable) form in &key argument ((AA))

Wrong declaration:

CL-USER 24 > (defun foo (&key (aa)) (declare inline-function foo))

Error: Alist element INLINE-FUNCTION is not a cons or NIL

Wrong form:

CL-USER 26 > (defun foo (&key (aa))
(declare (inline foo))
(((lambda ()
(lamda () ())))))
FOO

CL-USER 27 > (compile 'foo)

Error: Illegal car ((LAMBDA NIL (LAMDA NIL NIL)))
in compound form (((LAMBDA NIL #))).

And so on.

An INFIX macro:

(infix 3 + 2 ^ 5)

It has to implement infix syntax.

If it is still not clear, below is the syntax for the LOOP macro. The LOOP implementation has to implement the syntax, so that

(loop for i below 70 do (print i))

is recognized as a valid program and it better detect that

(loop do (print i) for i below 70 )

is not a valid program, because it violates the syntax below.

The ``simple'' loop form:

loop compound-form* => result*

The ``extended'' loop form:

loop [name-clause] {variable-clause}* {main-clause}* => result*

name-clause::= named name
variable-clause::= with-clause | initial-final | for-as-clause
with-clause::= with var1 [type-spec] [= form1] {and var2 [type-spec] [= form2]}*
main-clause::= unconditional | accumulation | conditional | termination-test | initial-final
initial-final::= initially compound-form+ | finally compound-form+
unconditional::= {do | doing} compound-form+ | return {form | it}
accumulation::= list-accumulation | numeric-accumulation
list-accumulation::= {collect | collecting | append | appending | nconc | nconcing} {form | it}
[into simple-var]
numeric-accumulation::= {count | counting | sum | summing | }
maximize | maximizing | minimize | minimizing {form | it}
[into simple-var] [type-spec]
conditional::= {if | when | unless} form selectable-clause {and selectable-clause}*
[else selectable-clause {and selectable-clause}*]
[end]
selectable-clause::= unconditional | accumulation | conditional
termination-test::= while form | until form | repeat form | always form | never form | thereis form
for-as-clause::= {for | as} for-as-subclause {and for-as-subclause}*
for-as-subclause::= for-as-arithmetic | for-as-in-list | for-as-on-list | for-as-equals-then |
for-as-across | for-as-hash | for-as-package
for-as-arithmetic::= var [type-spec] for-as-arithmetic-subclause
for-as-arithmetic-subclause::= arithmetic-up | arithmetic-downto | arithmetic-downfrom
arithmetic-up::= [[{from | upfrom} form1 | {to | upto | below} form2 | by form3]]+
arithmetic-downto::= [[{{from form1}}1 | {{{downto | above} form2}}1 | by form3]]
arithmetic-downfrom::= [[{{downfrom form1}}1 | {to | downto | above} form2 | by form3]]
for-as-in-list::= var [type-spec] in form1 [by step-fun]
for-as-on-list::= var [type-spec] on form1 [by step-fun]
for-as-equals-then::= var [type-spec] = form1 [then form2]
for-as-across::= var [type-spec] across vector
for-as-hash::= var [type-spec] being {each | the}
{{hash-key | hash-keys} {in | of} hash-table
[using (hash-value other-var)] |
{hash-value | hash-values} {in | of} hash-table
[using (hash-key other-var)]}
for-as-package::= var [type-spec] being {each | the}
{symbol | symbols |
present-symbol | present-symbols |
external-symbol | external-symbols}
[{in | of} package]
type-spec::= simple-type-spec | destructured-type-spec
simple-type-spec::= fixnum | float | t | nil
destructured-type-spec::= of-type d-type-spec
d-type-spec::= type-specifier | (d-type-spec . d-type-spec)
var::= d-var-spec
var1::= d-var-spec
var2::= d-var-spec
other-var::= d-var-spec
d-var-spec::= simple-var | nil | (d-var-spec . d-var-spec)
Arguments and Values:

compound-form---a compound form.

name---a symbol.

simple-var---a symbol (a variable name).

form, form1, form2, form3---a form.

step-fun---a form that evaluates to a function of one argument.

vector---a form that evaluates to a vector.

hash-table---a form that evaluates to a hash table.

package---a form that evaluates to a package designator.

type-specifier---a type specifier. This might be either an atomic type specifier or a compound type specifier, which introduces some additional complications to proper parsing in the face of destructuring; for further information, see Section 6.1.1.7 (Destructuring).

result---an object.