code:
# -*- shell-script -*-
# @configure_input@
#
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
# 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
#
# (This file was originally part of GNU Automake.)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Defines for Automake testing environment.
# Tom Tromey <tromey@cygnus.com>

# Be Bourne compatible.
# (Snippet copied from configure's initialization in Autoconf 2.57.)
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
  emulate sh
  NULLCMD=:
  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
  # is contrary to our usage.  Disable this feature.
  alias -g '${1+"$@"}'='"$@"'
elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
  set -o posix
fi

# Ensure we are running from the right directory.
test -f ./defs || {
   echo "defs: not found in current directory" 1>&2
   exit 1
}

# If srcdir is not set, then we are not running from `make check', be verbose.
if test -z "$srcdir"; then
   VERBOSE=x
   # compute $srcdir.
   srcdir=`echo "$0" | sed -e 's,/[^\\/]*$,,'`
   test $srcdir = $0 && srcdir=.
fi

# Ensure $srcdir is set correctly.
test -f $srcdir/defs.in || {
   echo "$srcdir/defs.in not found, check \$srcdir" 1>&2
   exit 1
}

me=`echo "$0" | sed -e 's,.*[\\/],,;s/\.test$//'`

# See how redirections should work.  User can set VERBOSE to see all
# output.
test -z "$VERBOSE" && {
   exec > /dev/null 2>&1
}

# Make sure we override the user shell.
SHELL='@SHELL@'
export SHELL

srcdir_abs=`cd $srcdir && pwd`
test -z "$TEXI2DVI" && TEXI2DVI=$srcdir_abs/texi2dvi

# be sure we use texi2dvi and texindex from our own directory, not
# whatever happens to be in the path.
echo "defs: prepending $srcdir_abs to PATH for testing."
PATH="@abs_builddir@:@abs_srcdir@:$PATH"

# Some shells forget to export modified environment variables.
# (See note about `export' in the Autoconf manual.)
export PATH

# User can override various tools used.
test -z "$PERL" && PERL='@PERL@'

if test -n "$required"
then
  for tool in $required
  do
    # Check that each required tool is present.
    case $tool in
      hevea)
        # HeVeA supports -version only.  Use configure to find it.
        test -n "@HEVEA@" || exit 77
	;;
      tex)
        # No all versions of Tex support `--version', so we use
        # a configure check.
        test -n "@TEX@" || exit 77
	;;
      # Generic case: the tool must support --version.
      *)
	echo "$me: running $tool --version"
	( $tool --version ) || exit 77
	;;
    esac
  done
fi

# Always use an absolute srcdir.  Otherwise symlinks made in subdirs
# of the test dir just won't work.
case "$srcdir" in
 [\\/]* | ?:[\\/]*)
    ;;

 *)
    srcdir=`CDPATH=: && cd "$srcdir" && pwd`
    ;;
esac

chmod -R a+rwx testSubDir > /dev/null 2>&1
rm -rf testSubDir > /dev/null 2>&1
mkdir testSubDir

# Copy in some files we need.
cp $srcdir/../doc/texinfo.tex testSubDir || exit 1

cd ./testSubDir

# Unset the user's envvars that might change the behavior.
unset TEXI2DVI_BUILD_MODE
unset TEXI2DVI_BUILD_DIRECTORY

echo "=== Running test $0"

# POSIX no longer requires 'egrep' and 'fgrep',
# but some hosts lack 'grep -E' and 'grep -F'.
EGREP='@EGREP@'
FGREP='@FGREP@'

# The amount we should wait after modifying files depends on the platform.
# For instance, Windows '95, '98 and ME have 2-second granularity
# and can be up to 3 seconds in the future w.r.t. the system clock.
sleep='sleep @MODIFICATION_DELAY@'

# The tests call `make -e' but we do not want $srcdir from the environment
# to override the definition from the Makefile.
testsrcdir=$srcdir
unset srcdir

# create_input_texi < INPUT
# -------------------------
# Create input.texi adding stdin inside a default prologue/epilogue
create_input_texi ()
{
  {
    cat << END
\input texinfo   @c -*-texinfo-*-
@setfilename input
@settitle input

@ifnottex
@node Top
@top input
@end ifnottex

END
    cat
    cat << END
@bye
END
  } >input.texi
}

# create_input_tex < INPUT
# ------------------------
# Create input.tex adding stdin inside a default prologue/epilogue
create_input_tex ()
{
  {
    cat << END
\documentclass{article}

\begin{document}
END
    cat
    cat << END
\end{document}
END
  } >input.tex
}

# TEXI2DVI_run status [options...]
# --------------------------------
# Run texi2dvi with OPTIONS, and fail if does not exit with STATUS.
TEXI2DVI_run ()
{
  expected_exitcode=$1
  shift
  exitcode=0
  if test -n "$VERBOSE"; then
    set x --debug --verbose ${1+"$@"}
    shift
  fi
  texi2dvi ${1+"$@"} 2>stderr >stdout || exitcode=$?
  cat stderr
  cat stdout
  test $exitcode = $expected_exitcode || exit 1
}

# TEXI2DVI_fail [options...]
# ---------------------------
# Run texi2dvi with OPTIONS, and fail if does not exit with failure.
TEXI2DVI_fail ()
{
  TEXI2DVI_run 1 ${1+"$@"}
}

# TEXI2DVI_pass [options...]
# ---------------------------
# Run texi2dvi with OPTIONS, and fail if does not exit with failure.
TEXI2DVI_pass ()
{
  TEXI2DVI_run 0 ${1+"$@"}
}

# optionset_get N OPTIONSET<1> -- OPTIONSET<2> -- ...
# ---------------------------------------------------
# Return OPTIONSET<N>
optionset_get ()
{
  n=$1
  shift
  # Find the optionset.
  while test $n != 1; do
    case $1 in
      --) n=`expr $n - 1`;;
    esac
    shift
  done
  # Return the optionset.
  while true; do
    case $#:$1 in
      0:) break;;
    *:--) shift; break;;
       *) echo "$1"; shift;;
    esac
  done
}

# list_files ()
# -------------
# Return the sorted list of files in this directory, except the "invisible" ones.
list_files ()
{
  for f in *
  do
    case $f in
      texinfo.tex | stdout | stderr | ls.expected | ls.value );;
      *) echo $f;;
    esac
  done | sort
}

# assert_files (EXPECTED-FILES)
# -----------------------------
# Only those files are valid in the current directory.
assert_files ()
{
  if test -n "$VERBOSE"; then
    ls
  fi
  list_files > ls.value
  echo "$*" | tr ' ' '\n' | sort > ls.expected
  diff -u ls.expected ls.value
}

# assert_and_remove_files (EXPECTED-FILES)
# ----------------------------------------
# Only those files are valid in the current directory.  Remove them
# afterwards.
assert_and_remove_files ()
{
  assert_files "$@"
  rm -rf "$@"
}

# title TITLE...
# --------------
# Make a huge title to ease the reading of the output.
title ()
{
  (
    set +x
    echo "    $@    " | sed -e 's/./=/g'
    echo "    $@    "
    echo "    $@    " | sed -e 's/./=/g'
  )
}

# $latex_paragraph
# ----------------
# Some lengthy LaTeX.
# grep -E '^[a-zA-Z]+$' /usr/share/dict/words | sed 200q | pr --page-width=76 --column=5 --omit-header
latex_paragraph='
A              Acrux        Aegean           Ahmad          Albany
AOL            Actaeon        Aelfric        Ahmadabad      Albee
Aachen         Acton        Aeneas           Ahmed          Alberio
Aaliyah        Acts         Aeneid           Ahriman        Albert
Aaron          Acuff        Aeolus           Aida           Alberta
Abbas          Ada          Aeroflot         Aiken          Alberto
Abbasid        Adam         Aeschylus        Aileen         Albigensian
Abbott         Adams        Aesculapius    Aimee     Albion
Abby           Adan         Aesop            Ainu           Albireo
Abdul          Adana        Afghan           Airedale       Albuquerque
Abe            Adar         Afghanistan    Airedales        Alcatraz
Abel           Addams       Afghans          Aisha          Alcestis
Abelard        Adderley       Africa         Ajax           Alcibiades
Abelson        Addie        African          Akbar          Alcmena
Aberdeen       Addison        Africans       Akhmatova      Alcoa
Abernathy      Adela        Afrikaans        Akihito        Alcott
Abidjan        Adelaide       Afrikaner      Akita          Alcuin
Abigail        Adele        Afrikaners     Akkad     Alcyone
Abilene        Adeline        Afro           Akron          Aldan
Abner          Aden         Afrocentrism   Al        Aldebaran
Abraham        Adenauer       Afros          Ala     Alden
Abram          Adhara       Ag       Alabama        Alderamin
Abrams         Adidas       Agamemnon        Alabaman       Aldo
Absalom        Adirondack     Agassi         Alabamans      Aldrin
Abuja          Adirondacks    Agassiz        Alabamian      Alec
Abyssinia      Adkins       Agatha           Alabamians     Aleichem
Abyssinian     Adler        Aggie            Aladdin        Alejandra
Ac             Adolf        Aglaia           Alamo          Alejandro
Acadia         Adolfo       Agnes            Alamogordo     Alembert
Acapulco       Adolph       Agnew            Alan           Aleppo
Accra          Adonis       Agni             Alana          Aleut
Acevedo        Adonises       Agra           Alar           Aleutian
Achaean        Adrian       Agricola         Alaric         Alex
Achebe         Adriana        Agrippa        Alaska         Alexander
Achernar       Adriatic       Agrippina      Alaskan        Alexandra
Acheson        Adrienne       Aguilar        Alaskans       Alexandria
Achilles       Advent       Aguinaldo        Alba           Alexei
Aconcagua      Adventist      Aguirre        Albania        Alexis
Acosta         Advents        Agustin        Albanian       Alfonso
Acropolis      Advil        Ahab             Albanians      Alfonzo
'

# Turn on shell traces when VERBOSE is set.
if test -n "$VERBOSE"; then
  set -x
else
  :
fi
pwd


code:
#! /bin/sh
# Copyright (C) 2006, 2007  Free Software Foundation, Inc.
#
# This file is part of GNU Texinfo.
#
# GNU Texinfo is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# GNU Texinfo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

required='tex'

. ./defs || exit 1

set -e

# one_run ARGS -- FILES
# ---------------------
# Run texi2dvi with ARGS, expect the FILES to be left.  Remove them.
one_run ()
{
  # Use pstricks.
  cat >input.tex <<EOF
\documentclass[a4paper]{article}
\usepackage{pst-node}
\pagestyle{empty}
\begin{document}
\begin{psmatrix}
  A & B & C & D & E & F
\end{psmatrix}
\psset{arrows=->}
\nccircle{1,1}{0.5}
\nccircle[angleA=30]{1,2}{0.4}
\nccircle[arrows=-,nodesep=0.05]{1,3}{0.2}
\ncloop[angleB=180]{1,4}{1,4}
\ncloop[angleB=180,linearc=0.35,nodesep=0.1]{1,5}{1,5}
\ncloop[angleB=180,linearc=0.15,loopsize=0.4]{1,6}{1,6}

\vspace{1cm}
\begin{psmatrix}
  A & B & C & D
\end{psmatrix}
\psset{arrows=->,nodesep=0.05}
\ncangles[armA=0.8,armB=0,angleA=70,angleB=110,linearc=0.22]{1,1}{1,1}
\ncdiag[arm=0.9,angleA=70,angleB=110,linearc=0.3]{1,2}{1,2}
\ncdiag[arm=0.8,angleA=50,angleB=130,linearc=0.25]{1,3}{1,3}
\ncdiag[arm=0.8,angleA=50,angleB=130,linearc=0.28]{1,4}{1,4}
\end{document}
EOF

  TEXI2DVI_pass $(optionset_get 1 "$@") input.tex
  # There should only be the expected and input files.
  assert_and_remove_files $(optionset_get 2 "$@") input.tex
}

one_run --dvipdf --batch --clean \
   -- \
   input.pdf

repo_name: mthl/texinfo

license: mit


code:
#! /bin/sh
# Copyright (C) 2006, 2007  Free Software Foundation, Inc.
#
# This file is part of GNU Texinfo.
#
# GNU Texinfo is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# GNU Texinfo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

required='hevea'

. ./defs || exit 1

set -e

# Force the creation of auxiliary files.
# Force it to be big enough to trigger the info file splitting.
{
  echo "\tableofcontents"
  for section in 1 2 3 4 5 6
  do
    echo "\section{$i}"
    for subsection in 1 2 3 4 5 6
    do
      echo "\subsection{$i}"
      echo "$latex_paragraph"
    done
  done
} |
create_input_tex

# run_hevea_check FORMAT OPTIONS
# ------------------------------
run_hevea_check ()
{
  title "$@"

  format="$1"
  shift

  # Testing the local build mode.
  TEXI2DVI_pass --batch --$format "$@" input.tex

  # Compute the expected output.
  outfiles=input.$out
  case $format in
     info) outfiles="$outfiles input.info-1 input.info-2";;
  esac

  # Compute the aux files.
  case $format:$@ in
    *clean* ) auxfiles='';;
     *tidy* ) auxfiles='input.t2d';;
     html:*|text:* ) auxfiles='input.haux input.htoc';;
      dvi:* ) auxfiles='input.aux input.log input.toc';;
          * ) auxfiles='';;
  esac
  # There should only be the expected output and the source file.
  assert_files input.tex $outfiles $auxfiles
  rm -fr $outfiles $auxfiles
}

for opt in "" "--clean" "--tidy"
do
  for out in html info text
    do
    run_hevea_check $out $opt
  done
done


code:
#! /bin/sh
# Copyright (C) 2003, 2005, 2007, 2008  Free Software Foundation, Inc.
#
# This file is part of GNU Texinfo.
#
# GNU Texinfo is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# GNU Texinfo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Make sure no "local" variable identifier is used twice, since
# local is not actually portable.

. ./defs || exit 1

set -e

