I made some minor changes below. Alongside my suggestions are explanations as to why. This is a hard command to describe!

> (defun mark-sexp (&optional arg allow-extend)
>  "Set mark ARG sexps from point or move mark ARG sexps.

Mark can be moved ARG steps. Press C-M-@ C-M-@ C-u 3 C-M-@. There will be five total sexps marked. I'm hoping this explains that case as well, and giving an intuition for what this command does ("move mark ARG steps").

> When invoked interactively without a prefix argument and no active
> region, mark moves one sexp forward.

I moved this first, both because I suspect it's the most common use case, but also because it's the the simplest to understand.

> When invoked interactively without a prefix argument, and region
> is active, mark moves one sexp away from point (i.e., forward
> if mark is at or after point, back if mark is before point), thus
> extending the region by one sexp.
> With ALLOW-EXTEND non-nil (interactively, with prefix argument),
> the place mark goes is the same place \\[forward-sexp] would move
> with the same value of ARG; if the mark is active, it moves ARG
> sexps from its current position, otherwise it is set ARG sexps
> from point.

Moved this earlier to keep the entire interactive block together.

> When the region is active, the direction the region is extended
> depends on the relative position of mark and point. This means the
> direction can be changed by pressing \\[exchange-point-and-mark]
> before this command..

I moved this to a separate section to simplify the earlier parts, and to call out that this is possible whenever the region is active.

> When called from Lisp with ALLOW-EXTEND omitted or nil, mark is
> set ARG (defaulting to 1) sexps from point.

Removed extra m from "omitted".

> This command assumes point is not in a string or comment."

Altogether, the docstring with my suggestions looks like:

> (defun mark-sexp (&optional arg allow-extend)
>  "Set mark ARG sexps from point or move mark ARG sexps.
> When invoked interactively without a prefix argument and no active
> region, mark moves one sexp forward.
> When invoked interactively without a prefix argument, and region
> is active, mark moves one sexp away from point (i.e., forward
> if mark is at or after point, back if mark is before point), thus
> extending the region by one sexp.
> With ALLOW-EXTEND non-nil (interactively, with prefix argument),
> the place mark goes is the same place \\[forward-sexp] would move
> with the same value of ARG; if the mark is active, it moves ARG
> sexps from its current position, otherwise it is set ARG sexps
> from point.
> When the region is active, the direction the region is extended
> depends on the relative position of mark and point. This means the
> direction can be changed by pressing \\[exchange-point-and-mark]
> before this command.
> When called from Lisp with ALLOW-EXTEND omitted or nil, mark is
> set ARG (defaulting to 1) sexps from point.
> This command assumes point is not in a string or comment."

This is a complicated command, for sure -- which is partially why I want simple functions to mark sexps forward and backward: to not have to think about different cases. Can we fork off a discussion about those functions? Having simple functions allows the user to do what they want without having to learn complex nuance.

On Thu, Apr 27, 2023 at 2:14 PM Juri Linkov <juri@linkov.net> wrote:
> I tried to describe the behavior in the doc string as follows:
>
>   (defun mark-sexp (&optional arg allow-extend)
>     "Set mark ARG sexps from point or move mark one sexp.
>   When called from Lisp with ALLOW-EXTEND ommitted or nil, mark is
>   set ARG sexps from point; ARG defaults to 1.
>   With ALLOW-EXTEND non-nil (interactively, with prefix argument),
>   the place mark goes is the same place \\[forward-sexp] would move
>   with the same value of ARG; if the mark is active, it moves ARG
>   sexps from its current position, otherwise it is set ARG sexps
>   from point; ARG defaults to 1.
>   When invoked interactively without a prefix argument and no active
>   region, mark moves one sexp forward.
>   When invoked interactively without a prefix argument, and region
>   is active, mark moves one sexp away of point (i.e., forward
>   if mark is at or after point, back if mark is before point), thus
>   extending the region by one sexp.  Since the direction of region
>   extension depends on the relative position of mark and point, you
>   can change the direction by \\[exchange-point-and-mark].
>   This command assumes point is not in a string or comment."
>
> It is still somewhat complicated and confusing, but at least it's
> accurate, I think.

mark-sexp has a counterpart mark-word that has almost the same
implementation and docstring.  So this could be fixed in both places.