Example of the \loop command in Tralics

In this page we describe a non-trivial example of the \loop command. We also show how to analyse the transcript file produced by Tralics.

The example

Consider a document that contains the following code:

% this is file testloop.tex $Revision: 1.4$
%\tracingall
\let\olda\a %% \newcount defines \a globally...
\countdef\td 4 \td=0%% no need to allocate a counter
\newif\ifprime \newif\ifunknown
\newcount\n \newcount\p \newcount\d \newcount\a \newcount\N
\def\primes#1{2,~3% assume that #1 is at least 3
\n=#1 \advance\n by-2 % n more to go
\p=5 % odd primes starting with p
\def\printp{,% we will invoke \printp if p is prime
\N=\n \divide\N by 8 \multiply \N by -8 \advance\N by \n
\ifnum\N=0 {
}\else{ }\fi
\ifnum\n=1 and~\fi % this precedes the last value
\def\printifprime{\testprimality \ifprime\printp\fi}
\def\testprimality{{\d=3 \global\primetrue
\def\trialdivision{\a=\p \divide\a by\d
\global\advance\td by 1 %% count calls to trialdivision
\ifnum\a>\d \unknowntrue\else\unknownfalse\fi
\multiply\a by\d
\ifnum\a=\p \global\primefalse\unknownfalse\fi}

The first thirty prime numbers are \primes{30}. trial division macro was
expanded \the\td\ times. %% Knuth says cpu time=4 sec, I see 0.02
\let\a\olda %% restore


The result

The XML translation by Tralics is:


<?xml version='1.0' encoding='iso-8859-1'?>
<!DOCTYPE unknown SYSTEM 'unknown.dtd'>
<!-- translated from latex by tralics 2.2 (pl3)-->
<unknown>
<p>The first thirty prime numbers are 2,&nbsp;3, 5, 7, 11, 13,
17, 19, 23, 29, 31, 37, 41, 43,
47, 53, 59, 61, 67, 71, 73, 79,
83, 89, 97, 101, 103, 107, 109, and&nbsp;113. trial division macro was
expanded 132 times.</p>
</unknown>


The example above is in the TeXbook, at the end of Chapter 20. We just added the code that computes the number of calls of \trialdivision, and the test of \n mod 8, that avoids long lines in the XML output. You may wish to look at the description of: \newcount, \newif, \ifnum.

What is in the transcript file?

We show here the content of the transcript file for version 2.15.4 rather than 2.2 (pl3) or 2.7 (pl5). The XML file is the same, modulo (a) the encoding, which is now UTF-8, (b) the comment line with the version also indicates teh compilation time, (c) &nbsp; has been replaced aby a character. The transcript file starts like this

Transcript file of tralics 2.15.4 for file testloop1.tex
Start compilation: 2015/12/02 16:44:19
OS: Apple, machine maclaurin
Starting translation of file testloop1.tex.
Output encoding: UTF8  (idem transcript).
Left quote is  right quote is '
\notrivialmath=1
++ Input encoding is 1 (iso-8859-1) for the main file
++ file .tralics_rc does not exist.
++ file ../confdir/.tralics_rc exists.
Configuration file identification: standard $Revision: 2.24$
Configuration file has type \documentclass
No \documentclass in source file
Using some default type
dtd is unknown from unknown.dtd (standard mode)
OK with the configuration file, dealing with the TeX file...
There are 30 lines


These lines are printed before translation starts. What you can see is that some configuration file was used, that did not define a document type for Tralics compatible with the current \documentclass (there is no document class specified in the source). As a consequence, the main XML element will be called <unknown>.

Let's remove the comment on the line with \tracingall. This will have as effect to add much more lines to the transcript file. (We could also use the --verbose flag, this would enter verbose mode from the start, and add some additional lines to the transcript file).

As a general rule, all lines of the form [number] text indicate source lines, read by Tralics. Other lines start with a plus sign, an open brace, or a command. A line of the form {\let} indicates that the main command interpreter of Tralics has executed the command \let. In some cases, a second line is printed by the C++ code associated to the command. Here you can see the arguments read by the command, and the effects: new and old value of \a.

[3] \let\olda\a %% \newcount defines \a globally...
{\let}
{\let \olda \a}
{changing \olda=undefined}
{into \olda=\a}


Here, you can see that \newif reads a command, and calls twice the inter code of \let.

[5] \newif\ifprime \newif\ifunknown
{\newif}
{\newif \ifprime}
{changing \primetrue=undefined}
{into \primetrue=macro:->\let \ifprime \iftrue }
{changing \primefalse=undefined}
{into \primefalse=macro:->\let \ifprime \iffalse }


This is now the trace for \global\primetrue. The \global command is a prefix that applies to the assignment that follows, that can be of the form \def cmd or \count nbr=...; the next token is fully expanded. In this case, \primetrue is a user defined command, the transcript file shows the body and the arguments (there are none here). The third line shows the argument of \global (the first token in the assignment sequence). What happens is the following; if the first non-expandable token is not an assignment operator, you will get an error of the form You cannot use the prefix \global before \par. Otherwise, the assignment will be marked global. You can have more than one prefix (for instance, before \def); so you can see You cannot use the prefix \global\long before \count.

{\global}
\primetrue ->\let \ifprime \iftrue
{\global\let}
{\let \ifprime \iftrue}
{globally changing \ifprime=\iffalse}
{into \ifprime=\iftrue}


This behaviour is different from TeX: if you say \global \let \ifprime \iftrue, TeX will print only one line in its transcript file. And for the code above, you will see:

{\global}
\primetrue ->\let \ifprime \iftrue


Chapter 24 of the TeXbook starts with explanations of what a <number> or a <dimen> is. These rules are complicated, this is why Tralics prints in the transcript file lines starting with +scanint or +scandimen whenever such a thing is scanned. In general the result is used by a command (here \countdef) or put in a register (here register number 4, via its symbolic name \td). If you ask: what is \td, the answer will be \count4 (previous versions of Tralics printed something like \countref1540, for a command that accesses to position 1540 in the table of integers). If the value of a counter is changed, you see the old and new value. If these two values are equal, you see reassigning instead of changing.

[4] \countdef\td 4 \td=0
{\countdef}
+scanint for \countdef->4
{\countdef \td=\count4}
{\count4}
+scanint for \td->0
{reassigning \count4=0}
...
{\divide}
+scanint for \N->8 %this divides N by 8
{changing \count69=28 into \count69=3}


In previous versions of Tralics, the third line was of the form {\countdef \n=1553}, where 1553 is to be interpreted as above (since 1540 was the index for counter 4, an easy computation shows that 1553 is the index of counter 17; the current version improves a bit the interpretation of the transcript file; even in silent mode, the transcript contains a line for each use of command of the type \countdef.

[6] \newcount\n \newcount\p \newcount\d \newcount\a \newcount\N
{\newcount}
{\countdef \n=\count65}
...
{\countdef \N=\count69}


You may see lines like these when a group starts. Here a begin-group character, i.e., a character of \catcode 1, an open brace, caused a new group to be pushed on the stack. The current level is increased to 2 (bottom level is number 1). You can also see that the current line is 27 (because all the stack alterations are the result of the \primes command.)

{begin-group character}
+stack: level + 2 for brace entered on line 27


You may see lines like these when a group ends. Here an end-group character, i.e., a character of \catcode 2, a closing brace, caused the stack to be unwound. The current execution level decreases (from 2 down to 1). The commands \ifunknown and \iterate are restored (we truncated the value). Two integers are restored (\d and \a).

{end-group character}
+stack: restoring \ifunknown=\iffalse.
+stack: restoring \count68=0.
+stack: restoring \iterate=macro:->\ifnum ...
+stack: restoring \count67=0.
+stack: level - 2 for brace from line 27


If in TeX, you say \par The first thirty prime numbers are, a line is added to the transcript for each group of letters (a line is also added for each non-letter, for instance a space). As can been seen, a paragraph is started, and horizontal mode is entered because of the first letter.

{the letter T}
{horizontal mode: the letter T}
{blank space  }
{the letter f}
{blank space  }
{the letter t}
{blank space  }
{the letter p}
{blank space  }
{the letter n}
{blank space  }
{the letter a}
{blank space  }


This is the Tralics transcript file

Character sequence: The first thirty prime numbers are .


You can see that there is no difference between spaces and other characters. The character sequence starts with the character that follows the space after colon, and ends with the characters that precedes the period (in the example, the first character is a T, the last is a space). Currently, mode changes are not logged.

Analysis of the transcript file

This is what the transcript file contains, starting with the line that follows the definitions. Lines preceding line 25 contain definitions and global assignments. Current mode is outer vertical, so that newline characters between definitions are ignored. Empty lines and \par commands are ignored as well, but appear in the transcript.

[25]
{\par}
[26]
{\par}


A new paragraph is started when the letter T is seen. The following things happen: current mode becomes horizontal; a new XML element is created; a new frame is pushed on the stack. What you see in the transcript file is the name of the frame, in some cases, as here, the frame and the element have the same name. At the end of the run, the XML tree contains three sons: the <p> element created here, and two newline characters, one before and one after. This element contains one son: a piece of text.

For every character translated, something is printed in the transcript file. The transcript file is flushed whenever a non-character is seen (here the \primes command). The characters are put in another buffer, flushed when needed.

[27] The first thirty prime numbers are \primes{30}. trial division macro was
{Push p 1}
Character sequence: The first thirty prime numbers are .


You can see the body and arguments of the \primes command (the transcript file contains a single line, contrarily to TeX that inserts newline characters whenever the line is full). You can also see that the expansion of ~ is \nobreakspace. The lines shown here add some characters to the XML buffer, namely 2, 3, a comma, and whatever \nobreakspace translates to.

\primes #1->2,~3\n =#1 \advance \n by-2 \p =5 \loop \ifnum \n >0
#1<-30
Character sequence: 2,.
~ ->\nobreakspace
{\nobreakspace}
Character sequence: 3.


This is the transcript for \n =#1 \advance \n by-2 \p =5.

{\count65}
+scanint for \n->30
{changing \count65=0 into \count65=30}
+scanint for \n->-2
{changing \count65=30 into \count65=28}
{\count66}
+scanint for \p->5
{changing \count66=0 into \count66=5}


This is the outer loop: we have to compute \n prime numbers (since we have already printed 2 and 3, the current value of \n is 28), and the candidate \p is 5. After this is found to be prime or not-prime, we increment it by two (because even numbers greater than two cannot be prime) and continue the loop.

{\loop}
{changing \iterate=undefined}
{into \iterate=macro:->\ifnum \n >0 ... }
\iterate ->\ifnum \n >0 \printifprime \advance \p by2 \relax
\expandafter \iterate \fi


Here you can see the expansion of \ifnum \n >0. Two numbers are compared, the result is true. Tralics indicates the values, it also indicates for each conditional its serial number.

+\ifnum1
+scanint for \ifnum->28
+scanint for \ifnum->0
+iftest1 true


We do not indicate here what is done with the number 5. We skip some code and go to 13.

\printifprime ->\testprimality \ifprime \printp \fi
...


The idea is the following: we open a group, and set \d to 3. We make the a priori assumption that our number is prime (the \global is needed, because we have to know, outside the group, whether the number is prime or not). The \trialdivision macro sets \ifunknown to some value: if set to false, the status is known, indicated by \ifprime. The number \d is incremented by 2. Loop invariant: we assume that \p is not a multiple of any prime number less that \d).

\printifprime ->\testprimality \ifprime \printp \fi
\testprimality ->{\d =3 \global \primetrue \loop \trialdivision
\ifunknown \advance \d by2 \repeat }
{begin-group character}
+stack: level + 2 for brace entered on line 27
{\count67}
+scanint for \d->3
{changing \count67=0 into \count67=3}
{\global}
\primetrue ->\let \ifprime \iftrue
{\global\let}
{\let \ifprime \iftrue}
{globally changing \ifprime=\iftrue}
{into \ifprime=\iftrue}


We are now in the inner loop. (note that \iterate is locally redefined for each loop)

{\loop}
{changing \iterate=macro:->... }
{into \iterate=macro:-> ...  }
\iterate->\trialdivision \ifunknown \advance \d by2 \relax
\expandafter \iterate \fi


We divide 13 by 3, quotient in \a, and increment the number of divisions. We set \ifunknown to false, because 3 is less than 4. Said otherwise our divider \d is less that the square root of \p, not big enough to conclude that \p is prime.

\trialdivision ->\a =\p \divide \a by\d
\ifnum \a >\d \unknowntrue \else \unknownfalse \fi
\multiply \a by\d \ifnum \a =\p \global \primefalse \unknownfalse \fi
{\count68}
+scanint for \a->13
{changing \count68=0 into \count68=13}
{\divide}
+scanint for \a->3
{changing \count68=13 into \count68=4}
{\global}
+scanint for \td->1
{globally changing \count4=4 into \count4=5}
+\ifnum28
+scanint for \ifnum->4
+scanint for \ifnum->3
+iftest28 true
\unknowntrue ->\let \ifunknown \iftrue
{\let}
{\let \ifunknown \iftrue}
{changing \ifunknown=\iffalse}
{into \ifunknown=\iftrue}
+\else28
+\fi28


We multiply 4 by 3, in \a, and compared to \p. Comparison is false, \d does not divide \p. We cannot conclude that \p is not prime.

{\multiply}
+scanint for \a->3
{changing \count68=4 into \count68=12}
+\ifnum29
+scanint for \ifnum->12
+scanint for \ifnum->13
+iftest29 false
+\fi29


Since we do not know that our number is prime, we must continue checking. We first increment out divider. We find it convenient to exchange the order of the \iterate and \fi tokens (in this case, the only effect of the \fi is to pop the condition stack). Purpose of the \relax: the scanint procedure reads an integer, and could evaluate the \expandafter without this token (said otherwise, the space after the digit 2 is not necessary).

+\ifunknown30
+iftest30 true
+scanint for \d->2
{changing \count67=3 into \count67=5}
{\relax}
{\expandafter \iterate \fi}
+\fi30


We have to execute the loop a second time.

\iterate ->\trialdivision \ifunknown \advance \d by2 \relax
\expandafter \iterate \fi
\trialdivision ->\a =\p \divide \a by\d \global \advance \td by 1
\ifnum \a >\d \unknowntrue \else \unknownfalse \fi
\multiply \a by\d \ifnum \a =\p \global \primefalse \unknownfalse \fi


We see here that 5 does not divide 13. However 13 is less than 5\times(5+1), hence is prime. Said otherwise, conditional number 31 shows that primality of 13 depends only on the result of test 32.

{\count68}
+scanint for \a->13
{changing \count68=12 into \count68=13}
{\divide}
+scanint for \a->5
{changing \count68=13 into \count68=2}
{\global}
+scanint for \td->1
{globally changing \count4=5 into \count4=6}
+\ifnum31
+scanint for \ifnum->2
+scanint for \ifnum->5
+iftest31 false
+\else31
\unknownfalse ->\let \ifunknown \iffalse
{\let}
{\let \ifunknown \iffalse}
{changing \ifunknown=\iftrue}
{into \ifunknown=\iffalse}
+\fi31
{\multiply}
+scanint for \a->5
{changing \count68=2 into \count68=10}
+\ifnum32
+scanint for \ifnum->10
+scanint for \ifnum->13
+iftest32 false
+\fi32


Since we know whether our number is prime or not, it is time to conclude the inner loop. The assignment \let\iterate\relax is not really useful, as will be seen in a minute. Its purpose is to release memory.

+\ifunknown33
+iftest33 false
+\fi33
{\let}
{\let \iterate \relax}
{changing \iterate=macro:->... }
{into \iterate=\relax}


The last action of \testprimality is to close the current group. What you can see is that two integers are restored (\a and \d). The value of \iterate is restored; this is the code of the outer loop.

{end-group character}
+stack: restoring \ifunknown=\iffalse.
+stack: restoring \count68=0.
+stack: restoring \iterate=macro:->\ifnum \n >0 ... \fi .
+stack: restoring \count67=0.
+stack: level - 2 for brace from line 27


Our number is prime! We print it.

+\ifprime34
+iftest34 true
\printp ->,\N =\n \divide \N by 8 ...
Character sequence: ,.


We print a space or a newline character, depending on whether \n is zero modulo 8. This piece of code is not in the original program. The test could also have been: \ifnum\N=0 \char13 \else \char32 \fi

{\count69}
+scanint for \N->25
{changing \count69=2 into \count69=25}
{\divide}
+scanint for \N->8
{changing \count69=25 into \count69=3}
{\multiply}
+scanint for \N->-8
{changing \count69=3 into \count69=-24}
+scanint for \N->25
{changing \count69=-24 into \count69=1}
+\ifnum35
+scanint for \ifnum->1
+scanint for \ifnum->0
+iftest35 false
+\else35
{begin-group character}
+stack: level + 2 for brace entered on line 27
Character sequence:  .
{end-group character}
+stack: level - 2 for brace from line 27
+\fi35


It is not the last number, so that only comma-space is printed.

+\ifnum36
+scanint for \ifnum->25
+scanint for \ifnum->1
+iftest36 false
+\fi36


The transcript shows a \fi34 corresponding to the \ifprime above and \fi27 that corresponds to nothing. In fact, we skipped a big part of the trace. Testing whether n is prime for n=5, 7, 9, 11, 13, and 15 uses the conditional 5, 12, 19, 24, 34, and 41: you can see that one more test is needed for 13 than for other numbers; remember that if a number is prime, a further test has to be done: is it the last one?

+scanint for \number->13
Character sequence: 13.
+scanint for \n->-1
{changing \count65=25 into \count65=24}
+\fi34
+scanint for \p->2
{changing \count66=13 into \count66=15}
{\relax}
{\expandafter \iterate \fi}
+\fi27


This considers the number 15.

\iterate ->\ifnum \n >0 \printifprime \advance \p by2 \relax ...
+\ifnum37
+scanint for \ifnum->24
+scanint for \ifnum->0
+iftest37 true
\printifprime ->\testprimality \ifprime \printp \fi
\testprimality ->{\d =3 \global \primetrue \loop \trialdivision ...
{begin-group character}
+stack: level + 2 for brace entered on line 27
+scanint for \d->3
{changing \count67=0 into \count67=3}
{\global}
\primetrue ->\let \ifprime \iftrue
{\global\let}
{\let \ifprime \iftrue}
{globally changing \ifprime=\iftrue}
{into \ifprime=\iftrue}


Dividing 15 by 3 is not enough to prove that 15 is a prime number.

{\loop}
{changing \iterate=macro:->\ifnum ... \fi }
{into \iterate=macro:->\trialdivision \ifunknown ... \fi }
\iterate ->\trialdivision \ifunknown \advance \d by2 \relax ...
\trialdivision ->\a =\p \divide \a by\d \global \advance \td by 1  ...
{\count68}
+scanint for \a->15
{changing \count68=0 into \count68=15}
{\divide}
+scanint for \a->3
{changing \count68=15 into \count68=5}
{\global}
+scanint for \td->1
{globally changing \count4=6 into \count4=7}
+\ifnum38
+scanint for \ifnum->5
+scanint for \ifnum->3
+iftest38 true
\unknowntrue ->\let \ifunknown \iftrue
{\let}
{\let \ifunknown \iftrue}
{changing \ifunknown=\iffalse}
{into \ifunknown=\iftrue}
+\else38
+\fi38


But dividing 15 by 3 is enough to prove that 15 is a not prime number.

{\multiply}
+scanint for \a->3
{changing \count68=5 into \count68=15}
+\ifnum39
+scanint for \ifnum->15
+scanint for \ifnum->15
+iftest39 true
{\global}
\primefalse ->\let \ifprime \iffalse
{\global\let}
{\let \ifprime \iffalse}
{globally changing \ifprime=\iftrue}
{into \ifprime=\iffalse}
\unknownfalse ->\let \ifunknown \iffalse
{\let}
{\let \ifunknown \iffalse}
{changing \ifunknown=\iftrue}
{into \ifunknown=\iffalse}
+\fi39


Skipping 6000 lines.

+iftest563 false
+\fi563
{\let}
{\let \iterate \relax}
{changing \iterate=macro:->\ifnum ... \fi }
{into \iterate=\relax}
Character sequence: . trial division macro was .
[28] expanded \the\td\ times. % Knuth says cpu time=4 sec, I see 0.02
Character sequence: expanded .
{\the}
{\the \td}
\the->132.
Character sequence: 132.
{\ }
Character sequence: times. .


Line 29 of the input file is a hack: the \a command is not defined by plainTeX, so that Knuth can use, but it is defined in LaTeX, and we have to restore it.

[29] \let\a\olda %% restore
{\let}
{\let \a \olda}
{changing \a=\count68}
{into \a=\a}


The last line of the file is empty, this is equivalent \par, and has as effect to finish the current element.

[30]
{\par}
{Text:The first thirty prime numbers are 2, 3, 5, 7, 11, 13,
17, 19, 23, 29, 31, 37, 41, 43,
47, 53, 59, 61, 67, 71, 73, 79,
83, 89, 97, 101, 103, 107, 109, and 113. trial division macro was
expanded 132 times. }
{Pop 1: document_v p_v}
++ Input stack empty at end of file
`

Note the last line of the file, the empty one, is not really needed. Without it, the last line of the XML result would have been expanded 132 times. </p></unknown>. In fact, when Tralics executes a \par command, it removes the last space (that corresponds in general to the end-of-line on the line before the empty line), and adds a newline character in the XML tree after the <p> element. In this example, there is a space before the comment line 28, the EOL characters on lines 28 and 29 are commented out, and this one is visible if the paragraph is ended without a \par command.

back to home page © INRIA 2003, 2006 Last modified $Date: 2015/12/15 09:13:31$