# Make sure quotes are used to protect the value.  See the comment in
# texi2dvi itself about local.
if sed -e 's/^[	 ]*#.*//' $TEXI2DVI |
    $EGREP "\blocal [a-zA-Z_0-9]*=[^\"']" |
    $EGREP -v "\blocal [a-zA-Z_0-9]*=[a-zA-Z0-9]*$" |
    grep .
then
  false
else
  true
fi

# Make sure each name is used only once.
if sed -n -e 's/^#.*//' \
          -e 's/local \([a-zA-Z_0-9]*\)/\1/gp' $TEXI2DVI |
    sort |
    uniq -c |
    grep -v '^.*1 ' |
    grep .
then
  false
else
  true
fi

# Make sure we don't use $().
if $FGREP '$(' $TEXI2DVI
then
  false
else
  true
fi


code:
#! /bin/sh
# Copyright (C) 2003, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
#
# This file is part of GNU Texinfo.
#
# GNU Texinfo is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# GNU Texinfo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

required='tex'

. ./defs || exit 1

set -e

# one_run ARGS -- FILES
# ---------------------
# Run texi2dvi with ARGS, expect the FILES to be left.  Remove them.
one_run ()
{
  # Testing the local build mode.
  # Use @foo to check that -t works properly.
  echo "@foo" | create_input_texi
  TEXI2DVI_pass $(optionset_get 1 "$@") \
     -t '@macro foo' \
     -t 'Foo'        \
     -t '@end macro' \
     input.texi
  # There should only be the expected FILES and the input file.
  assert_and_remove_files $(optionset_get 2 "$@") input.texi
}

one_run --batch \
   -- \
   input.aux input.cp input.dvi input.fn input.ky input.log \
   input.pg input.tp input.vr

one_run --batch -o output.dvi \
   -- \
   input.aux input.cp input.fn input.ky input.log \
   input.pg input.tp input.vr output.dvi

# Testing the clean build mode.
for mode in --clean --build=clean
do
  # There should only be the DVI and the TEXI file.
  one_run $mode --batch -- input.dvi

  # There should only be the DVI and the TEXI file.
  one_run $mode --batch -o output.dvi -- output.dvi
done

# Testing the tidy build mode.
one_run --build=tidy --batch -o output.dvi \
   -- \
   output.dvi output.t2d

# mostly-clean should remove auxiliary files, but not the expected
# output.
touch output.dvi
one_run --build=tidy --batch -o output.dvi --mostly-clean \
   -- \
   output.dvi

# There should only be the DVI and the TEXI file.
create_input_texi </dev/null
cp input.texi input2.texi
one_run --clean --batch input.texi input2.texi \
   -- \
   input2.dvi input2.texi input.dvi


code:
#!/bin/sh
# $Id$
# Copyright 2006, 2011, 2012, 2013 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Original author: Karl Berry.
#
# Test texinfo.tex changes by running various manuals through with an
# old version, saving the .ps result from dvips, doing the same with a
# new version, and comparing.  Idea from Stepan Kasal.
#
# Another option is to run the manuals all the way through using
# texi2dvi, which tests in another way.

tsrc=`cd \`dirname $0\` && pwd`/../..
PATH=$tsrc/util:$PATH

tdoc=$tsrc/doc
default_manuals="$tdoc/texinfo.texi $tdoc/info.texi $tdoc/info-stnd.texi"

olddir=$tsrc/../gnulib/config
newdir=$tdoc
tempdir=$tsrc/@tests/test
full=false
manuals=

while test $# -gt 0; do
  case $1 in
  --f*) full=true;;
  --o*) shift; olddir="$1";;
  --n*) shift; newdir="$1";;
  --t*) shift; tempdir="$1";;
  -*) echo "$0: unknown option \`$1'." >&2; exit 1;;
  *) manuals="$manuals $1";;
  esac
  shift
done

test -z "$manuals" && manuals=$default_manuals
initial_dir=`pwd`

cd $tempdir || exit 1
rm -f *

run_tex() \
{
  TEXINPUTS=.:$mandir: tex $manual \
  || { echo "$0: tex $manual failed." >&2; exit 1; }
}

for manual in $manuals; do
  mandir=`dirname $manual`
  test $mandir = . && mandir=$initial_dir
  manual_base=`basename "$manual" | sed 's/\.[^.]*$//'`

  rm -rf $manual_base.* texinfo.tex
  ln -s $newdir/texinfo.tex texinfo.tex

  if $full; then
    # instead of comparing, do full test of just the new texinfo.tex.
    echo "$0: testing $manual_base... (tex)"
    texi2dvi $manual || { echo "texi2dvi $manual failed." >&2; exit 1; }
    echo "$0: testing $manual_base... (pdf)"
    texi2dvi --pdf $manual \
    || { echo "texi2dvi --pdf $manual failed." >&2; exit 1; }

  else
    echo "$0: testing $manual_base... (new)"
    run_tex
    dvips $manual_base -o || exit 1
    mv $manual_base.ps new.$manual_base.ps

    echo "$0: testing $manual_base... (old)"
    rm -rf $manual_base.* texinfo.tex
    ln -s $olddir/texinfo.tex texinfo.tex
    run_tex
    dvips $manual_base -o || exit 1
    mv $manual_base.ps old.$manual_base.ps

    diff -U0 old.$manual_base.ps new.$manual_base.ps
  fi
done

path: contrib/nontests/txitextest


code:
#! /usr/local/bin/gawk -f

# texi.outline ---  produce an outline from a texinfo source file
#
# Copyright (C) 1998 Arnold David Robbins
#
# TEXI.OUTLINE is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# TEXI.OUTLINE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# NOTE:
#	This program uses gensub(), which is specific to gawk.
#	With some work (split, substr, etc), it could be made to work
#	on other awks, but it's not worth the trouble for me.

BEGIN	\
{
	# Levels at which different nodes can be
	Level["@top"] =	0
	Level["@appendix"] = 1
	Level["@chapter"] = 1
	Level["@majorheading"] = 1
	Level["@unnumbered"] = 1
	Level["@appendixsec"] = 2
	Level["@heading"] = 2
	Level["@section"] = 2
	Level["@unnumberedsec"] = 2
	Level["@unnumberedsubsec"] = 3
	Level["@appendixsubsec"] = 3
	Level["@subheading"] = 3
	Level["@subsection"] = 3
	Level["@appendixsubsubsec"] = 4
	Level["@subsubheading"] = 4
	Level["@subsubsection"] = 4
	Level["@unnumberedsubsubsec"] = 4

	# insure that we were called correctly
	if (ARGC != 2) {
		printf("usage: %s texinfo-file\n", ARGV[0]) > "/dev/stderr"
		exit 1
	}

	# init header counters
	app_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	app_h = 0
	l1_h = l2_h = l3_h = l4_h = 0
}

# skip lines we're not interested in
/^[^@]/	|| ! ($1 in Level)	{ next }

Level[$1] == 1	{
	if ($1 !~ /^@unnumbered/ || $1 !~ /heading/)
		l1_h++
	l2_h = l3_h = l4_h = 0
	Ntabs = 0
	Number = makenumber($1)
	Title = maketitle($0)
	print_title()
}

Level[$1] == 2	{
	l2_h++
	l3_h = l4_h = 0
	Ntabs = 1
	Number = makenumber($1)
	Title = maketitle($0)
	print_title()
}

Level[$1] == 3	{
	l3_h++
	l4_h = 0
	Ntabs = 2
	Number = makenumber($1)
	Title = maketitle($0)
	print_title()
}

Level[$1] == 4	{
	l4_h++
	Ntabs = 3
	Number = makenumber($1)
	Title = maketitle($0)
	print_title()
}

# maketitle --- extract title

function maketitle(str,		text)
{
	$1 = ""		# clobber section keyword
	text = $0
	gsub(/^[ \t]*/, "", text)
	text = gensub(/@[a-z]+{/, "", "g", text)
	text = gensub(/([^@])}/, "\\1", "g", text)
	return text
}

# print_title --- print the title

function print_title(	i)
{
	for (i = 1; i <= Ntabs; i++)
		printf "\t"
	printf("%s %s\n", Number, Title)
}

# makenumber --- construct a heading number from levels and section command

function makenumber(command,	result, lev1)
{
	result = ""
	if (command ~ /^@appendix/) {
		if (Level[command] == 1)
			app_h++

		lev1 = substr(app_letters, app_h, 1)
	} else if (command ~ /^@unnumbered/ || command ~ /heading/) {
		lev1 = "(unnumbered)"
	} else
		lev1 = l1_h ""

	result = lev1 "."
	if (l2_h > 0) {
		result = result l2_h "."
		if (l3_h > 0) {
			result = result l3_h "."
			if (l4_h > 0) {
				result = result l4_h "."
			}
		}
	}
	return result
}


code:
# $Id$
# Makefile to build Texinfo from the Perl POD documentation,
# using pod2texi.
#
# Copyright 2013, 2014, 2015 Free Software Foundation, Inc.
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

perl_pod_subdir = $(shell pwd)/perl-[0-9]*.*/pod
texi_pod_doc = perldoc-all
texi_pod_doc_incl = $(texi_pod_doc)-incl.texi
texi_pod_subdir = $(texi_pod_doc)

texinfo_srcdir = $(shell cd ../../ && pwd)
pod2texi = perl $(texinfo_srcdir)/Pod-Simple-Texinfo/pod2texi.pl
pod2texi_args  = --base-level=section
pod2texi_args += --preamble=''  # we want our own
pod2texi_args += --subdir=$(texi_pod_subdir)

# Unfortunately have to use --no-validate since not all the names and
# L<references> match up.  Split HTML by chapter is the natural thing here.
gendocs_args  = --email bug-texinfo@gnu.org
gendocs_args += --common "--no-warn --no-validate"
gendocs_args += --split chapter
gendocs_args += --no-ascii
gendocs_args += --source $(texi_pod_subdir)
gendocs_args += --tex "--batch -t @finalout"
gendocs_title = "Perl documentation in Texinfo"

all: $(texi_pod_doc_incl)
	rm -rf manual *.aux *.toc *.??
# turns out the combined docs overflow one of TeX's arrays; increase it.
	env save_size=100000 \
	  gendocs.sh $(gendocs_args) $(texi_pod_doc) $(gendocs_title)

