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:
\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}