Extract a character at position x from a string using primitives

Something like this? Shown both with ignoring and with counting spaces. Set up for pdftex, but with commented lines for pdflatex.

%\documentclass{article}
\def\extract{\catcode`\ =\active\extractx}
\def\extractx#1#2{\def\extractcount{#2}\expandafter\extracthelp#1\relax}
\def\extracthelp#1#2\relax{%
  \edef\extractcount{\the\numexpr\extractcount-1\relax}%
  \ifnum\extractcount=0\relax``#1''\else%
    \ifx\relax#2\relax[EOF]\else\extracthelp#2\relax\fi\fi%
  }
%\begin{document}
Spaces ignored: \def\x{This is a string 2015/12.}

\extract{\x}{1}

\extract{\x}{5}

\extract{\x}{6}

\extract{\x}{22}

Spaces counted: {\catcode`\ =\active \gdef\x{This is a string 2015/12.}}

\extract{\x}{1}

\extract{\x}{5}

\extract{\x}{6}

\extract{\x}{22}

%\end{document}
\bye

enter image description here


Assuming your string only contains printable ASCII characters, here's a quite clumsy implementation. The last example shows that braced groups are treated as single items.

\catcode`@=11

\def\extract{\futurelet\next\extract@save}
\def\extract@save{%
  \ifx\next[%
    \expandafter\extract@save@opt
  \else
    \let\extract@return\@firstofone
    \expandafter\extract@
  \fi
}
\def\extract@save@opt[#1]{%
  \def\extract@return##1{\def#1{##1}}%
  \extract@
}
\def\extract@#1#2{%
  \edef\extract@string{#1}%
  \extract@loop=\z@
  \extract@max=#2\relax
  \expandafter\extract@i\extract@string\extract
}
\def\extract@i{%
  \advance\extract@loop\@ne
  \futurelet\next\extract@ii
}
\def\extract@ii{%
  \ifx\next\extract
    \expandafter\@gobble
  \else
    \expandafter\extract@iii
  \fi}
\def\extract@iii{%
  \ifx\next\space@token
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\extract@check@space}%
  {\extract@check}%
}
\def\extract@check@space{%
  \ifnum\extract@loop=\extract@max
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\extract@return{ }\extract@finish}%
  {\expandafter\extract@i\@firstofone}%
}
\def\extract@check#1{%
  \ifnum\extract@loop=\extract@max
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\extract@return{#1}\extract@finish}%
  {\extract@i}%
}
\def\extract@finish#1\extract{}
\newcount\extract@loop
\newcount\extract@max
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\@firstofone#1{#1}
\long\def\@gobble#1{}
\begingroup\def\\ \\{\endgroup\let\space@token= }\\ \\
\catcode`@=12

X\extract{abc def}{3}X (should be c)

X\extract{abc def}{4}X (should be space)

X\extract{abc def}{5}X (should be d)

X\extract{abc def}{8}X (should be empty)

X\extract{abc }{4}X (should be space)

\def\mystring{abc def}

X\extract\mystring{7}X (should be f)

\extract[\foo]\mystring{6}

{\tt\meaning\foo}

\extract[\foo]{A{BC}D}{2}

{\tt\meaning\foo}

\bye

Without the optional argument, the item is just returned in the input stream; with the optional argument it is saved in the control sequence given between brackets.

enter image description here

Of course I'd do it quite differently. With this code, \extract is fully expandable as shown with the final \edef example. Note that you can also count backwards. The code requires an e-TeX engine, so not Knuth TeX.

\input expl3-generic

\ExplSyntaxOn
\cs_new:Npn \extract #1 #2
 {
  \str_item:fn { #1 } { #2 }
 }
\cs_generate_variant:Nn \str_item:nn {f}
\ExplSyntaxOff

X\extract{abc def}{3}X (should be c)

X\extract{abc def}{4}X (should be space)

X\extract{abc def}{5}X (should be d)

X\extract{abc def}{8}X (should be empty)

X\extract{abc }{4}X (should be space)

\def\mystring{abc def}

X\extract\mystring{7}X (should be f)

X\extract\mystring{-2}X (should be e)

\edef\foo{\extract\mystring{6}}

{\tt\meaning\foo}

\bye

enter image description here