$(texi_pod_doc_incl): $(perl_pod_subdir)/*.pod
	rm -rf $(texi_pod_subdir)
#
# We remove all X<...> cross-reference constructs from the pods
# before processing, since they are not parsed properly, resulting
# in many chapters called simply "NAME", e.g., perldebug and perlsyn.
#
# We omit the numerous delta pods, which are uninteresting when
# searching (and take tons of time and space to process).
#
# We also omit perlunicook, since it necessarily has many binary Unicode
# characters, and processing the whole thing in UTF-8 makes it less useful.
#
# Because we start from the source directory, we also lose all the
# generated .pods, such as perluniprops.  We would not want
# all of them (e.g., perltw.pod), but some (like uniprops) might be nice.
# Maybe someday.
#
	cd $(perl_pod_subdir) \
	&& perl -p -i.bak -e 's,X<.*?>>?,,g' *.pod \
	&& $(pod2texi) -o $@ $(pod2texi_args) \
	  `ls *.pod | egrep -v "(delta|unicook)"` \
	&& mv $(texi_pod_subdir) $@ ../..

# updating on www.gnu.org.  We do not install the split HTML files
# because the long file names cause trouble for some GNU webmasters,
# who use encryption software that reduces the maximum file name length
# by about half.  Sigh.
www_target = $(HOME)/gnu/www/www/software/perl/manual
install:
	cp -arf manual/. $(www_target)/.
	rm -rf $(www_target)/html_chapter
	cd $(www_target) && cvs update -P

mostlyclean:
	rm -rf manual $(texi_pod_doc_incl)

# when running make dist, ensure we have none of the build files,
# since the whole contrib/ directory is included in its entirety.
distclean:
	rm -rf manual perl-5.*.* $(texi_pod_doc) $(texi_pod_doc_incl)
	rm -rf *.?? *.??? *.info *~ \#*

path: contrib/perldoc-all/GNUmakefile


code:
$Id$
texinfo/contrib/perldoc-all/README

  Copyright 2013 Free Software Foundation, Inc.

  Copying and distribution of this file, with or without modification,
  are permitted in any medium without royalty provided the copyright
  notice and this notice are preserved.

This stuff is about making Texinfo out of the standard Perl *.pod files.
The results are at http://www.gnu.org/software/perl/manual.

The idea is to unpack the perl distribution here, as in,
  wget http://www.cpan.org/src/5.0/perl-5.NN.M.tar.gz  # with right NN.M
  tar xf perl-*.tar.gz
and run (GNU) make.  Aside from the result hopefully being useful in
itself (just copy the .info file to an Info directory), it also serves
as a nontrivial example of using pod2texi.

path: contrib/perldoc-all/README


code:
<title>%%TITLE%% - GNU Project - Free Software Foundation (FSF)</title>

%%TITLE%%

<address>GNU Project</address>
<address>last updated %%DATE%%</address>

This translation of the Perl
documentation from POD to Texinfo is not official, and not endorsed
by the Perl developers (indeed, they haven't seen it).  It was created
by the GNU Texinfo developers because they found it useful to have the
core Perl documentation available in Info and other formats, and thought
they would share the results.  Suggestions welcome.

This output is created entirely by the Texinfo tools; see the contrib/perldoc-all
directory in the Texinfo sources for the procedure used.

This documentation (%%PACKAGE%%) is available in the following formats:

HTML
    (%%HTML_MONO_SIZE%%K bytes) - entirely on one web page.

HTML - with one web page per
    chapter.

HTML compressed
    (%%HTML_MONO_GZ_SIZE%%K gzipped characters) - entirely on
    one web page.

HTML compressed
    (%%HTML_CHAPTER_TGZ_SIZE%%K gzipped tar file) -
    with one web page per chapter.

Info document
    (%%INFO_TGZ_SIZE%%K bytes gzipped tar file).

TeX dvi file
    (%%DVI_GZ_SIZE%%K bytes gzipped).

PDF file
    (%%PDF_SIZE%%K bytes).

Texinfo source
    (%%TEXI_TGZ_SIZE%%K bytes gzipped tar file).

You can buy printed copies of
some manuals (among other items) from the Free Software Foundation;
this helps support FSF activities.

(This page generated by the %%SCRIPTNAME%%
script.)

Please send general FSF & GNU inquiries to
<gnu@gnu.org>.
There are also other ways to contact
the FSF.

Please send broken links and other corrections or suggestions to
<%%EMAIL%%>.

Copyright © 2013 Free Software Foundation, Inc.

This page is licensed under a Creative
Commons Attribution-NoDerivs 3.0 United States License.


code:
\input texinfo
@c This top-level file is so trivial, it has to be public domain.
@setfilename perldoc-all.info
@settitle Perl pod documentation
@finalout
@tex
\global\hfuzz=\maxdimen
\global\hbadness=10000
@end tex

@shortcontents
@contents

@ifnottex
@node Top
@top Perl pod documentation

This translation of the Perl documentation
(@url{http://perldoc.perl.org}) from POD to Texinfo is not official,
and not endorsed by the Perl developers (indeed, they haven't seen
it).  It was created by the GNU Texinfo developers because they found
it useful to have the Perl documentation available in Info and other
formats, and thought they would share the results.  Suggestions welcome.

This is created entirely by the Texinfo tools; see the
@url{http://svn.savannah.gnu.org/viewvc/trunk/contrib/perldoc-all/?root=texinfo,
@file{contrib/perldoc-all}} directory in the Texinfo sources for the
procedure used.  The output is available at
@url{http://www.gnu.org/software/perl/manual}.
@end ifnottex

@include perldoc-all-incl.texi

@bye


code:
#! /usr/local/bin/gawk -f

# prepinfo.awk --- fix node lines and menus
#
# Copyright 1998 Arnold Robbins
#
# PREPINFO is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# PREPINFO is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

BEGIN	\
{
	# manifest constants
	TRUE = 1
	FALSE = 0

	# Levels at which different nodes can be
	Level["@top"] =	0
	Level["@appendix"] = 1
	Level["@chapter"] = 1
	Level["@majorheading"] = 1
	Level["@unnumbered"] = 1
	Level["@appendixsec"] = 2
	Level["@heading"] = 2
	Level["@section"] = 2
	Level["@unnumberedsec"] = 2
	Level["@unnumberedsubsec"] = 3
	Level["@appendixsubsec"] = 3
	Level["@subheading"] = 3
	Level["@subsection"] = 3
	Level["@appendixsubsubsec"] = 4
	Level["@subsubheading"] = 4
	Level["@subsubsection"] = 4
	Level["@unnumberedsubsubsec"] = 4

	# Length of menus
	Menumargin = 78

	# Length of menu item
	Min_menitem_length = 29

	# insure that we were called correctly
	if (ARGC != 2) {
		printf("usage: %s texinfo-file\n", ARGV[0]) > "/dev/stderr"
		exit 1
	}

	# Arrange for two passes over input file
	Pass = 1
	ARGV[2] = "Pass=2"
	ARGV[3] = ARGV[1]
	ARGC = 4
	Lastlevel = -1

	# Initialize stacks
	Up[-1] = "(dir)"
	Prev[0] = "(dir)"

	if (Debug == "args") {
		for (i = 0; i < ARGC; i++)
			printf("ARGV[%d] = %s\n", i, ARGV[i]) > "/dev/stderr"
	}
}

$1 == "@node"	\
{
	Name = getnodename($0)
	Nodeseen = TRUE

	if ((l = length(Name)) > Maxlen)
		Maxlen = l

	if (Debug == "nodenames")
		printf("Name = %s\n", Name) > "/dev/stderr"

	if (Pass == 1)
		next
}

Pass == 1 && /^@c(omment)?[ \t]+fakenode/ \
{
	if (Debug == "fakenodes")
		printf("fakenode at %d\n", FNR) > "/dev/stderr"
	Fakenode = TRUE
	next
}

Pass == 1 && ($1 in Level)	\
{
	# skip fake nodes --- titles without associated @node lines
	if (Fakenode) {
		if (Debug == "fakenodes")
			printf("%s at %d is a fakenode\n", $1, FNR) > "/dev/stderr"
		Fakenode = FALSE
		next
	}

	if (Debug == "titles")
		printf("Processing %s: Name = %s\n", $1, Name) > "/dev/stderr"

	# save type
	type = $1

	if (! Nodeseen) {
		err_prefix()
		printf("%s line with no @node or fakenode line\n",
			type) > "/dev/stderr"
		Badheading[FNR] = 1
		# ??? used ???
		next
	} else
		Nodeseen = FALSE	# reset it

	# Squirrel away the info
	levelnum = Level[type]
	Node[Name ".level"] = levelnum
	Node[Name ".name"] = Name
	if (Debug == "titles") {
		printf("Node[%s\".level\"] = %s\n", Name, Node[Name ".level"]) > "/dev/stderr"
		printf("Node[%s\".name\"] = %s\n", Name, Node[Name ".name"]) > "/dev/stderr"
	}

	if (levelnum == Lastlevel) {	# e.g., two sections in a row
		Node[Name ".up"] = Up[levelnum - 1]
		if (levelnum in Prev) {
			Node[Prev[levelnum] ".next"] = Name
			Node[Name ".prev"] = Prev[levelnum]
		}
		Prev[levelnum] = Name
		Up[levelnum] = Name	# ???
	} else if (levelnum < Lastlevel) {	# section, now chapter
		Lastlevel = levelnum
		Node[Name ".up"] = Up[levelnum - 1]
		if (levelnum in Prev) {
			Node[Name ".prev"] = Prev[levelnum]
			Node[Prev[levelnum] ".next"] = Name
		}
		Prev[levelnum] = Name
		Up[levelnum] = Name
	} else {		# chapter, now section, levelnum > Lastlevel
		Node[Name ".up"] = Up[levelnum - 1]
		Node[Up[Lastlevel] ".child"] = Name
		Up[levelnum] = Name
		Prev[levelnum] = Name
		Lastlevel = levelnum
	}

	# For master menu
	if (Level[$1] >= 2)
		List[++Sequence] = Name

	if (Debug == "titles") {
		printf("Node[%s\".prev\"] = %s\n", Name, Node[Name ".prev"]) > "/dev/stderr"
		printf("Node[%s\".up\"] = %s\n", Name, Node[Name ".up"]) > "/dev/stderr"
		printf("Node[%s\".child\"] = %s\n", Name, Node[Name ".child"]) > "/dev/stderr"
	}
}

Pass == 2 && Debug == "dumptitles" && FNR <= 1	\
{
	for (i in Node)
		printf("Node[%s] = %s\n", i, Node[i]) | "sort 1>&2"
	close("sort 1>&2")
}

/^@menu/ && Pass == 1, /^@end[ \t]+menu/ && Pass == 1	\
{
	if (/^@menu/ || /^@end[ \t]+menu/)
		next

#	if (Debug == "menu")
#		printf("processing: %s\n", $0) > "/dev/stderr"

	if (/^\*/) {
		if (In_menitem) {	# file away info from previousline
			Node[node ".mendesc"] = desc
			Node[node ".longdesc"] = longdesc
			if (Debug == "mendesc") {
				printf("Node[%s.mendesc] = %s\n",
					node, Node[node ".mendesc"]) > "/dev/stderr"
				printf("Node[%s.longdesc] = %s\n",
					node, Node[node ".longdesc"]) > "/dev/stderr"
			}
		}
		In_menitem = TRUE

		# pull apart menu item
		$1 = ""		# nuke ``*''
		$0 = $0		# reparse line
		i1 = index($0, ":")
		if (i1 <= 0) {
			err_prefix()
			printf("badly formed menu item") > "/dev/stderr"
			next
		}
		if (substr($0, i1+1, 1) != ":") { # desc: node.  long desc
			i2 = index($0, ".")
			if (i2 <= 0) {
				err_prefix()
				printf("badly formed menu item") > "/dev/stderr"
				next
			}
			desc = substr($0, 1, i1 - 1)
			sub(/^[ \t]+/, "", node)
			sub(/[ \t]+$/, "", node)
			longdesc = substr($0, i2 + 1)
		} else {	# nodname:: long desc
			desc = ""
			node = substr($0, 1, i1 - 1)
			sub(/^[ \t]+/, "", node)
			sub(/[ \t]+$/, "", node)
			longdesc = substr($0, i1 + 2)
		}
	} else if (In_menitem) {	# continuation line
		longdesc = longdesc " " $0
	} else
		In_menitem = FALSE

	Node[node ".mendesc"] = desc
	Node[node ".longdesc"] = longdesc
	if (Debug == "mendesc") {
		printf("Node[%s.mendesc] = %s\n",
			node, Node[node ".mendesc"]) > "/dev/stderr"
		printf("Node[%s.longdesc] = %s\n",
			node, Node[node ".longdesc"]) > "/dev/stderr"
	}

	if (Debug == "menu")
		printf("Menu:: Name %s: desc %s: longdesc %s\n",
			node, desc, longdesc) > "/dev/stderr"
}

function err_prefix()
{
	printf("%s: %s: %d: ", ARGV[0], FILENAME, FNR) > "/dev/stderr"
}

function getnodename(str)
{
	sub(/@node[ \t]+/, "", str)
	sub(/,.*/, "", str)
	if (Debug == "nodenames")
		printf("getnodename: return %s\n", str) > "/dev/stderr"
	return str
}

Pass == 2 && /^@node/	\
{
	Name = getnodename($0)

	# Top node is special. It's next is the first child
	n = Node[Name ".next"]
	if (Node[Name ".level"] == 0 && n == "")
		n = Node[Name ".child"]

	printf("@node %s, %s, %s, %s\n", Name, n,
		Node[Name ".prev"] ? Node[Name ".prev"] : Node[Name ".up"],
		Node[Name ".up"])
	next
}

Pass == 2 && /^@menu/	\
{
	# First, nuke current contents of menu
	do {
		if ((getline) <= 0) {
			err_prefix()
			printf("unexpected EOF inside menu\n") > "/dev/stderr"
			exit 1
		}
	} while (! /^@end[ \t]+menu/)

	# next, compute maximum length of a node name
	max = 0
	for (n = Node[Name ".child"]; (n ".next") in Node; n = Node[n ".next"]) {
		if ((n ".desc") in Node)
			s = Node[n ".desc"] ": " n "."
		else
			s = n "::"
		l = length(s)
		if (l > max)
			max = l
	}
	if (max < Min_menitem_length)
		max = Min_menitem_length

	# now dump the menu
	print "@menu"

	for (n = Node[Name ".child"]; (n ".next") in Node; n = Node[n ".next"]) {
		print_menuitem(n, max)
	}
	print_menuitem(n, max)

	if (Name == "Top") {	# Master Menu
		if (Maxlen < Min_menitem_length)
			Maxlen = Min_menitem_length
		print ""
		for (i = 1; i <= Sequence; i++)
			print_menuitem(List[i], Maxlen)
		print ""
	}
	print "@end menu"
	next
}

Pass == 2	# print

function print_menuitem(n, max,		nodesc, i, dwords, count, p)
{
	nodesc = FALSE
	if (! ((n ".longdesc") in Node)) {
		err_prefix()
		printf("warning: %s: no long description\n", n) > "/dev/stderr"
		nodesc = TRUE
	} else {
		for (i in dwords)
			delete dwords[i]
		count = split(Node[n ".longdesc"], dwords, "[ \t\n]+")
	}
	if ((n ".desc") in Node)
		s = Node[n ".desc"] ": " n "."
	else
		s = n "::"
	printf("* %-*s", max, s)

	if (Debug == "mendescitem")
		printf("<* %-*s>\n", max, s) > "/dev/stderr"

	p = max + 2
	if (! nodesc) {
		for (i = 1; i <= count; i++) {
			l = length(dwords[i])
			if (l == 0)
				continue
			if (p + l + 1 > Menumargin) {
				printf("\n%*s", max + 2, " ")
				p = max + 2
			}
			printf(" %s", dwords[i])
			p += l + 1
		}
	}
	print ""
}


code:
#!/bin/sh
# Auxiliary script to work around TeX 3.0 bug.      ---- tex3patch  ----
# patches texinfo.tex in current directory, or in directory given as arg.

ANYVERSION=no

for arg in $1 $2
do
	case $arg in
		--dammit | -d ) ANYVERSION=yes ;;

		* ) dir=$arg
	esac
done

if [ -z "$dir" ]; then
  dir='.'
fi

