Extended syntax for integer expressions

Lars Hellström Lars.Hellstrom@math.umu.se
Thu, 3 Dec 1998 18:22:18 +0100 (MET)


I do not know how it is for fontinst users in general, but I sometimes find
fontinst's syntax for integer expressions a little annoying. It is not that
I cannot use it and it is certainly not that I find it inefficient, but I
do often have trouble reading the expressions I have written because there
are simply too many braces! If my text editor didn't have a "balance"
command, I would often be in serious trouble.

Thus I began to consider the question of whether one could implement the
operations so that they are written between the operands (infix notation)
instead of before (prefix notation). It turned out that it wasn't really
that hard, so I made the necessary adjustments to my own fontinst, so that
I now have the following syntax for an <INTEGER EXPRESSION>:

  <INTEGER EXPRESSION>  is  <TERM>  or
      <INTEGER EXPRESSION> \+ <TERM>  or
      <INTEGER EXPRESSION> \- <TERM>
  <TERM>  is  <FACTOR>  or
      <TERM> \* <FACTOR>  or
      <TERM> \/ <FACTOR>  or
      <TERM> \% <FACTOR>
  <FACTOR>  is  <ANY TeX NUMBER>  or
      \int{<NAME>}  or
      \width{<GLYPH>}  or
      \heigth{<GLYPH>}  or
      \depth{<GLYPH>}  or
      \italic{<GLYPH>}  or
      \kerning{<LEFT>}{<RIGHT>}  or
      \neg{<INTEGER EXPRESSION>}  or
      \add{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \sub{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \mul{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \div{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \scale{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \max{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \min{<INTEGER EXPRESSION>}{<INTEGER EXPRESSION>}  or
      \( <INTEGER EXPRESSION> \)

(`is' separates the <...>-quantity being defined from its definition. `or'
separates two alternative definitions.)

In particular, observe that any standard integer expression will fit these
rules, so any old integer expression retains its meaning.

The \+, \-, \*, \/, and \% control sequences acts as the addition,
subtraction, multiplication, division, and scaling operations
respectively---they differ from the \add, \sub, \mul, \div, and \scale
operations only in their syntax, not in what they compute. The \( and \)
control sequences work as left and right parenthesis respectively, making
it possible to change the order in which the operations are performed.

A few examples of these extended integer expressions and what they evaluate to:
  1\+2\*3               evaluates to 7
  1\+2\*\add{3}{4}      evaluates to 15
  5\+4\*\sub{3}{2\-1}   evaluates to 13 ( = 5+4*(3-(2-1)) )
  1\+2\*\(3\+4\*5\)     evaluates to 47 ( = 1+2*(3+4*5) )

But why have I written to the fontinst list about this? Simply because I
think that this extension of the syntax for integer expressions may be of
interest to fontinst users in general and not only to me. It might even be
included in the next release of fontinst, it Ulrik so permits.

There is however one problem with this extension: It slows down the
computation of integer expressions. Since fontinst is already a pretty slow
program, I would not be surprised if changes in its more central parts that
make it even slower are not appreciated. It therefore seems most natural to
me that whether or not fontinst should be equipped for the above extended
syntax for integer expressions is decided through a general debate on the
subject in the fontinst list. I hereby invite to such a debate.

I have made some experiments trying to determine how much slower fontinst
becomes from the overhead of handling infix operations, using the latin.mtx
and t1.etx from v1.801 fontinst. These experiments indicated that the
slackening of the speed was in the order of 1% (but other people's
measurements would be highly welcome as I have come to be somewhat
sceptical to how precise the "time taken" reports of my TeX really are). It
should also be observed that these measurements were for code that used
prefix operations (\add,\sub,etc.) throughout and that it should be
possible to get back some of the time lost by using infix operations
(\+,\-,etc.) instead, as the implementation is made primarily for these.

The implementation follows below. Any suggestions on how it can be improved
(in particular, how it can be sped up) are of course of interest. The main
idea is that the control sequences \calculate_b and \calculate_c are used
to hold the commands to perform an operation which could not be performed
just yet due to that one of the operands has not yet been computed.
Therefore \calculate_c is performed after every <TERM> to take care of any
delayed \*, \/, or \% and \calculate_b is performed after every <INTEGER
EXPRESSION> to take care of any delayed \+ or \-. \calculate_b may act on
\b_count (hence its name) and \result. \calculate_c may act on \c_count and
\result.

\def\eval_expr#1{
   \begingroup
      \restore_calc_b\restore_calc_c
      \global\result=#1\relax
      \calculate_c\calculate_b
   \endgroup
}
\def\restore_calc_b{\let\calculate_b\relax}
\def\restore_calc_c{\let\calculate_c\relax}
\restore_calc_b\restore_calc_c
\def\({0\bgroup \restore_calc_b\restore_calc_c \global\result=}
\def\){\relax \calculate_c\calculate_b \egroup}
\def\+{
   \relax
   \calculate_c\calculate_b
   \b_count=\result
   \def\calculate_b{\do_add\restore_calc_b}
   \global\result=
}
\def\do_add{\global\advance \result \b_count}
\def\-{
   \relax
   \calculate_c\calculate_b
   \b_count=\result
   \def\calculate_b{\do_sub\restore_calc_b}
   \global\result=
}
\def\do_sub{\advance \b_count -\result \global\result=\b_count}
\def\*{
   \relax
   \calculate_c
   \c_count=\result
   \def\calculate_c{\do_mul\restore_calc_c}
   \global\result=
}
\def\do_mul{\global\multiply \result \c_count}
\def\/{
   \relax
   \calculate_c
   \c_count=\result
   \def\calculate_c{\do_div\restore_calc_c}
   \global\result=
}
\def\do_div{\divide \c_count \result \global\result=\c_count}
\def\%{
   \relax
   \calculate_c
   \c_count=\result
   \def\calculate_c{\do_scale\restore_calc_c}
   \global\result=
}
\def\do_scale{
   \global\multiply \result \c_count
   \global\divide \result \one_thousand
}
\def\neg#1{\result
   \eval_expr{#1}
   \global\result=-\result
}
\def\add#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \global\advance\result by \a_count
}
\def\sub#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \advance\a_count by -\result
   \global\result=\a_count
}
\def\mul#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \global\multiply\result by \a_count
}
\def\div#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \divide\a_count by \result
   \global\result=\a_count
}
\def\max#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \ifnum\a_count>\result \global\result=\a_count \fi
}
\def\min#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \ifnum\a_count<\result \global\result=\a_count \fi
}
\def\scale#1#2{\result
   \eval_expr{#1}
   \a_count=\result
   \eval_expr{#2}
   \global\multiply\result by \a_count
   \global\divide\result by \one_thousand
}

Feature: If you fail to match \( and \) properly, TeX will give you an
error message about mismatched groups. TeX's automatic insertion/deletion
of tokens will however be the right thing to do to keep your groups
properly nested, so you can always continue just by pressing return/enter.


Lars Hellström