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