I'm not sure why, but when the string unrnn is examined, TeX is at grouping level 3; however, when r is being processed, the grouping level decreases to 2.
\documentclass{article}
\usepackage{soul}
% Initially empty, then should be nonempty afterward
\newcommand*{\state}{}
\makeatletter
\def\SOUL@soeverytoken{%
\showthe\currentgrouplevel
\show\state
\showthe\SOUL@token
\ifx\state\empty% Should only happen once...
\renewcommand*{\state}{Nonempty}%
(\the\SOUL@token)%
\else
[\the\SOUL@token]%
\fi
}
\makeatother
\begin{document}
% For some reason, the string "unrnn" gives an unexpected result.
% \so{nnrnn} \par % (n)[n][r][n][n]
\so{unrnn} \par % (u)n[n][n]
% \so{unnnn} \par % (u)[n][n][n][n]
% \so{unrn} \par % (u)[n][r][n]
\end{document}
I added some diagnostic commands to see what happens.
> 3.
\SOUL@everytoken ->\showthe \currentgrouplevel
\show \state \showthe \SOUL@t...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> \state=macro:
->.
\SOUL@everytoken ...urrentgrouplevel \show \state
\showthe \SOUL@token \ifx ...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> u.
\SOUL@everytoken ...w \state \showthe \SOUL@token
\ifx \state \empty \renewc...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> 3.
\SOUL@everytoken ->\showthe \currentgrouplevel
\show \state \showthe \SOUL@t...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> \state=macro:
->Nonempty.
\SOUL@everytoken ...urrentgrouplevel \show \state
\showthe \SOUL@token \ifx ...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> n.
\SOUL@everytoken ...w \state \showthe \SOUL@token
\ifx \state \empty \renewc...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> 2.
\SOUL@everytoken ->\showthe \currentgrouplevel
\show \state \showthe \SOUL@t...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> \state=macro:
->.
\SOUL@everytoken ...urrentgrouplevel \show \state
\showthe \SOUL@token \ifx ...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> r.
\SOUL@everytoken ...w \state \showthe \SOUL@token
\ifx \state \empty \renewc...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
> 2.
\SOUL@everytoken ->\showthe \currentgrouplevel
\show \state \showthe \SOUL@t...
l.24 \so{unrnn}
\par % (u)[n](r)[n][n]
?
If I try with \so{nnrnn} the grouping level starts from 2.
I'd avoid overloading \so.
\documentclass{article}
\usepackage{soul}
\makeatletter
\newcommand{\myso}[1]{%
\begingroup
\gdef\my@state{}%
\def\SOUL@soeverytoken{%
\ifx\my@state\empty% Should only happen once...
\gdef\my@state{x}%
(\the\SOUL@token)%
\else
[\the\SOUL@token]%
\fi
}%
\so{#1}
\endgroup
}
\makeatother
\begin{document}
\myso{nnrnn} \par % (n)[n][r][n][n]
\myso{unrnn} \par % (u)n[n][n]
\myso{unnnn} \par % (u)[n][n][n][n]
\myso{unrn} \par % (u)[n][r][n]
\end{document}

A different implementation with expl3 and \text_map_inline:nn.
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\myso}{m}
{
\airwreck_process:n { #1 }
}
\bool_new:N \l_airwreck_first_bool
\cs_new_protected:Nn \airwreck_process:n
{
\bool_set_true:N \l_airwreck_first_bool
\text_map_inline:nn { #1 }
{
\bool_if:NTF \l_airwreck_first_bool
{% first item
(##1)
\bool_set_false:N \l_airwreck_first_bool
}
{% other items
[##1]
}
}
}
\ExplSyntaxOff
\begin{document}
\myso{nnrnn} \par % (n)[n][r][n][n]
\myso{unrnn} \par % (u)n[n][n]
\myso{unnnn} \par % (u)[n][n][n][n]
\myso{unrn} \par % (u)[n][r][n]
\end{document}

soulfor this when the LaTeX kernel these days have tools to go through token lists etc. – daleif Jun 20 '23 at 09:25