if [ 2 -lt $# ] || [ ! -f "$dir/texinfo.tex" ]; then
  echo "To patch texinfo.tex for peaceful coexistence with Unix TeX 3.0,"
  echo "run    $0"
  echo "with no arguments in the same directory as texinfo.tex; or run"
  echo "       $0 DIRECTORY"
  echo "(where DIRECTORY is a path leading to texinfo.tex)."
  exit
fi

if [ -z "$TMPDIR" ]; then
  TMPDIR=/tmp
fi

echo "Checking for \`dummy.tfm'"

( cd $TMPDIR; tex '\relax \batchmode \font\foo=dummy \bye' )

grep -s '3.0' $TMPDIR/texput.log
if [ 1 = "$?" ] && [ "$ANYVERSION" != "yes" ]; then
	echo "You probably do not need this patch,"
        echo "since your TeX does not seem to be version 3.0."
	echo "If you insist on applying the patch, run $0"
	echo "again with the option \`--dammit'"
	exit
fi

grep -s 'file not found' $TMPDIR/texput.log
if [ 0 = $? ]; then
	echo "This patch requires the dummy font metric file \`dummy.tfm',"
	echo "which does not seem to be part of your TeX installation."
	echo "Please get your TeX maintainer to install \`dummy.tfm',"
	echo "then run this script again."
	exit
fi
rm $TMPDIR/texput.log

echo "Patching $dir/texinfo.tex"

sed -e 's/%%*\\font\\nullfont/\\font\\nullfont/' \
    $dir/texinfo.tex >$TMPDIR/texinfo.tex
mv $dir/texinfo.tex $dir/texinfo.tex-distrib; mv $TMPDIR/texinfo.tex $dir

if [ 0 = $? ]; then
	echo "Patched $dir/texinfo.tex to avoid TeX 3.0 bug."
	echo "The original version is saved as $dir/texinfo.tex-distrib."
else
	echo "Patch failed.  Sorry."
fi
----------------------------------------tex3patch ends


code:
;; texi-docstring-magic.el -- munge internal docstrings into texi
;;
;; Keywords: lisp, docs, tex
;; Author: David Aspinall <da@dcs.ed.ac.uk>
;; Copyright (C) 1998 David Aspinall
;; Maintainer:  David Aspinall <da@dcs.ed.ac.uk>
;;
;; This package is distributed under the terms of the
;; GNU General Public License, Version 3.
;; You should have a copy of the GPL with your version of
;; GNU Emacs or the Texinfo distribution.
;;
;;
;; This package generates Texinfo source fragments from Emacs
;; docstrings.   This avoids documenting functions and variables
;; in more than one place, and automatically adds Texinfo markup
;; to docstrings.
;;
;; It relies heavily on you following the Elisp documentation
;; conventions to produce sensible output, check the Elisp manual
;; for details.  In brief:
;;
;;  * The first line of a docstring should be a complete sentence.
;;  * Arguments to functions should be written in upper case: ARG1..ARGN
;;  * User options (variables users may want to set) should have docstrings
;;    beginning with an asterisk.
;;
;; Usage:
;;
;;  Write comments of the form:
;;
;;    @c TEXI DOCSTRING MAGIC: my-package-function-or-variable-name
;;
;;  In your texi source, mypackage.texi.  From within an Emacs session
;;  where my-package is loaded, visit mypackage.texi and run
;;  M-x texi-docstring-magic to update all of the documentation strings.
;;
;;  This will insert @defopt, @deffn and the like underneath the
;;  magic comment strings.
;;
;;  The default value for user options will be printed.
;;
;;  Symbols are recognized if they are defined for faces, functions,
;;  or variables (in that order).
;;
;; Automatic markup rules:
;;
;; 1. Indented lines are gathered into @lisp environment.
;; 2. Pieces of text `stuff' or surrounded in quotes marked up with @samp.
;; 3. Words *emphasized* are made @strong{emphasized}
;; 4. Words sym-bol which are symbols become @code{sym-bol}.
;; 5. Upper cased words ARG corresponding to arguments become @var{arg}.
;;    In fact, you can any word longer than three letters, so that
;;    metavariables can be used easily.
;;    FIXME: to escape this, use `ARG'
;; 6. Words 'sym which are lisp-quoted are marked with @code{'sym}.
;;
;; -----
;;
;; Useful key binding when writing Texinfo:
;;
;;  (define-key TeXinfo-mode-map "C-cC-d" 'texi-docstring-magic-insert-magic)
;;
;; -----
;;
;; Useful enhancements to do:
;;
;;  * Use customize properties (e.g. group, simple types)
;;  * Look for a "texi-docstring" property for symbols
;;    so TeXInfo can be defined directly in case automatic markup
;;    goes badly wrong.
;;  * Add tags to special comments so that user can specify face,
;;    function, or variable binding for a symbol in case more than
;;    one binding exists.
;;
;; ------

(defun texi-docstring-magic-splice-sep (strings sep)
  "Return concatenation of STRINGS spliced together with separator SEP."
  (let (str)
    (while strings
      (setq str (concat str (car strings)))
      (if (cdr strings)
	  (setq str (concat str sep)))
      (setq strings (cdr strings)))
    str))

(defconst texi-docstring-magic-munge-table
  '(;; 1. Indented lines are gathered into @lisp environment.
    ("\\(^.*\\S-.*$\\)"
     t
     (let
	 ((line (match-string 0 docstring)))
       (if (eq (char-syntax (string-to-char line)) ?\ )
	   ;; whitespace
	   (if in-quoted-region
	       line
	     (setq in-quoted-region t)
	     (concat "@lisp\n" line))
	 ;; non-white space
	 (if in-quoted-region
	     (progn
	       (setq in-quoted-region nil)
	       (concat "@end lisp\n" line))
	   line))))
    ;; 2. Pieces of text `stuff' or surrounded in quotes
    ;; are marked up with @samp.  NB: Must be backquote
    ;; followed by forward quote for this to work.
    ;; Can't use two forward quotes else problems with
    ;; symbols.
    ;; Odd hack: because ' is a word constituent in text/texinfo
    ;; mode, putting this first enables the recognition of args
    ;; and symbols put inside quotes.
    ("\\(`\\([^']+\\)'\\)"
     t
     (concat "@samp{" (match-string 2 docstring) "}"))
    ;; 3. Words *emphasized* are made @strong{emphasized}
    ("\\(\\*\\(\\w+\\)\\*\\)"
     t
     (concat "@strong{" (match-string 2 docstring) "}"))
    ;; 4. Words sym-bol which are symbols become @code{sym-bol}.
    ;; Must have at least one hyphen to be recognized,
    ;; terminated in whitespace, end of line, or punctuation.
    ;; (Only consider symbols made from word constituents
    ;; and hyphen.
    ("\\(\\(\\w+\\-\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)"
     (or (boundp (intern (match-string 2 docstring)))
	 (fboundp (intern (match-string 2 docstring))))
     (concat "@code{" (match-string 2 docstring) "}"
	     (match-string 4 docstring)))
    ;; 5. Upper cased words ARG corresponding to arguments become
    ;; @var{arg}
    ;; In fact, include any word so long as it is more than 3 characters
    ;; long.  (Comes after symbols to avoid recognizing the
    ;; lowercased form of an argument as a symbol)
    ;; FIXME: maybe we don't want to downcase stuff already
    ;; inside @samp
    ;; FIXME: should - terminate?  should _ be included?
    ("\\([A-Z0-9\\-]+\\)\\(/\\|\)\\|}\\|\\s-\\|\\s.\\|$\\)"
     (or (> (length (match-string 1 docstring)) 3)
	 (member (downcase (match-string 1 docstring)) args))
     (concat "@var{" (downcase (match-string 1 docstring)) "}"
	     (match-string 2 docstring)))

    ;; 6. Words 'sym which are lisp quoted are
    ;; marked with @code.
    ("\\(\\(\\s-\\|^\\)'\\(\\(\\w\\|\\-\\)+\\)\\)\\(\\s\)\\|\\s-\\|\\s.\\|$\\)"
     t
     (concat (match-string 2 docstring)
	     "@code{'" (match-string 3 docstring) "}"
	     (match-string 5 docstring)))
    ;; 7,8. Clean up for @lisp environments left with spurious newlines
    ;; after 1.
    ("\\(\\(^\\s-*$\\)\n@lisp\\)" t "@lisp")
    ("\\(\\(^\\s-*$\\)\n@end lisp\\)" t "@end lisp"))
    "Table of regexp matches and replacements used to markup docstrings.
Format of table is a list of elements of the form
   (regexp predicate replacement-form)
If regexp matches and predicate holds, then replacement-form is
evaluated to get the replacement for the match.
predicate and replacement-form can use variables arg,
and forms such as (match-string 1 docstring)
Match string 1 is assumed to determine the
length of the matched item, hence where parsing restarts from.
The replacement must cover the whole match (match string 0),
including any whitespace included to delimit matches.")

(defun texi-docstring-magic-munge-docstring (docstring args)
  "Markup DOCSTRING for texi according to regexp matches."
  (let ((case-fold-search nil))
    (dolist (test texi-docstring-magic-munge-table docstring)
      (let ((regexp	(nth 0 test))
	    (predicate  (nth 1 test))
	    (replace    (nth 2 test))
	    (i		0)
	    in-quoted-region)

	(while (and
		(< i (length docstring))
		(string-match regexp docstring i))
	  (setq i (match-end 1))
	  (if (eval predicate)
	      (let* ((origlength  (- (match-end 0) (match-beginning 0)))
		     (replacement (eval replace))
		     (newlength   (length replacement)))
		(setq docstring
		      (replace-match replacement t t docstring))
		(setq i (+ i (- newlength origlength))))))
	(if in-quoted-region
	    (setq docstring (concat docstring "\n@end lisp"))))))
  ;; Force a new line after (what should be) the first sentence,
  ;; if not already a new paragraph.
  (let*
      ((pos      (string-match "\n" docstring))
       (needscr  (and pos
		      (not (string= "\n"
				    (substring docstring
					       (1+ pos)
					       (+ pos 2)))))))
    (if (and pos needscr)
	(concat (substring docstring 0 pos)
		"@*\n"
		(substring docstring (1+ pos)))
      docstring)))

(defun texi-docstring-magic-texi (env grp name docstring args &optional endtext)
  "Make a texi def environment ENV for entity NAME with DOCSTRING."
  (concat "@def" env (if grp (concat " " grp) "") " " name
	  " "
	  (texi-docstring-magic-splice-sep args " ")
	  ;; " "
	  ;; (texi-docstring-magic-splice-sep extras " ")
	  "\n"
	  (texi-docstring-magic-munge-docstring docstring args)
	  "\n"
	  (or endtext "")
	  "@end def" env "\n"))

(defun texi-docstring-magic-format-default (default)
  "Make a default value string for the value DEFAULT.
Markup as @code{stuff} or @lisp stuff @end lisp."
  (let ((text       (format "%S" default)))
    (concat
     "\nThe default value is "
     (if (string-match "\n" text)
	 ;; Carriage return will break @code, use @lisp
	 (if (stringp default)
	     (concat "the string: \n@lisp\n" default "\n@end lisp\n")
	   (concat "the value: \n@lisp\n" text "\n@end lisp\n"))
       (concat "@code{" text "}.\n")))))

(defun texi-docstring-magic-texi-for (symbol)
  (cond
   ;; Faces
   ((find-face symbol)
    (let*
	((face	    symbol)
	 (name      (symbol-name face))
	 (docstring (or (face-doc-string face)
			"Not documented."))
	 (useropt   (eq ?* (string-to-char docstring))))
      ;; Chop off user option setting
      (if useropt
	  (setq docstring (substring docstring 1)))
      (texi-docstring-magic-texi "fn" "Face" name docstring nil)))
   ((fboundp symbol)
    ;; Functions.
    ;; We don't handle macros,  aliases, or compiled fns properly.
    (let*
	((function  symbol)
	 (name	    (symbol-name function))
	 (docstring (or (documentation function)
			"Not documented."))
	 (def	    (symbol-function function))
	 (argsyms   (cond ((eq (car-safe def) 'lambda)
			   (nth 1 def))))
	 (args	    (mapcar 'symbol-name argsyms)))
      (if (commandp function)
	  (texi-docstring-magic-texi "fn" "Command" name docstring args)
	(texi-docstring-magic-texi "un" nil name docstring args))))
   ((boundp symbol)
    ;; Variables.
    (let*
	((variable  symbol)
	 (name      (symbol-name variable))
	 (docstring (or (documentation-property variable
						'variable-documentation)
			"Not documented."))
	 (useropt   (eq ?* (string-to-char docstring)))
	 (default   (if useropt
			(texi-docstring-magic-format-default
			 (default-value symbol)))))
      ;; Chop off user option setting
      (if useropt
	  (setq docstring (substring docstring 1)))
      (texi-docstring-magic-texi
       (if useropt "opt" "var") nil name docstring nil default)))
   (t
    (error "Don't know anything about symbol %s" (symbol-name symbol)))))

(defconst texi-docstring-magic-comment
  "@c TEXI DOCSTRING MAGIC:"
  "Magic string in a texi buffer expanded into @defopt, or @deffn.")

(defun texi-docstring-magic ()
  "Update all texi docstring magic annotations in buffer."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((magic (concat "^"
			 (regexp-quote texi-docstring-magic-comment)
			 "\\s-*\\(\\(\\w\\|\\-\\)+\\)$"))
	  p
	  symbol)
      (while (re-search-forward magic nil t)
	(setq symbol (intern (match-string 1)))
	(forward-line)
	(setq p (point))
	;; If comment already followed by an environment, delete it.
	(if (and
	     (looking-at "@def\\(\\w+\\)\\s-")
	     (search-forward (concat "@end def" (match-string 1)) nil t))
	    (progn
	      (forward-line)
	      (delete-region p (point))))
	(insert
	 (texi-docstring-magic-texi-for symbol))))))

(defun texi-docstring-magic-face-at-point ()
  (ignore-errors
    (let ((stab (syntax-table)))
      (unwind-protect
	  (save-excursion
	    (set-syntax-table emacs-lisp-mode-syntax-table)
	    (or (not (zerop (skip-syntax-backward "_w")))
		(eq (char-syntax (char-after (point))) ?w)
		(eq (char-syntax (char-after (point))) ?_)
		(forward-sexp -1))
	    (skip-chars-forward "'")
	    (let ((obj (read (current-buffer))))
	      (and (symbolp obj) (find-face obj) obj)))
	(set-syntax-table stab)))))

(defun texi-docstring-magic-insert-magic (symbol)
  (interactive
   (let* ((v (or (variable-at-point)
		 (function-at-point)
		 (texi-docstring-magic-face-at-point)))
	  (val (let ((enable-recursive-minibuffers t))
                 (completing-read
		  (if v
		      (format "Magic docstring for symbol (default %s): " v)
		     "Magic docstring for symbol: ")
                   obarray '(lambda (sym)
			      (or (boundp sym)
				  (fboundp sym)
				  (find-face sym)))
		   t nil 'variable-history))))
     (list (if (equal val "") v (intern val)))))
  (insert "\n" texi-docstring-magic-comment " " (symbol-name symbol)))

(provide 'texi-docstring-magic)


code:
$Id$
texinfo/contrib/texifont/README

Everything in this directory is public domain.

The main source file is fsel.tex, tested by ftest.tex, documentation
(such as it is) in texifont.txi.

The story here is that Oleg Katsitadze and I (Karl Berry) collaborated
in the mid-2000s on a new font system for Texinfo (and that could work
with Eplain).  Oleg wrote essentially all the code, while I advised on
the design, etc.

The idea was to support all the widely-available free font families and
widely used input encodings.  getting away from of the current
hardwiring of Computer Modern in texinfo.tex.  The example at the end of
documentation gives the basic flavor.  As it stands, it is not hooked
into texinfo.tex or Texinfo documents at all.  That is not the hard part :).

Unfortunately, it never came to fruition.  Even more unfortunately, Oleg
and I can't even give any real specifics of what the "next thing" to do
is, we've lost the details in the haze of time.  As I recall, the
current problem was something about caching of glyphs to avoid
unnecessary (and greatly excessive) switching of fonts.  In addition,
"attributes" (fattr.tex) were starting to come into play.  But it all
remains a work in progress.

Anyway, I'm not sanguine about anyone ever doing anything with this, but
just in case some super-duper TeX hacker has too much time on their
hands :) and wants to pick it up, here it is.  Contact
bug-texinfo@gnu.org ...

path: contrib/texifont/README


code:
\catcode`\@=11
\input ienc
\input oenc
\input fsel
\catcode`\@=12

\def\changedocenc#1 {%
  \message{^^J** #1}%
  \documentencoding #1
}

\hfuzz=\maxdimen
%\tracingfonts2

\setfontencoding{T1}\setfontfamily{LMRoman}\selectfont
\changedocenc ISO-8859-1
\input sample.latin1 % Icelandic

\bigskip
\changedocenc ISO-8859-15
\input sample.latin9 % French

\bigskip
\changedocenc ISO-8859-2
\input sample.latin2 % Czech

\bigskip
\changedocenc ISO-8859-2
\setfontencoding{OT1}\setfontfamily{CMRoman}\selectfont
\input sample.latin2 % Czech

\bigskip
\setfontencoding{T2A}\setfontfamily{LHRoman}\selectfont
\changedocenc KOI8-R
\input sample.koi8r % Russian

\bigskip
\changedocenc CP1251
\input sample.cp1251 % Russian

\bye

% Local variables:
% compile-command: "tex --interact=nonstopmode enctest.tex"
% End:


code:
% font set              Computer Modern, Latin Modern, Bera
%   font family         CMRoman, CMTypewriter, CMSansSerif
%
% font feature          slant, weight, width
%   font attribute      sl, it, bold, cond
%
% fontfile
%
% Font features and attributes:
%
%     size              design point size
%     encoding          ot1, oml, oms, omx, t1, ts1, t2a
%     slant             up, sl, it, ui, cursive
%     weight            lt, med, semib, bold, bx,
%     width             cond, normal, ext
%     figures           oldfigures, liningfigures
%     caps              normalcaps, allcaps, smallcaps, nocaps

\catcode`@=11
%
% \fsz:CMRoman/<OT1>/<up>/<med>/<normal>/<liningfigures>/<normalcaps>
% ->
% cmr5/5,cmr7/7,cmr8/8,cmr9/9,cmr10/10,cmr12/12,cmr17/17,.
%
% {CMRoman}{b}{bx}
% {OML,CMRoman}{m}{up}
% {OML,CMRoman}{bx}{up}
% {OML,CMRoman}{b}{bx}
% {CMSans}{it}{sl}
% {CMSans}{b}{bx}
% {CMSans,OML}{}{CMRoman}
% {ConcreteRoman}{b}{LMSans,b}
% {OT1,CMMath}{}{CMRoman,n}
% {OMX,CMMath}{bx}{m}
%

\input eplain

%
%
%
% Font-related logging.  Meanings of \ftracelevel values are:
%
%   0 - none
%   1 - warning
%   2 - debug
%   3 - verbose
\newcount\ftracelevel
\ftracelevel=1
%
\def\f@warning{\f@trace0}%
\def\f@debug  {\f@trace1}%
\def\f@verbose{\f@trace2}%
\def\f@trace#1{%
  \ifnum#1<\ftracelevel
    \expandafter\message
  \else
    \expandafter\gobble
  \fi
}%
%
%
%
% Defining new fonts.
%
% \newfont MAG-FACTOR FILE-NAME DESIGN-SIZE [ATTRIBUTES]
%
% If ATTRIBUTES is not given, will reuse ATTRIBUTES from the
% last \newfont call with ATTRIBUTES given.  To define a font with
% empty attributes, pass \empty or {} for ATTRIBUTES.
\let\newfont@last@attributes\empty
%
{\catcode`\^^M=12 % Comment out all line ends from now on.
\gdef\newfont{\begingroup \catcode`\^^M=12 \@newfont}%
\gdef\@newfont#1 #2 #3^^M{\endgroup%
  \def\newfont@mag{#1}%
  \def\newfont@fname{#2}%
  \@new@font#3 ^^M%
}%
\gdef\@new@font #1 #2^^M{% DESIGN-SIZE [ATTRIBUTES]
  \get@dimen{#1}% Convert font size into a dimen.
  \if^^M#2^^M% Assume #2 contains no (leading) ^^Ms.
    % #2 is empty, continue to use the current \newfont@last@attributes.
  \else%
    \@new@font@#2^^M% Redefine \newfont@last@attributes, removing the space.
  \fi%
  \def@newfont%
}%
\gdef\@new@font@#1 ^^M{\def\newfont@last@attributes{#1}}%
}%
%
%\def\def@newfont{%
%  \message{^^J.\newfont@mag.\newfont@fname.\the\dimen@.\newfont@last@attributes.}%
%}%
%
\def\def@newfont{%
  % Empty \ff@temp0, \ff@temp1, ..., \ff@temp<MAX-ITEM-IDX>.
  \ff@temp@reset
  \for\ff@temp@i:=\newfont@last@attributes\do{%
    \f@get@af\ff@temp@i
    \expandafter\edef\csname ff@temp\tempb\endcsname{\tempa\space}%
  }%
  % Combine all \f@temp<X> into \f@name.
  \edef\f@name{f:\ff@temp@collect}%
  \expandafter\let\expandafter\temp \csname \f@name \endcsname
  %
  \ifx\temp\relax
    \let\do\relax
    \expandafter\xdef\csname\f@name\endcsname
      {\do \the\dimen@ \space \newfont@mag\space {\newfont@fname}}%
    \f@debug{^^J\the\inputlineno: Started font def with
             "\csname \f@name\endcsname".}%
  \else
    \edef\newfont@size{\the\dimen@}%
    \divide\dimen@ by1000
    \multiply\dimen@ by\newfont@mag\relax
    \let\newfont@prefix\empty
    \let\do\newfont@insert
    % Try inserting the new size.
    \temp\relax
    % If the new size hasn't been inserted, append it now.
    \ifx\do\newfont@insert
      \let\do\relax
      \expandafter\xdef\csname\f@name\endcsname {%
        \newfont@prefix \do \newfont@size\space \newfont@mag\space {\newfont@fname}%
      }%
      \f@debug{^^J\the\inputlineno: Updated font def to
               "\csname \f@name\endcsname".}%
    \fi
  \fi
}%
%
% \newfont@insert FONT-SIZE MAG-FACTOR FILE-NAME
%
% Insert new font file into the list of font files in \newfont@prefix,
% preserving ascending order of scaled design sizes.
%
% Pass in new font's design size in \newfont@size, mag factor
% in \newfont@mag, font file name in \newfont@fname, pre-computed
% scaled size in \dimen@.
\def\newfont@insert#1 #2 #3{%
  \toks@=\expandafter{\newfont@prefix}%
  % We are comparing scaled font sizes, not design sizes.
  \dimen@ii=#1\divide\dimen@ii by1000 \multiply\dimen@ii by#2\relax
  %
  \ifdim \dimen@ii < \dimen@
    % Not inserting yet, go on to the next font file.
    \edef\newfont@prefix{\the\toks@ \noexpand\do #1 #2 {#3}}%
  \else
    % Time to insert the new font file.  If we are adding a font file
    % for the same design size/mag factor, the new def will override
    % the old, but give out a warning.
    \def\newfont@next{\noexpand\do #1 #2 {#3}}%
    \ifnum #2=\newfont@mag
      \ifdim #1=\newfont@size
        \f@warning{^^J\the\inputlineno: Warning: Replacing font "#3" (#1@#2)
          with font "\newfont@fname" (\newfont@size @\newfont@mag).}%
        \let\newfont@next\empty
      \fi
    \fi
    \edef\newfont@prefix{\the\toks@
      \noexpand\do \newfont@size \space \newfont@mag \space {\newfont@fname}%
      \newfont@next}%
    % No need to parse further, just append the rest of the list.
    \expandafter\newfont@append
  \fi
}%
%
\def\newfont@append#1\relax{%
  \let\do\relax
  \expandafter\xdef\csname\f@name\endcsname {\newfont@prefix #1}%
  \f@debug{^^J\the\inputlineno: Updated font def to
           "\csname \f@name\endcsname".}%
}%
%
%
%
% Defining new font features and attributes.
%
\newcount\fcacheidx % Font cache index.
\newcount\ffeatcount % Font feature count.
% We'll build up the following as we add font features.
\let\ff@reset\empty
\let\ff@collect\empty
\let\ff@temp@reset\empty
\let\ff@temp@collect\empty
%
% \newfontattr FONTFEATURE FONTATTR
\def\newfontattr #1 #2 {%
  % Define a new font attribute, if it's not defined yet.
  \expandafter\ifx\csname fa:#2\endcsname \relax
  \else
    \errmessage{Font attribute "#2" already defined as part
      of font feature "\csname ff@\csname faf:#2\endcsname\endcsname"}%
  \fi
  \expandafter\xdef\csname fa:#2\endcsname{\the\fcacheidx}%
  \expandafter\xdef\csname fa@\the\fcacheidx\endcsname{#2}%
  % Invalidate current font cache (and update index for the next font
  % attribute).
  \global\advance\fcacheidx by1
  %
  % Define a new font feature, if it's not defined yet.
  \expandafter\ifx\csname ff:#1\endcsname \relax
    \expandafter\xdef\csname ff:#1\endcsname{\the\ffeatcount}%
    \expandafter\xdef\csname ff@\the\ffeatcount\endcsname{#1}%
    % Update \ff@reset to clear the new font feature cell \ffN.
    \toks@=\expandafter{\ff@reset}%
    \xdef\ff@reset{\the\toks@
      \let\expandafter\noexpand\csname ff\the\ffeatcount\endcsname
        \noexpand\empty}%
    % Update \ff@temp@reset to clear the new font feature cell \ff@tempN.
    \toks@=\expandafter{\ff@temp@reset}%
    \xdef\ff@temp@reset{\the\toks@
      \let\expandafter\noexpand\csname ff@temp\the\ffeatcount\endcsname
        \noexpand\empty}%
    % Update \ff@collect to include the new font feature cell \ffN.
    \toks@=\expandafter{\ff@collect}%
    \xdef\ff@collect{\the\toks@
      ^^A\expandafter\noexpand\csname ff\the\ffeatcount\endcsname}%
    % Update \ff@temp@collect to include the new font feature cell \ffN.
    \toks@=\expandafter{\ff@temp@collect}%
    \xdef\ff@temp@collect{\the\toks@
      ^^A\expandafter\noexpand\csname ff@temp\the\ffeatcount\endcsname}%
    % Set \ffN to \empty, otherwise it will be set to \relax the first
    % time we try to access it through \csname...\endcsname, and we
    % depend on it to be either a number or \empty.
    \global\expandafter\let\csname ff\the\ffeatcount\endcsname \empty
    %
    \global\advance\ffeatcount by1
    % We've added a new font feature, so we should invalidate current
    % font cache.  But we've already done so above when adding the new
    % font attribute.
    %\global\advance\fcacheidx by1
  \fi
  % Assign the font attribute to the font feature.
  \expandafter\xdef\csname faf:#2\endcsname{\csname ff:#1\endcsname}%
}%
%
% \newfontattrs FONTFEATURE FONTATTRIBUTE[,...]
\def\newfontattrs #1 #2 {%
  \for\temp:=#2\do{\newfontattr #1 {\temp} }%
}%
%
%
%
% Setting a font.
%
\newcount\f@base@mag \f@base@mag=1000
\newcount\f@count@
% Set a font based on the current values of \ff0, \ff1, ..., or
% execute #1 if such font is not defined.
\def\f@setfont#1{%
  \f@verbose{^^J\the\inputlineno: (Started search for a font.}%
  % Check the cache.
  \expandafter\let\expandafter\temp
    \csname f\the\fcacheidx:\the\f@cur@size:\ff@collect\endcsname
  \ifx\temp\relax
    % Not in the cache.
    \let\do\relax
    \expandafter\let\expandafter\temp \csname f:\ff@collect\endcsname
    \ifx\temp\relax
      \f@debug{^^JFont is not defined.}%
      #1% Execute the no-font action.
    \else
      \dimen@=\z@ % Fake previous size for the first font in the list.
      \let\f@fname\empty
      \let\do\f@search@size
      \global\dimen@i=\f@cur@size
      \temp\relax
      \ifnum\f@base@mag=\f@count@ \else
        \global\divide\dimen@i by\f@base@mag
        \global\multiply\dimen@i by\f@count@
      \fi
      \f@debug{Found font "\f@fname", mag factor \the\f@count@,
        scaled size \the\dimen@i.}%
      % Save it in the cache.
      \global \expandafter\font
        \csname f\the\fcacheidx:\the\f@cur@size:\ff@collect\endcsname
        \f@fname \space at\the\dimen@i
      % Set it.
      \csname f\the\fcacheidx:\the\f@cur@size:\ff@collect\endcsname
    \fi
  \else
    % Got a cache hit.
    \f@debug{Found font in the cache.}%
    \temp
  \fi
  \f@verbose{Ended search for a font.)}%
}%
%
% \f@search@size DESIGN-SIZE MAG-FACTOR FILE-NAME
%
% Pass in the desired font size in \dimen@i.  Font file name is saved
% in \f@fname, font's mag factor in \f@count@.
\def\f@search@size #1 #2 #3{%
  \f@verbose{Looking at font def "#3" (#1@#2).}%
  \dimen@ii=\dimen@ % Get the size of the previous font from the list.
  % Scale design size of the next font as per current base mag factor.
  \dimen@=#1\relax
  \ifnum#2=\f@base@mag \else
    \divide\dimen@ by\f@base@mag \multiply\dimen@ by#2\relax
  \fi
  % Calculate the "dividing" point size.
  \count@=\dimen@ % Don't clobber \dimen@.
  \advance\count@ by-\dimen@ii % Curr size - prev size.
  % Don't do the following line -- .3333*3pt < 1pt.
  %\advance\dimen@ii by.3333\count@ % 1/3 of the way from prev to curr.
  \divide\count@ by3
  \advance\dimen@ii by\count@ sp% 1/3 of the way from prev to curr.
  %
  \ifdim \dimen@i > \dimen@ii
    % The previous font's size is too small.  The current font might
    % or might not be the one we need, but we assume it is, in case
    % it's the last one in the list.
    \def\f@fname{#3}%
    \f@count@=#2\relax
  \else
    % The target size is close enough to the prev font's size, so we take that.
    \ifx\f@fname\empty
      % This is the case when we need a size smaller than the very
      % first font in the list.
      \def\f@fname{#3}%
      \f@count@=#2\relax
    \fi
    \expandafter\f@search@gobble
  \fi
}%
%
\def\f@search@gobble#1\relax{}%
%
%
%
% Font feature manipulations.
%
\newdimen\f@cur@size \f@cur@size=10pt
% \setfont{ATTRS}
\def\setfont{%
  % Empty \ff0, \ff1, ..., \ff<ffeature_count - 1>.
  \ff@reset
  \addfontattrs % Substitutes and sets the font.
}%
%
% \modfont{REM-FEATURES}{ADD-OR-MOD-ATTRS}
\def\modfont#1{%
  \unsetfontfeatures{#1}%
  \addfontattrs % Substitutes and sets the font.
}%
%
% \addfontattrs{ADD-OR-MOD-ATTRS}
\def\addfontattrs#1{%
  % For each feature with attribute in #1, set \ff<f> to `<a> ', where
  % <f> is the feature index and <a> is the attribute index.  For a
  % number or a dimen in #1, set \f@cur@size.  Dimen specs starting
  % with '.' (e.g., '.1pt') are not supported.
  \for\f@i:=#1\do{%
    \expandafter\gobble@to@finish % Gobbles 'pt' when \f@i is '10pt'.
      \ifnum9<1\f@i\relax
    \finish
        % A number or a dimen.
        \get@dimen{\f@i}%
        \f@cur@size=\dimen@
      \else
    \finish
        % Not a number.
        \f@get@af\f@i
        \expandafter\edef\csname ff\tempb\endcsname {\tempa\space}%
      \fi
  }%
  % Try to set the font; if it's not defined, do substitution and try
  % to set the resulting font.  Do font substitution inside a group so
  % that the current set of font attributes is not clobbered.
  \f@setfont{{\f@subst \f@setfont{\f@err@nofont}\expandafter}\the\font}%
}%
%
% Take a dimension or a number, and save it in \dimen@.  In case of a
% number, assume `pt' units.
\def\get@dimen#1{%
  \afterassignment\gobble@to@finish
  \dimen@#1pt \finish
}%
\def\gobble@to@finish#1\finish{}%
%
\def\f@err@nofont{%
  \dumpfontfeatures
  \errmessage{^^JFound no font def for the selected feature set}%
}%
%
% \remfontfeatures{REM-FEATURES}
\def\remfontfeatures#1{%
  \unsetfontfeatures{#1}%
  % Try to set the font; if it's not defined, do substitution and try
  % to set the resulting font.  Do font substitution inside a group so
  % that the current set of font attributes is not clobbered.
  \f@setfont{{\f@subst \f@setfont{\f@err@nofont}\expandafter}\the\font}%
}%
%
\def\unsetfontfeatures#1{%
  % Unset \ff<f> for each feature <f> in #1.
  \for\f@i:=#1\do{%
    \expandafter\let\expandafter\temp\csname ff:\f@i\endcsname
    \ifx\temp\relax
      \errmessage{Undefined font feature "\f@i"}%
    \fi
    \expandafter\let \csname ff\temp\endcsname \empty
  }%
}%
%
% Pretty-print the current settings of font features.
\def\dumpfontfeatures{%
  \message{^^J\the\inputlineno: font size \the\f@cur@size, features (}%
  \@dumpfontfeatures\message
  \message{).}%
}%
%
\def\@dumpfontfeatures#1{%
  \fori0\ffeatcount{%
    \edef\temp{\csname ff\the\count@\endcsname}%
    #1{%
      \csname ff@\the\count@\endcsname=%
      \ifx\temp\empty
        <unset>%
      \else
        \expandafter\dump@ff\temp
      \fi
    }%
  }%
}%
%
\def\dump@ff#1 {\csname fa@#1\endcsname}%
%
%
%
% Construction of font filter strings.
%
% \fontsubstpre =MATCH-ATTR -REM-FEATURES +ADD-OR-MOD-ATTRS
%
% Install a new font substitution to be applied before other font
% substitutions.
\def\fontsubstpre{%
  \let\fontsubst@mklist\fontsubst@pre
  \@fontsubst
}%
%
% \fontsubstpost =MATCH-ATTR -REM-FEATURES +ADD-OR-MOD-ATTRS
%
% Install a new font substitution to be applied after other font
% substitutions.
\def\fontsubstpost{%
  \let\fontsubst@mklist\fontsubst@post
  \@fontsubst
}%
%
% \@fontsubst =MATCH-ATTR -REM-FEATURES +ADD-OR-MOD-ATTRS
\def\@fontsubst{%
  % Construct the match string in \fontsubst@match@list.
  \f@mk@falist\fontsubst@\fontsubst@match@list % =MATCH-ATTR
}%
%
% \fontsubst@ -REM-FEATURES +ADD-OR-MOD-ATTRS
\def\fontsubst@{%
  % Construct the rem string in \fontsubst@rem@list.
  \f@mk@flist\fontsubst@@\fontsubst@rem@list % -REM-FEATURES
}%
%
% \fontsubst@ +ADD-OR-MOD-ATTRS
\def\fontsubst@@{%
  % Construct the add string in \fontsubst@add@list.
  \f@mk@falist\fontsubst@fin\fontsubst@add@list% +ADD-OR-MOD-ATTRS
}%
%
\def\fontsubst@fin{%
  % Add the new substitution to either head or tail of the current
  % substitution list.
  \let\@end\relax
  \edef\f@subst@list{\fontsubst@mklist}%
}%
%
\def\fontsubst@pre{%
  \fontsubst@match@list \@end
  \fontsubst@rem@list   \@end
  \fontsubst@add@list   \@end
  \f@subst@list
}%
%
\def\fontsubst@post{%
  \f@subst@list
  \fontsubst@match@list \@end
  \fontsubst@rem@list   \@end
  \fontsubst@add@list   \@end
}%
%
% Initialize the font substitution list to empty.
\let\f@subst@list\empty
%
% Given the name of an attribute, return index of the attribute
% in \tempa and index of the attribute's feature in \tempb.
\def\f@get@af#1{%
  % Get attribute's index.
  \expandafter\let\expandafter\tempa\csname fa:#1\endcsname
  \ifx\tempa\relax
    \errmessage{Undefined font attribute "#1"}%
  \fi
  % Get feature index for the attribute.
  \expandafter\let\expandafter\tempb\csname faf:#1\endcsname
  \ifx\tempb\relax
    \errmessage{Undefined font attribute "#1"}%
  \fi
}%
%
% Given a comma-separated list of attributes #4, construct a string
% (saving it in macro #2) as a sequence of `F.A,' specs, where F is
% the index of the feature to which attribute belongs, and A is the
% index of the attribute.  After that, call #1.  #3 is an ignored
% syntax sugar ("=" or "+") from the user-visible command.
\def\f@mk@falist#1#2#3#4 {%
  \let#2\empty % Start with a clean slate.
  \let\do\relax
  \for\f@i:=#4\do{%
    \f@get@af\f@i
    \edef#2{#2\do\tempb.\tempa,}% Append the spec to the list.
  }%
  #1%
}%
%
% Given a comma-separated list of features #4, construct a string
% (saving it in macro #2) as a sequence of `F,' specs, where F is the
% feature index.  After that, call #1.  After that, call #1.  #3 is an
% ignored syntax sugar ("-") from the user-visible command.
\def\f@mk@flist#1#2#3#4 {%
  \let#2\empty
  \let\do\relax
  \for\f@i:=#4\do{%
    \expandafter\let\expandafter\temp\csname ff:\f@i\endcsname
    \ifx\temp\relax
      \errmessage{Undefined font feature "\f@i"}%
    \fi
    \edef#2{#2\do\temp,}%
  }%
  #1%
}%
%
% \fori{FROM-INCL}{TO-EXCL}{EXEC}
\def\fori#1#2#3{%
  \count@=#1\relax
  \loop
    #3\relax
    \advance\count@ by1
  \ifnum\count@<#2\repeat
}%
%
%
%
% Generic filter parsing macros.  Configure by defining these
% callbacks (before running \f@run@filter on the filter string):
%
%   \f@do@filter@match#1.#2,  - match the pair filter/attribute.
%   \f@do@filter@rem          - unset feature `F,'.
%   \f@do@filter@add          - add the pair `F.A,'.
%   \f@hook@filter@end        - action at the end of the filter (upon
%                               successful match).
%   \f@hook@filter@skip       - action for \f@filter@gobble@this
%                               and \f@filter@gobble@all.
%
% To skip one filter, \f@do@filter@match can
% call \f@filter@gobble@this.  To skip all remaining filters,
% call \f@filter@gobble@all.
%
% Note:  Theoretically, we can use a construct like
%
%   \csname ff@\csname faf:A\endcsname\endcsname
%
% to get the feature corresponding to attribute A.  But this would
% fail with an incomprehensible error message (`missing \endcsname')
% if \csname faf:A\endcsname is undefined, so we'd have to test this
% before each use.  To avoid the overhead, we just add the font
% feature index to the font filter.
%
%
% \f@run@filter
%   [MATCH-ATTR\@end REM-FEATURES\@end ADD-OR-MOD-ATTRS\@end [...]]\relax
\def\f@run@filter{%
  \let\do\f@do@filter@match
  \let\@end\f@run@filter@rem
}%
%
% \f@run@filter@rem REM-FEATURES\@end ADD-OR-MOD-ATTRS\@end ... \relax
\def\f@run@filter@rem{%
  \let\do\f@do@filter@rem
  \let\@end\f@run@filter@add
}%
%
% \f@run@filter@add ADD-OR-MOD-ATTRS\@end ... \relax
\def\f@run@filter@add{%
  \let\do\f@do@filter@add
  \let\@end\f@hook@filter@end
}%
%
\def\f@filter@gobble@this#1\@end#2\@end#3\@end{\f@hook@filter@skip \f@run@filter}%
\def\f@filter@gobble@all#1\relax{\f@hook@filter@skip}%
%
%
%
% Filter parsing callbacks for font substitution.
%
% Apply only the first font substitution matching the current font.
\def\f@subst@once{%
  \let\f@do@filter@match\f@subst@match@init
  \let\f@do@filter@rem\f@subst@rem
  \let\f@do@filter@add\f@subst@add
  \let\f@hook@filter@end\f@subst@nomore
  \let\f@hook@filter@skip\f@subst@skip
  \expandafter\f@run@filter \f@subst@list \relax
}%
%
% Apply all font substitutions in order, allowing substitutions to be
% chained.
\def\f@subst{%
  \let\f@do@filter@match\f@subst@match@init
  \let\f@do@filter@rem\f@subst@rem
  \let\f@do@filter@add\f@subst@add
  \let\f@hook@filter@end\f@subst@again
  \let\f@hook@filter@skip\f@subst@skip
  \f@verbose{^^J\the\inputlineno: (Running font substitution filter:}%
  \expandafter\f@run@filter \f@subst@list \relax
  \f@verbose{^^J)}%
}%
%
% Match one feature.
\def\f@subst@match@init{%
  \f@verbose{^^J(}%
  \let\do\f@subst@match
  \do
}%
%
\def\f@subst@match#1.#2,{%
  \f@verbose{^^Jmatching \csname ff@#1\endcsname.\csname fa@#2\endcsname}%
  % This funky way to compare the two numbers takes care of \ff#1
  % being \empty.  However, keep in mind that if \ff#1 is undefined,
  % the following will make it a \relax.  Space at the end of \ff#1
  % will be gobbled by TeX, because it's a space following a number.
  \ifnum 1#2=1\csname ff#1\endcsname \else
    \f@verbose{^^J  skipping, unmatched
      \csname ff@#1\endcsname.\csname fa@#2\endcsname}%
    \expandafter\f@filter@gobble@this % Skip to the next filter.
  \fi
}%
%
% Remove one feature.
\def\f@subst@rem#1,{%
  \f@verbose{^^Junsetting \csname ff@#1\endcsname}%
  \expandafter\let\csname ff#1\endcsname \empty
}%
%
% Add attribute #2 (which must belong to feature #1).
\def\f@subst@add#1.#2,{%
  \f@verbose{^^Jadding \csname ff@#1\endcsname.\csname fa@#2\endcsname}%
  \expandafter\def\csname ff#1\endcsname{#2 }%
}%
%
\def\f@subst@nomore{\f@verbose{^^J)}\f@filter@gobble@all}%
\def\f@subst@again{\f@verbose{^^J)}\f@run@filter}%
\def\f@subst@skip{\f@verbose{^^J)}}%
%
%
%
% Filter parsing callbacks for pretty-printing the current font filter
% string.
%
% Pretty-print the current font filter string.
\def\dumpfontfilter{\f@dump@filter\f@subst@list}%
%
% Pretty-print the given font filter string.
\def\f@dump@filter#1{%
  \let\f@do@filter@match\f@dump@match@init
  \let\f@do@filter@rem\f@dump@rem@init
  \let\f@do@filter@add\f@dump@add@init
  \let\f@hook@filter@end\f@dump@again
  \let\f@hook@filter@skip\relax
  \message{\the\inputlineno: (Dumping font substitution filter:}%
  \expandafter\f@run@filter #1\relax
  \message{^^J)}%
}%
\def\f@dump@match@init{%
  \message{^^J(^^J=}%
  \def\do##1.##2,{\message{\csname ff@##1\endcsname.\csname fa@##2\endcsname}}%
  \do
}%
\def\f@dump@rem@init{%
  \message{^^J-}%
  \def\do##1,{\message{\csname ff@##1\endcsname}}%
  \do
}%
\def\f@dump@add@init{%
  \message{^^J+}%
  \def\do##1.##2,{\message{\csname ff@##1\endcsname.\csname fa@##2\endcsname}}%
  \do
}%
\def\f@dump@again{\message{^^J)}\f@run@filter}%

\endinput

\ftracelevel=3

\newfontattr family   CMRoman
\newfontattr family   CMTypewriter
\newfontattr family   CMSansSerif
\newfontattr encoding OT1
\newfontattr encoding OML
\newfontattr encoding OMS
\newfontattr encoding OMX
\newfontattr slant    up
\newfontattr slant    sl
\newfontattr slant    it
\newfontattr slant    ui

\fontsubstpre =CMRoman -slant +OT1
%\dumpfontfilter

\fontsubstpre =up - +CMRoman,OML
%\dumpfontfilter

\fontsubstpost =CMTypewriter -encoding +
%\dumpfontfilter

\fontsubstpost =CMRoman,up,OML -slant +OMX
%\dumpfontfilter

\fontsubstpre =it,CMTypewriter,OMX -family,encoding +OT1
%\dumpfontfilter

\fontsubstpost =CMSansSerif,ui -encoding +CMTypewriter,OMX,it
%\dumpfontfilter

\fontsubstpost =it,CMTypewriter,OMX -encoding,family +ui,OMS

\dumpfontfilter
\dumpfontfeatures

%\newfont 10 cmbx10 {}
%\message{^^J***\f@name}
\newfont 10 1000 cmti10 CMRoman,it,OT1
\message{^^J***\f@name}
\newfont 10 1000 cmt10 CMRoman,OT1
\message{^^J***\f@name}
\newfont 12pt 1000 cmti12 CMRoman,it,OT1
\message{^^J***\f@name}
\newfont 10 1000 cmmi10 ui,OMS
\message{^^J***\f@name}
\newfont 8 1000 cmti8 CMRoman,it,OT1
\message{^^J***\f@name}
\newfont 11 1000 cmti11 CMRoman,it,OT1
\message{^^J***\f@name}
\newfont 8 1000 cmt8 CMRoman,OT1
\message{^^J***\f@name}
\newfont 9 1000 cmt9 CMRoman,OT1
\message{^^J***\f@name}
\newfont 0.2cm 1000 cmti1cm CMRoman,it,OT1
\message{^^J***\f@name}
\newfont 1.in 1000 cmin it,OT1
\message{^^J***\f@name}
\newfont 5. 1000 cmot1 OT1
\message{^^J***\f@name}

\setfont        {CMRoman,it,0.3cm,OMS}% -> family=CMRoman encoding=OT1 slant=<unset>
\dumpfontfeatures
\setfont        {CMTypewriter,it,OMX}%  -> family=<unset> encoding=OT1 slant=it
\dumpfontfeatures
\addfontattrs   {7,up}%                 -> family=CMRoman encoding=OT1 slant=<unset>
\dumpfontfeatures
\remfontfeatures{family}%               -> family=<unset> encoding=OT1 slant=<unset>
\dumpfontfeatures
\modfont        {}{CMSansSerif,ui}%     -> family=<unset> encoding=OMS slant=ui
\dumpfontfeatures
\remfontfeatures{}%                     -> family=<unset> encoding=OMS slant=ui
\dumpfontfeatures
\addfontattrs   {}%                     -> family=<unset> encoding=OMS slant=ui
\dumpfontfeatures
\modfont        {}{}%                   -> family=<unset> encoding=OMS slant=ui
\dumpfontfeatures

\message{^^J*********************************************************}

\newfontattr family   TestFamily
\newfont 5. 1000 sz5-1.0-1 TestFamily
\message{^^J***\f@name}
\newfont 5. 1000 sz5-1.0-2 TestFamily
\message{^^J***\f@name}
\newfont 5. 1400 sz5-1.4-1 TestFamily
\message{^^J***\f@name}
\newfont 5. 900 sz5-0.9-1 TestFamily
\message{^^J***\f@name}
\newfont 5. 900 sz5-0.9-2 TestFamily
\message{^^J***\f@name}
\newfont 5. 1400 sz5-1.4-2 TestFamily
\message{^^J***\f@name}
\setfont        {CMRoman,9pt}%          ->

\newfont 10 1000 cmr10 CMRoman,rm,OT1
\message{^^J***\f@name}

\bye


code:
% font set              Computer Modern, Latin Modern, Bera
%   font family         CMRoman, CMTypewriter, CMSansSerif
%
% font feature          slant, weight, width
%   font attribute      sl, it, bold, cond
%
% fontfile
%
% Font features and attributes:
%
%     size              design point size
%     encoding          ot1, oml, oms, omx, t1, ts1, t2a
%     slant             up, sl, it, ui, cursive
%     weight            lt, med, semib, bold, bx,
%     width             cond, normal, ext
%     figures           oldfigures, liningfigures
%     caps              normalcaps, allcaps, smallcaps, nocaps

\input eplain

\catcode`@=11
%
% \fsz:CMRoman/<OT1>/<up>/<med>/<normal>/<liningfigures>/<normalcaps>
% ->
% cmr5/5,cmr7/7,cmr8/8,cmr9/9,cmr10/10,cmr12/12,cmr17/17,.
%
% {CMRoman}{b}{bx}
% {OML,CMRoman}{m}{up}
% {OML,CMRoman}{bx}{up}
% {OML,CMRoman}{b}{bx}
% {CMSans}{it}{sl}
% {CMSans}{b}{bx}
% {CMSans,OML}{}{CMRoman}
% {ConcreteRoman}{b}{LMSans,b}
% {OT1,CMMath}{}{CMRoman,n}
% {OMX,CMMath}{bx}{m}
%

%
%
%
% Font-related logging.  Meanings of \ftracelevel values are:
%
%   0 - none
%   1 - warning
%   2 - debug
%   3 - verbose
\newcount\ftracelevel
\ftracelevel=1
%
\def\f@warning{\f@trace0 }%
\def\f@debug  {\f@trace1 }%
\def\f@verbose{\f@trace2 }%
\def\f@trace#1{%
  \ifnum#1<\ftracelevel
    \expandafter\message
  \else
    \expandafter\gobble
  \fi
}%
%
%
%
% Construction of font filter strings.
%
% \fontsubstpre{MATCH-ATTR}{REM-FEATURES}{ADD-OR-MOD-ATTRS}
%
% Install a new font substitution to be applied before other font
% substitutions.
\def\fontsubstpre{%
  \let\fontsubst@mklist\fontsubst@pre
  \@fontsubst
}%
%
% \fontsubstpost{MATCH-ATTR}{REM-FEATURES}{ADD-OR-MOD-ATTRS}
%
% Install a new font substitution to be applied after other font
% substitutions.
\def\fontsubstpost{%
  \let\fontsubst@mklist\fontsubst@post
  \@fontsubst
}%
%
% \@fontsubst{MATCH-ATTR}{REM-FEATURES}{ADD-OR-MOD-ATTRS}
\def\@fontsubst{%
  % Construct the match string in \fontsubst@match@list.
  \f@mk@falist\fontsubst@\fontsubst@match@list%{MATCH-ATTR}
}%
%
% \fontsubst@{REM-FEATURES}{ADD-OR-MOD-ATTRS}
\def\fontsubst@{%
  % Construct the rem string in \fontsubst@rem@list.
  \f@mk@flist\fontsubst@@\fontsubst@rem@list%{REM-FEATURES}
}%
%
% \fontsubst@{ADD-OR-MOD-ATTRS}
\def\fontsubst@@{%
  % Construct the add string in \fontsubst@add@list.
  \f@mk@falist\fontsubst@fin\fontsubst@add@list%{ADD-OR-MOD-ATTRS}
}%
%
\def\fontsubst@fin{%
  % Add the new substitution to either head or tail of the current
  % substitution list.
  \let\@end\relax
  \edef\f@subst@list{\fontsubst@mklist}%
}%
%
\def\fontsubst@pre{%
  \fontsubst@match@list \@end
  \fontsubst@rem@list   \@end
  \fontsubst@add@list   \@end
  \f@subst@list
}%
%
\def\fontsubst@post{%
  \f@subst@list
  \fontsubst@match@list \@end
  \fontsubst@rem@list   \@end
  \fontsubst@add@list   \@end
}%
%
% Initialize the font substitution list to empty.
\let\f@subst@list\empty
%
% Given the name of an attribute, return index of the attribute
% in \tempa and index of the attribute's feature in \tempb.
\def\f@get@af#1{%
  % Get attribute's index.
  \expandafter\let\expandafter\tempa\csname fa:#1\endcsname
  \ifx\tempa\relax
    \errmessage{Undefined font attribute `#1'}%
  \fi
  % Get feature index for the attribute.
  \expandafter\let\expandafter\tempb\csname faf:#1\endcsname
  \ifx\tempb\relax
    \errmessage{Undefined font attribute `#1'}%
  \fi
}%
%
% Given a comma-separated list of attributes #3, construct a string
% (saving it in macro #2) as a sequence of `F.A,' specs, where F is
% the index of the feature to which attribute belongs, and A is the
% index of the attribute.  After that, call #1.
\def\f@mk@falist#1#2#3{%
  \let#2\empty % Start with a clean slate.
  \let\do\relax
  \for\f@i:=#3\do{%
    \f@get@af\f@i
    \edef#2{#2\do\tempb.\tempa,}% Append the spec to the list.
  }%
  #1%
}%
%
% Given a comma-separated list of features #3, construct a string
% (saving it in macro #2) as a sequence of `F,' specs, where F is the
% feature index.  After that, call #1.
\def\f@mk@flist#1#2#3{%
  \let#2\empty
  \let\do\relax
  \for\f@i:=#3\do{%
    \expandafter\let\expandafter\temp\csname ff:\f@i\endcsname
    \ifx\temp\relax
      \errmessage{Undefined font feature `\f@i'}%
    \fi
    \edef#2{#2\do\temp,}%
  }%
  #1%
}%
%
% \fori{FROM-INCL}{TO-EXCL}{EXEC}
\def\fori#1#2#3{%
  \count@=#1\relax
  \loop
    #3\relax
    \advance\count@ by1
  \ifnum\count@<#2\repeat
}%
%
%
%
% Font feature manipulations.
%
% \setfont{ATTRS}
\def\setfont{%
  % Empty \ff0, \ff1, ..., \ff<ffeature_count - 1>.
  \ff@reset
  \addfontattrs % Substitutes and sets the font.
}%
%
% \modfont{REM-FEATURES}{ADD-OR-MOD-ATTRS}
\def\modfont#1{%
  \unsetfontfeatures{#1}%
  \addfontattrs % Substitutes and sets the font.
}%
%
% \addfontattrs{ADD-OR-MOD-ATTRS}
\def\addfontattrs#1{%
  % For each feature with attribute in #1, set \ff<f> to <a>, where
  % <f> is the feature index and <a> is the attribute index.
  \for\f@i:=#1\do{%
    \f@get@af\f@i
    \expandafter\let\csname ff\tempb\endcsname \tempa
  }%
  % Substitute the font.
  \f@subst
  % Set the font, if we've found one.
}%
%
% \remfontfeatures{REM-FEATURES}
\def\remfontfeatures#1{%
  \unsetfontfeatures{#1}%
  % Substitute the font.
  \f@subst
  % Set the font, if we've found one.
}%
%
\def\unsetfontfeatures#1{%
  % Unset \ff<f> for each feature <f> in #1.
  \for\f@i:=#1\do{%
    \expandafter\let\expandafter\temp\csname ff:\f@i\endcsname
    \ifx\temp\relax
      \errmessage{Undefined font feature `\f@i'}%
    \fi
    \expandafter\let \csname ff\temp\endcsname \empty
  }%
}%
%
% Pretty-print the current settings of font features.
\def\dumpfontfeatures{%
  \def\fa@{}% This will be used for features which are not set,
            % for which \ffN is \empty.
  \message{^^JCurrent font features: (}%
  \fori0\ffeatcount{\message{%
    \csname ff@\the\count@\endcsname.%
    \expandafter\dump@ff\expandafter{\csname ff\the\count@\endcsname}}}%
  \message{)}%
}%
\def\dump@ff#1{\csname fa@#1\endcsname}%
%
%
%
% Generic filter parsing macros.  Configure by defining these
% callbacks (before running \f@run@filter on the filter string):
%
%   \f@do@filter@match#1.#2,  - match the pair filter/attribute
%   \f@do@filter@rem    - unset feature `F,'
%   \f@do@filter@add    - add the pair `F.A,'
%   \f@end@filter@add   - action at the end of the filter.
%
% We could avoid adding font feature together with an attribute and
% use a construct like \csname ff@\csname faf:A\endcsname\endcsname
% to get the feature corresponding to attribute A.  But this would
% fail with an incomprehensible error message (`missing \endcsname')
% if \csname faf:A\endcsname is undefined, so we'd have to test this
% before each use.  To avoid the overhead, we just add the font
% feature index to the font filter.
%
% \f@run@filter
%   [MATCH-ATTR\@end REM-FEATURES\@end ADD-OR-MOD-ATTRS\@end [...]]\relax
\def\f@run@filter{%
  \let\do\f@do@filter@match
  \let\@end\f@run@filter@rem
}%
\def\f@run@filter@again{\f@verbose{^^J)}\f@run@filter}%
%
% \f@run@filter@rem REM-FEATURES\@end ADD-OR-MOD-ATTRS\@end ... \relax
\def\f@run@filter@rem{%
  \let\do\f@do@filter@rem
  \let\@end\f@run@filter@add
}%
%
% \f@run@filter@add ADD-OR-MOD-ATTRS\@end ... \relax
\def\f@run@filter@add{%
  \let\do\f@do@filter@add
  \let\@end\f@end@filter@add
}%
%
\def\f@filter@gobble@this#1\@end#2\@end#3\@end{\f@run@filter@again}%
\def\f@filter@gobble@all#1\relax{}%
%
%
%
% Filter parsing callbacks for font substitution.
%
% Apply only the first font substitution matching the current font.
\def\f@subst@once{%
  \let\f@do@filter@match\f@subst@match@init
  \let\f@do@filter@rem\f@subst@rem
  \let\f@do@filter@add\f@subst@add
  \let\f@end@filter@add\f@subst@nomore
  \expandafter\f@run@filter \f@subst@list \relax
}%
%
% Apply all font substitutions in order, allowing substitutions to be
% chained.
\def\f@subst{%
  \let\f@do@filter@match\f@subst@match@init
  \let\f@do@filter@rem\f@subst@rem
  \let\f@do@filter@add\f@subst@add
  \let\f@end@filter@add\f@run@filter@again
  \expandafter\f@run@filter \f@subst@list \relax
}%
%
% Match one feature.
\def\f@subst@match@init{%
  \f@verbose{^^J(}%
  \let\do\f@subst@match
  \do
}%
%
\def\f@subst@match#1.#2,{%
  \f@verbose{^^Jmatching \csname ff@#1\endcsname.\csname fa@#2\endcsname}%
  % This funky way to compare the two numbers takes care of \ff#1
  % being \empty.  However, keep in mind that if \ff#1 is undefined,
  % the following will make it a \relax.
  \ifnum 1#2=1\csname ff#1\endcsname \else
    \f@verbose{^^J  skipping unmatched
      \csname ff@#1\endcsname.\csname fa@#2\endcsname}%
    \expandafter\f@filter@gobble@this % Skip to the next filter.
  \fi
}%
%
% Remove one feature.
\def\f@subst@rem#1,{%
  \f@verbose{^^Junsetting \csname ff@#1\endcsname}%
  \expandafter\let\csname ff#1\endcsname \empty
}%
%
% Add attribute #2 (which must belong to feature #1).
\def\f@subst@add#1.#2,{%
  \f@verbose{^^Jadding \csname ff@#1\endcsname.\csname fa@#2\endcsname}%
  \expandafter\def\csname ff#1\endcsname{#2}%
}%
%
\def\f@subst@nomore{\f@verbose{^^J)}\f@filter@gobble@all}%
%
%
%
% Filter parsing callbacks for pretty-printing the current font filter
% string.
%
%\def\dumpfontfilter{%
%}%
% Pretty-print the current font filter string.
\def\dumpfontfilter{\f@dump@filter\f@subst@list}%
%
% Pretty-print the given font filter string.
\def\f@dump@filter#1{%
  \message{^^J(}%
  \let\do\space
  \expandafter\f@dump@filter@#1\relax
}%
%
\def\f@dump@filter@#1\@end#2\@end#3\@end#4\relax{%
  \message{^^J=#1^^J-#2^^J+#3^^J}%
  \def\temp{#4}%
  \ifx\temp\empty
    \message{^^J)}%
    \expandafter\f@filter@gobble@all
  \else
    \expandafter\f@dump@filter@
  \fi
  #4\relax
}%
%
%
%
% Defining new font features.
%
\newcount\fcacheidx % Font cache index.
\newcount\ffeatcount % Font feature count.
\let\ff@reset\empty % We'll build this up as we add font features.
%
% \newfontattr FONTFEATURE FONTATTR
\def\newfontattr #1 #2 {%
  % Define a new font attribute, if it's not defined yet.
  \expandafter\ifx\csname fa:#2\endcsname \relax
  \else
    \errmessage{Font attribute `#2' already defined as part
      of font feature `\csname ff@\csname faf:#2\endcsname\endcsname'}%
  \fi
  \expandafter\xdef\csname fa:#2\endcsname{\the\fcacheidx}%
  \expandafter\xdef\csname fa@\the\fcacheidx\endcsname{#2}%
  % Invalidate current font cache (and update index for the next font
  % attribute).
  \global\advance\fcacheidx by1
  %
  % Define a new font feature, if it's not defined yet.
  \expandafter\ifx\csname ff:#1\endcsname \relax
    \expandafter\xdef\csname ff:#1\endcsname{\the\ffeatcount}%
    \expandafter\xdef\csname ff@\the\ffeatcount\endcsname{#1}%
    % Update \ff@reset to clear the new font feature cell \ffN.
    \toks@=\expandafter{\ff@reset}%
    \xdef\ff@reset{\the\toks@
      \let\expandafter\noexpand\csname ff\the\ffeatcount\endcsname
        \noexpand\empty}%
    % Set \ffN to \empty, otherwise it will be set to \relax the first
    % time we try to access it through \csname...\endcsname, and we
    % depend on it to be either a number or \empty.
    \global\expandafter\let\csname ff\the\ffeatcount\endcsname \empty
    %
    \global\advance\ffeatcount by1
    % We've added a new font feature, so we should invalidate current
    % font cache.  But we've already done so above when adding the new
    % font attribute.
    %\global\advance\fcacheidx by1
  \fi
  % Assign the font attribute to the font feature.
  \expandafter\xdef\csname faf:#2\endcsname{\csname ff:#1\endcsname}%
}%
%
% \newfontfamily FONTSET FONTFAMILY
\def\newfontfamily #1 #2 {%
  \f@def{fset}{#1}{ffam}{#2}{Font family}%
  \f@defadd{fset:#1}{#2}%
}%
%
% \f@def {PARENT-PREFIX} {PARENT-NAME} {CHILD-PREFIX} {CHILD-NAME} {CHILD-DESCR}
\def\f@def#1#2#3#4#5{%
  % Define parent.
  \expandafter\gdef\csname #1:#2\endcsname
  \expandafter\ifx\csname#1:#2\endcsname\relax
    \expandafter\gdef\csname#1:#2\endcsname{}%
  \else
    \let\do\space
    \errmessage{#3 #2 already defined as `\csname#1:#2\endcsname'}%
  \fi
}%

\ftracelevel=3

\newfontattr family   CMRoman
\newfontattr family   CMTypewriter
\newfontattr family   CMSansSerif
\newfontattr encoding OT1
\newfontattr encoding OML
\newfontattr encoding OMS
\newfontattr encoding OMX
\newfontattr slant    up
\newfontattr slant    sl
\newfontattr slant    it
\newfontattr slant    ui

%\expandafter\def\csname ff:family\endcsname{0}%
%\expandafter\def\csname ff:encoding\endcsname{1}%
%\expandafter\def\csname ff:slant\endcsname{2}%
%
%\expandafter\def\csname fa:CMRoman\endcsname{0}%
%\expandafter\def\csname fa:CMTypewriter\endcsname{1}%
%\expandafter\def\csname fa:CMSansSerif\endcsname{2}%
%
%\expandafter\def\csname fa:OT1\endcsname{3}%
%\expandafter\def\csname fa:OML\endcsname{4}%
%\expandafter\def\csname fa:OMS\endcsname{5}%
%\expandafter\def\csname fa:OMX\endcsname{6}%
%
%\expandafter\def\csname fa:up\endcsname{7}%
%\expandafter\def\csname fa:sl\endcsname{8}%
%\expandafter\def\csname fa:it\endcsname{9}%
%\expandafter\def\csname fa:ui\endcsname{10}%
%
%\ffeatcount=3
%\fattrcount=11
%
%\expandafter\def\csname faf:CMRoman\endcsname{0}%
%\expandafter\def\csname faf:CMTypewriter\endcsname{0}%
%\expandafter\def\csname faf:CMSansSerif\endcsname{0}%
%
%\expandafter\def\csname faf:OT1\endcsname{1}%
%\expandafter\def\csname faf:OML\endcsname{1}%
%\expandafter\def\csname faf:OMS\endcsname{1}%
%\expandafter\def\csname faf:OMX\endcsname{1}%
%
%\expandafter\def\csname faf:up\endcsname{2}%
%\expandafter\def\csname faf:sl\endcsname{2}%
%\expandafter\def\csname faf:it\endcsname{2}%
%\expandafter\def\csname faf:ui\endcsname{2}%

\let\do\relax

\fontsubstpre{CMRoman}{slant}{OT1}
\message{^^J\f@subst@list}

\fontsubstpre{up}{}{CMRoman,OML}
\message{^^J\f@subst@list}

\fontsubstpost{CMSansSerif}{}{OMX}
\message{^^J\f@subst@list}

\fontsubstpost{CMTypewriter}{encoding}{}
\message{^^J\f@subst@list}

\fontsubstpost{CMRoman,up,OML}{slant}{OT1}
\message{^^J\f@subst@list}

\fontsubstpre{it,CMTypewriter,OMX}{family,encoding}{OT1}
\message{^^J\f@subst@list}

\fontsubstpost{it,CMTypewriter,OMX}{encoding,family}{ui,OMS}
\message{^^J\f@subst@list}

%        0.0, 2.9, 1.5
\setfont{CMRoman,it,OMS}% -> , 4, 9
\dumpfontfeatures
%        0.1, 2.9, 1.5
%\setfont{CMTypewriter,it,OMS}% -> 1, , 9
%%        0.2, 2.9, 1.5
%\setfont{CMSansSerif,it,OMS}% -> 2, 6, 9
%%        0.1, 2.9, 1.6
%\setfont{CMTypewriter,it,OMX}% -> , 5, 10
%%        0.0, 2.7, 1.3
%\setfont{CMRoman,up,OT1}% -> , 4, 7
%        0.0, 2.7, 1.4
%\setfont{CMRoman,up,OML}% -> 0, 3,
\dumpfontfilter

%%        0.0, 2.9, 1.4
%\setfont{CMRoman,it,OML}% -> , 4, 9
%\dumpffs
%\modfont{}{CMTypewriter}% -> 1, 4, 9 -> 1, , 9
%\dumpffs
%\modfont{slant}{ui,CMSansSerif}% -> 2, , 10 ->
%\dumpffs

\bye

% \setfont{CMRoman,b,sl}
% \modfont{}{up} % CMRoman,b,up

\def\test{ii,iv,iii,i}
\def\i{0}
\def\ii{1}
\def\iii{2}
\def\iv{3}

\count0=2147483647
\count@=-1

% \f@sort{MAX-ITEM-IDX}{ITEMS}{ITEM-CS-PREFIX}
\def\f@sort#1#2#3{%
  \let\f@sorted\empty
  % Empty \f@sort0, \f@sort1, ..., \f@sort<MAX-ITEM-IDX>.
  \fori{0}{#1}{\expandafter\let\csname f@sort\the\count@\endcsname \empty}%
  % Set \f@sort<X>, where <X> is the value of \<ITEM-CS-PREFIX><ITEM>.
  \for\f@sort@i:=#2\do{%
    \expandafter\edef\csname f@sort\csname#3\f@sort@i\endcsname\endcsname{%
      \csname \f@sort@i\endcsname,%
    }%
  }%
  % Combine all \f@sort<X> into \f@sorted.
  \fori{0}{#1}{%
    \edef\f@sorted{%
      \f@sorted
      \csname f@sort\the\count@\endcsname
    }%
  }%
}%

