How to go about creating custom parser in Latex

Version 1:

My answer is using mostly TeX primitives. I know there are other ways to do it, especially using expl3, but I'm not there yet, so...

I defined the main macro \makelinesegment that will take one argument, in the example, 1:1-1:2-2:2.

\makelinesegment appends will pass its argument to \@coordpair, which takes as argument something in the form x:y-. \@coordpair will take the x and y coordinates and add them to \this@path as (x,y)--.

When \@coordpair reaches the end of the input (i.e. finds \empty:\empty-), it finishes and return to \makelinesegment.

Up to now we have (1,1)--(1,2)--(2,2)--. \makelinesegment then calls \gobblemm to remove the final -- from \this@path.

Finally, \makelinesegment calls \draw\this@path;.

\documentclass{article}

\usepackage{tikz}

\makeatletter
\def\makelinesegment#1{%
  \def\this@path{}%
  \@coordpair#1-\empty:\empty-\@empty%
  \expandafter\gobblemm\this@path;
  \draw\this@path;
}
\def\gobblemm#1--;{\edef\this@path{#1}}
\def\@coordpair#1:#2-#3\@empty{%
  \ifx#1\empty
  \else
    \edef\this@path{\this@path(#1,#2)--}%
    \expandafter\@coordpair%
  \fi
  #3\@empty%
}
\makeatother

\begin{document}
\pagenumbering{gobble}

\begin{tikzpicture}
  \makelinesegment{1:1-1:2-2:2}
\end{tikzpicture}

\end{document}

Version 2:

As I said in the comments, this notation may (and will) cause ambiguity when using negative coordinates. So I propose a slight change: replace the - that separates the coordinate pairs by ,. Like this: 1:1,1:2,2:2.

The solution then becomes even simpler, for we can use LaTeX's \@for to iterate on a comma-separated list.

Now the \makelinesegment macro just passes its argument to \@for, who splits the coorinate pairs and passes them to \@pair. \@pair then appends the coordinate pair (x,y)-- to \this@list.

And the rest is like in the first version; \gobblemm removes the last -- and \makelinesegment calls \draw\this@path;.

\documentclass{article}

\usepackage{tikz}

\makeatletter
\def\makelinesegment#1{%
  \def\this@path{}%
  \@for\@pair:=#1\do{%
    \expandafter\splitpair\@pair\@empty%
  }%
  \expandafter\gobblemm\this@path;%
  \draw\this@path;
}
\def\gobblemm#1--;{\edef\this@path{#1}}
\def\splitpair#1:#2\@empty{%
  \edef\this@path{\this@path(#1,#2)--}%
}
\makeatother

\begin{document}
\pagenumbering{gobble}

\begin{tikzpicture}
  \makelinesegment{1:1,1:2,2:2}
\end{tikzpicture}

\end{document}

This is an expl3 implementation.

\lineparser_make_line should be quite self-explaining, it splits the parameter at -, does a map to transform a:b to (a,b) and then calls \draw with the sequence joined with --s. The splitting of the coordinate get a bit ugly because : is a letter in \ExplSyntaxOn\ExplSyntaxOff blocks, so if : would be used as a argument delimiter directly it would have the wrong catcode.

\documentclass{article}

\usepackage{xparse,expl3,tikz}

\ExplSyntaxOn
% We need
%\cs_new:Npn\__lineparser_transform_coords:w#1:#2\q_mark}{
% but the ":" is changed by\ExplSyntaxOn, so we have to use a trick to use the normal colon in the parameter
\use:x{\cs_new:Npn\exp_not:N\__lineparser_transform_coords:w##1\c_colon_str##2\exp_not:N\q_mark}{
  (#1,#2)
}
\seq_new:N\__lineparser_coordinates_seq
\seq_new:N\__lineparser_coordinates_transformed_seq
\cs_new:Nn\lineparser_make_line:n{
  \seq_set_split:Nnn\__lineparser_coordinates_seq{ - }{#1}
  \seq_set_map:NNn
    \__lineparser_coordinates_transformed_seq
    \__lineparser_coordinates_seq
    {
      \__lineparser_transform_coords:w##1\q_mark
    }
  \draw
    \seq_use:Nn
      \__lineparser_coordinates_transformed_seq
      { -- }
    ;
}
\NewDocumentCommand\makelinesegment{m}{
  \lineparser_make_line:n{#1}
}
\ExplSyntaxOff

\begin{document}
\pagenumbering{gobble}

\begin{tikzpicture}
  \makelinesegment{1:1-1:2-2:2}
\end{tikzpicture}

\end{document}

Similar to Phelype Oleiniks answer there are special functions to deal with comma lists, so if \makelinesegment{1:1,1:2,2:2} is to be parsed, you can for example use

\documentclass{article}

\usepackage{xparse,expl3,tikz}

\ExplSyntaxOn
% We need
%\cs_new:Npn\__lineparser_transform_coords:w#1:#2\q_mark}{
% but the ":" is changed by\ExplSyntaxOn, so we have to use a trick to use the normal colon in the parameter
\use:x{\cs_new:Npn\exp_not:N\__lineparser_transform_coords:w##1\c_colon_str##2\exp_not:N\q_mark}{
  (#1,#2)
}
\seq_new:N\__lineparser_coordinates_seq
\seq_new:N\__lineparser_coordinates_transformed_seq
\cs_new:Nn\lineparser_make_line:n{
  \seq_set_from_clist:Nn\__lineparser_coordinates_seq{#1}% <-- The change
  \seq_set_map:NNn
    \__lineparser_coordinates_transformed_seq
    \__lineparser_coordinates_seq
    {
      \__lineparser_transform_coords:w##1\q_mark
    }
  \draw
    \seq_use:Nn
      \__lineparser_coordinates_transformed_seq
      { -- }
    ;
}
\NewDocumentCommand\makelinesegment{m}{
  \lineparser_make_line:n{#1}
}
\ExplSyntaxOff

\begin{document}
\pagenumbering{gobble}

\begin{tikzpicture}
  \makelinesegment{1:1,1:2,2:2}
\end{tikzpicture}

\end{document}

This is a bit longer and more verbose than the primitive TeX solution by Phelype Oleinik, but I think it is easier to understand.


There was no test file provided (again) so I borrowed one from one of the other answers. For such a simple transformation you don't really need any extra parsing code just define the expansion to happen inline:

enter image description here

\documentclass{article}

\usepackage{tikz}

\def\makelinesegment#1{\draw\xmakelinesegment#1-\empty;}
\def\xmakelinesegment#1:#2-#3{%
(#1,#2)
\ifx\empty#3\else--\expandafter\xmakelinesegment\fi
#3}


\begin{document}
\pagenumbering{gobble}

\begin{tikzpicture}
 \makelinesegment{1:1-1:2-2:2}
\end{tikzpicture}

\end{document}

Tags:

Parsing