\f@sort{4}{\test}{}
\show\f@sorted
\bye

% \f@def {PREFIX} {NAME} {DESCR}
\f@def#1#2#3{%
  \expandafter\ifx\csname#1:#2\endcsname\relax
    \expandafter\gdef\csname#1:#2\endcsname{}%
  \else
    \let\do\space
    \errmessage{#3 #2 already defined as `\csname#1:#2\endcsname'}%
  \fi
}%

% \f@defadd {PARENT} {CHILD}
\f@defadd#1#2{%
  \let\do\relax
  \expandafter\edef\expandafter\temp\expandafter{%
    \csname #1\endcsname \do #2}%
  \global\expandafter\let\csname #1\endcsname \temp
}%

% \newfontset FONTSET
\def\newfontset #1 {%
  \f@def{fset}{#1}{Font set}
}%

% \newfontfamily FONTSET FONTFAMILY
\def\newfontfamily #1 #2 {%
  \f@def{ffam}{#2}{Font family}%
  \f@defadd{fset:#1}{#2}%
}%

% \newfontfeature FONTFEATURE
\def\newfontfeature #1 {%
  \f@def{ff}{#1}{Font feature}%
}%

% \newfontattr FONTFEATURE FONTATTR
\def\newfontattr #1 #2 {%
  \f@def{fa}{#2}{Font attribute}%
  \f@defadd{ff:#1}{#2}%
}%

% \newfont FONTFAMILY SIZE ENCODING ATTRIBUTES
\def\newfont #1 #2 #3 #4 {%
}%

\def\f@addattrchar#1#2{%
\bgroup
  \uccode`a=#2%
  \uppercase{%
\egroup
    \xdef#1{a#1}%
  }%
}%

\def\fori#1#2#3{%
  \count@=#1\relax
  \loop
    #3\relax
    \advance\count@ by1
  \ifnum\count@<#2\repeat
}%

\def\f@resetattrs#1{%
 \gdef#1{fa:}%
 \fori{0}{20}{\f@addattrchar#1{`\*}}%
}%

\newcount\numfontfeatures

\f@resetattrs\f@attributes
\show\f@attributes

\bye