# -*- CPERL -*-
# /=====================================================================\ #
# |  TeX                                                                | #
# | Core TeX Implementation for LaTeXML                                 | #
# |=====================================================================| #
# | Part of LaTeXML:                                                    | #
# |  Public domain software, produced as part of work done by the       | #
# |  United States Government & not subject to copyright in the US.     | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov>                        #_#     | #
# | http://dlmf.nist.gov/LaTeXML/                              (o o)    | #
# \=========================================================ooo==U==ooo=/ #
package LaTeXML::Package::Pool;
use strict;
use LaTeXML::Package;
use Unicode::Normalize;
use LaTeXML::Util::Alignment;
use LaTeXML::Util::KeyVal;
use LaTeXML::Util::Pathname;
no warnings 'redefine';

RegisterNamespace(ltx=>"http://dlmf.nist.gov/LaTeXML");

DefMacroI("\\\@empty",undef,Tokens());

# Base ID prefixes everything.
if(my $docid = LookupValue('DOCUMENTID')){
  DefMacroI('\thedocument@ID',undef,$docid); }
else {
  Let(T_CS('\thedocument@ID'),T_CS('\@empty')); }
NewCounter('@XMARG','document',idprefix=>'XM');

#**********************************************************************
# CORE TeX; Built-in commands.
#**********************************************************************

#======================================================================
# Define parsers for standard parameter types.

# Skip any spaces, but don't contribute an argument.
DefParameterType('SkipSpaces', sub{$_[0]->skipSpaces; }, novalue=>1);

# Read the next token
DefParameterType('Token',      sub{$_[0]->readToken; });

# Read the next token, after expanding any expandable ones.
DefParameterType('XToken',     sub{$_[0]->readXToken; });

# Read a number
DefParameterType('Number',     sub{$_[0]->readNumber; });

# Read a dimension
DefParameterType('Dimension',  sub{$_[0]->readDimension; });

# Read a Glue (aka skip)
DefParameterType('Glue',       sub{$_[0]->readGlue; });

# Read a MuDimension (math)
DefParameterType('MuDimension',sub{$_[0]->readMuDimension; });

# Read a MuGlue (math)
DefParameterType('MuGlue',     sub{$_[0]->readMuGlue; });

# Read until the next (balanced) open brace {
# used for the last TeX-style delimited argument
DefParameterType('UntilBrace', sub{ 
  my($gullet)=@_;
  my $value=$gullet->readUntil(T_BEGIN);
  $gullet->unread(T_BEGIN);
  $value; });

DefParameterType('XUntil', sub {
  my($gullet,$until)=@_;
  ($until) = $until->unlist;	# Make sure it's a single token!!!
  my ($token,@tokens)=();
  while($token = $gullet->readXToken()){
    if($token->equals($until)){
      last; }
    elsif($token->getCatcode == CC_BEGIN){
      push(@tokens,$token,$gullet->readBalanced->unlist,T_END); }
    elsif(my $defn = LookupDefinition($token)){
      push(@tokens,Invocation($token,$defn->readArguments($gullet))); }
    else {
      push(@tokens,$token); }}
  Tokens(@tokens); });

# Read a matching keyword, eg. Match:=
DefParameterType('Match',      sub{shift->readMatch(@_); });

# Read a keyword; eg. Keyword:to
# (like Match, but ignores catcodes)
DefParameterType('Keyword',    sub{shift->readKeyword(@_); });

# Read balanced material (?)
DefParameterType('Balanced',   sub{$_[0]->readBalanced; });

# Read a Semiverbatim argument; ie w/ most catcodes neutralized.
DefParameterType('Semiverbatim',sub{ $_[0]->readArg; }, semiverbatim=>1,
		 reversion=>sub { (T_BEGIN,$_[0]->revert,T_END); });

# Read a LaTeX-style optional argument (ie. in []), but the contents read as Semiverbatim.
DefParameterType('OptionalSemiverbatim',sub{ $_[0]->readOptional; },
		 semiverbatim=>1, optional=>1,
		 reversion=>sub { ($_[0] ? (T_OTHER('['),$_[0]->revert,T_OTHER(']')) : ()); });

# Read an argument that will not be digested.
DefParameterType('Undigested',  sub { $_[0]->readArg; }, undigested=>1,
		reversion=>sub {  (T_BEGIN,$_[0]->revert,T_END); });

# Read a LaTeX-style optional argument (ie. in []), but it will not be digested.
DefParameterType('OptionalUndigested',  sub { $_[0]->readOptional; }, 
		 undigested=>1, optional=>1,
		reversion=>sub {  ($_[0] ? (T_OTHER('['),$_[0]->revert,T_OTHER(']')):()); });

# Read a keyword value (KeyVals), that will not be digested.
DefParameterType('UndigestedKey',  sub { $_[0]->readArg; }, undigested=>1);

# Read a token as used when defining it, ie. it may be enclosed in braces.
DefParameterType('DefToken', sub {
  my($gullet)=@_;
  my $token = $gullet->readToken;
  while($token->equals(T_BEGIN)){
    my @toks = grep(!$_->equals(T_SPACE),$gullet->readBalanced->unlist);
    $token = shift(@toks);
    $gullet->unread(@toks); }
  $token; });

# Read a variable, ie. a token (after expansion) that is a writable register.
DefParameterType('Variable', sub {
  my($gullet)=@_;
  my $token = $gullet->readXToken;
  my $defn = $token && LookupDefinition($token);
  if((defined $defn) && $defn->isRegister && !$defn->isReadonly){
    [$defn,$defn->readArguments($gullet)]; }
  else {
    Error(":expected:<variable> A <variable> was supposed to be here, got".Stringify($token));
    undef; }});

DefParameterType('TeXFileName', sub {
  my($gullet)=@_;
  my($token,$cc,@tokens)=();
  while(($token = $gullet->readXToken())
	&& (($cc=$token->getCatcode) != CC_SPACE) &&($cc!=CC_EOL)&&($cc!=CC_COMMENT)&&($cc!=CC_CS)){
    push(@tokens,$token); }
  $gullet->unread($token);
  Tokens(@tokens); });

# This reads a Box as needed by \raise, \lower, \moveleft, \moveright.
# Hopefully there are no issues with the box being digested
# as part of the reader???
DefParameterType('MoveableBox', sub{
  my($gullet)=@_;
  $gullet->skipSpaces;
  my ($box,@stuff) = $STATE->getStomach->invokeToken($gullet->readXToken);
  Error(":expected:<box>  A <box> was supposed to be here, got ".Stringify($box))
    unless $box && $box->isa('LaTeXML::Whatsit')
      && ($box->getDefinition->getCSName =~ /^(\\hbox|\\vbox||\\vtop)$/);
  $box; });

# Read a parenthesis delimited argument.
# Note that this does NOT balance () within the argument.
DefParameterType('BalancedParen', sub {
  my ($gullet) = @_;
  my $tok = $gullet->readXToken;
  if (ref $tok && ToString($tok) eq '(') {
    $gullet->readUntil(T_OTHER(')'));
  } else {
    $gullet->unread($tok) if ref $tok;
    undef; }},
   reversion=> sub {
     (T_OTHER('('), $_[0]->revert, T_OTHER(')')); });


# Read a digested argument.
# The usual parameter (generally written as {}) gets
# tokenized and digested in separate stages (like TeX),
# and so it is tokenized w/o recognizing any special macros within (eg. \url).
# This parameter gets digested until the (required) opening { is balanced.
# It is useful when the content would usually need to have been \protect'd
# in order to correctly deal with catcodes.
DefParameterType('Digested', sub {
  my($gullet)=@_;
  $gullet->skipSpaces;
  my $ismath     = $STATE->lookupValue('IN_MATH');
  my $token = $gullet->readXToken;
  my @list=();
  if($token->equals(T_BEGIN)){
    Digest($token);
    @list = $STATE->getStomach->digestNextBody(); pop(@list); }
  else {
    @list = $STATE->getStomach->invokeToken($token); }
  # In most (all?) cases, we're really looking for a single Whatsit here...
  @list = grep(ref $_ ne 'LaTeXML::Comment', @list);
  (scalar(@list) == 1 ? $list[0] 
   : ($ismath ? LaTeXML::MathList->new(@list) : LaTeXML::List->new(@list))); },
		 undigested=>1,		# since _already_ digested.
		 reversion=>sub { (T_BEGIN,$_[0]->revert,T_END); });

# A variation: Digest until we encounter a given token!
DefParameterType('DigestUntil', sub {
  my($gullet,$until)=@_;
  $gullet->skipSpaces;
  my $ismath     = $STATE->lookupValue('IN_MATH');
  my @list = $STATE->getStomach->digestNextBody($until); 
  @list = grep(ref $_ ne 'LaTeXML::Comment', @list);
  (scalar(@list) == 1 ? $list[0] 
   : ($ismath ? LaTeXML::MathList->new(@list) : LaTeXML::List->new(@list))); },
		 undigested=>1,		# since _already_ digested.
		 reversion=>sub { (T_BEGIN,$_[0]->revert,T_END); });

# Reads until the current group has ended.
# This is useful for environment-like constructs,
# particularly alignments (which may or may not be actual environments),
# but which need special treatment of some of their content
# as the expansion is carried out.
DefParameterType('DigestedBody', sub {
  my($gullet)=@_;
  my $ismath     = $STATE->lookupValue('IN_MATH');
  my @list = $STATE->getStomach->digestNextBody();
  # In most (all?) cases, we're really looking for a single Whatsit here...
  @list = grep(ref $_ ne 'LaTeXML::Comment', @list);
  (scalar(@list) == 1 ? $list[0] 
   : ($ismath ? LaTeXML::MathList->new(@list) : LaTeXML::List->new(@list))); },
 undigested=>1);

# In addition to the standard TeX Dimension, there are various LaTeX constructs
# (particularly, the LaTeX picture environment, and the various pstricks packages)
# that take a different sort of length.  They differ in two ways.
#   (1) They do not accept a comma as decimal separator 
#      (they generally use it to separate coordinates), and
#   (2) They accept a plain float which is scaled against a Dimension register.
#      Actually, there are two subcases:
#     (a) picture accepts a float, which is scaled against \unitlength
#     (b) pstricks accepts a float, and optionally a unit,
#        If the unit is omitted, it is relative to \psxunit or \psyunit.
# How to capture these ?
## DefParameterType('Length', sub {
##   my($gullet,$unit)=@_;

#**********************************************************************
# Expandable Primitives
# See The TeXBook, Ch. 20, Definitions (also called Macros) pp. 212--215
#**********************************************************************

#======================================================================
# Conditionals
#   Expand enough to determine true/false, then maybe skip
#   record a flag somewhere so that \else or \fi is recognized
#   (otherwise, they should signal an error)

# Skipping for conditionals
#   0 : skip to \fi
#  -1 : skip to \else, if any, or \fi
#   n : skip to n-th \or, if any, or \else, if any, or \fi.
sub skipConditionalBody {
  my($gullet,$nskips)=@_;
  my $level=1;
  my $n_ors = 0;
  while(my $t= $gullet->readToken){
    if(defined(my $defn = LookupDefinition($t))){
      if($defn->isExpandable && $defn->isConditional){
	$level++; }
      elsif(Equals($t,T_CS('\fi')) && (!--$level)){
	fiHandler($gullet); return; }
      elsif($level > 1){	# Ignore nested \else,\or
      }
      elsif(Equals($t,T_CS('\or')) && (++$n_ors == $nskips)){
	return; }
      elsif(Equals($t,T_CS('\else')) && $nskips){
	return; }}}
  Fatal(":expected:\\fi Missing \\fi or \\else, conditional fell off end"); }

# NOTE: See Definition setting current_if_level before conditionals!

sub ifHandler   { 
  my($gullet,$boolean)=@_;
  AssignValue(if_level=>LookupValue('current_if_level'), 'global');
  skipConditionalBody($gullet,-1) unless $boolean; return; }

sub elseHandler { 
  my($gullet)=@_;
  my ($curr,$level) = (LookupValue('current_if_level'),LookupValue('if_level'));
  if(!$curr){
    Error(":unexpected:\\else  Didn't expect an \\else since we seem not to be in an \\if");
    return; }
  elsif($curr > $level){
    (T_CS('\relax'),T_CS('\else')); }
  else {
    skipConditionalBody($gullet,0); return; }}

sub fiHandler {
  my($gullet)=@_;
  my ($curr,$level) = (LookupValue('current_if_level'),LookupValue('if_level'));
  if(!$curr){
    Error(":unexpected:\\fi  Didn't expect an \\fi here since we seem not to be in an \\fi");
    return; }
  elsif($curr > $level){
    (T_CS('\relax'),T_CS('\fi')); }
  else {
    AssignValue(current_if_level=>$curr-1, 'global'); 
    AssignValue(if_level=>$curr-1, 'global'); 
    return; }}

#======================================================================
# Should complain if we aren't actually evaluating an \if
DefMacroI('\else',undef, \&elseHandler);
DefMacroI('\or',  undef, \&elseHandler);
DefMacroI('\fi',  undef, \&fiHandler);

sub compare {
  my($a,$rel,$b)=@_;
  if   ($rel->equals(T_OTHER('<')) || $rel->equals(T_CS('\@@<'))){ $a < $b; }
  elsif($rel->equals(T_OTHER('='))                              ){ $a == $b; }
  elsif($rel->equals(T_OTHER('>')) || $rel->equals(T_CS('\@@>'))){ $a > $b; }
  else { Error(":expected:<relationaltoken> Expected a relational token but got ".Stringify($rel)." in comparison conditional"); }}

DefMacro('\ifnum Number Token Number', 
	      sub { ifHandler($_[0],compare($_[1]->valueOf,$_[2],$_[3]->valueOf)); },
	      isConditional=>1);
DefMacro('\ifdim Dimension Token Dimension',
	      sub { ifHandler($_[0],compare($_[1]->valueOf,$_[2],$_[3]->valueOf)); },
	      isConditional=>1);
DefMacro('\ifodd Number',
	      sub { ifHandler($_[0],$_[1]->valueOf % 2); },
	     isConditional=>1);


# NOTE: We don't KNOW if we're in vertical, horizontal or inner mode!!!!!!!
DefMacroI('\ifvmode', undef, sub { ifHandler($_[0],0); }, isConditional=>1);
DefMacroI('\ifhmode', undef, sub { ifHandler($_[0],0); }, isConditional=>1);
DefMacroI('\ifinner', undef, sub { ifHandler($_[0],0); }, isConditional=>1);

DefMacroI('\ifmmode',undef, sub { ifHandler($_[0],LookupValue('IN_MATH')); }, isConditional=>1);

DefMacro('\if XToken XToken',
	      sub { ifHandler($_[0],$_[1]->getCharcode == $_[2]->getCharcode); },
	      isConditional=>1);
DefMacro('\ifcat XToken XToken', 
	      sub { ifHandler($_[0],$_[1]->getCatcode  == $_[2]->getCatcode); },
	      isConditional=>1);

# !! ????
DefMacro('\ifx Token Token', sub {
  my($gullet,$token1,$token2)=@_;
  my $def1=LookupMeaning($token1);
  my $def2=LookupMeaning($token2);
  my $bool=0;
  if(defined $def1 != defined $def2){	       # Don't both have defs or not have defs
    $bool=0; }
  elsif(!defined $def1 && !defined $def2){	# Neither have defs
    $bool = ($token1->getCatcode  == $token2->getCatcode)
      &&    ($token1->getCharcode == $token2->getCharcode); }
  elsif($def1->equals($def2)){	# Same defn (hopefully covers same primitives ???)
    $bool = 1; }		# But what about same \chardefs, etc....
  ifHandler($gullet,$bool); },
	      isConditional=>1);

# Kinda rough: We don't really keep track of modes as carefully as TeX does.
# We'll assume that a box is horizontal if there's anything at all,
# but it's not a vbox (!?!?)
sub classify_box {
  my($boxnum)=@_;
  my $box = LookupValue('box'.$boxnum->valueOf);
  if(!$box){ undef; }
  elsif($box->isa('LaTeXML::Whatsit') && ($box->getDefinition eq LookupDefinition(T_CS('\vbox')))){
    'vbox'; }
  else {
    'hbox'; }}

DefMacro('\ifvoid Number', sub { ifHandler($_[0],!classify_box($_[1]));}, isConditional=>1);
DefMacro('\ifhbox Number', sub { ifHandler($_[0],classify_box($_[1]) eq 'hbox');}, isConditional=>1);
DefMacro('\ifvbox Number', sub { ifHandler($_[0],classify_box($_[1]) eq 'vbox');}, isConditional=>1);

# Implementing this assumes we've got some notion of file numbers!!!
DefMacroI('\ifeof',  undef,sub { ifHandler($_[0],0); }, isConditional=>1);
DefMacroI('\iftrue', undef,sub { ifHandler($_[0],1); }, isConditional=>1);
DefMacroI('\iffalse',undef,sub { ifHandler($_[0],0); }, isConditional=>1);

DefMacro('\ifcase Number', sub {
  my($gullet,$num)=@_;
  AssignValue(if_level=>LookupValue('current_if_level'), 'global');
  $num = $num->valueOf;
  if($num > 0){ 
    skipConditionalBody($gullet,$num); }
  return; },
  isConditional=>1);

#======================================================================

DefPrimitive('\relax',undef);
DefMacro('\number Number',       sub { Explode($_[1]->valueOf); });
# define it here (only approxmiately), since it's already useful.
Let(T_CS('\protect'),T_CS('\relax'));

#======================================================================

DefMacro('\romannumeral Number', sub { roman($_[1]->valueOf); });
DefMacro('\string Token ',        sub { Explode($_[1]->getString); });
DefMacroI('\jobname',undef,Tokens()); # Set to the filename by initialization

DefMacroI('\fontname', undef,sub { Explode("fontname not implemented"); });

DefMacro('\meaning Token',sub {
  my($gullet,$tok)=@_;
  my $meaning = LookupMeaning($tok);
  Explode(defined $meaning ? $meaning : 'undefined'); });

DefMacroI('\csname',undef,sub {
  my($gullet)=@_;
  my($token,@toks)=();
  while(($token = $gullet->readXToken()) && ($token->getString ne '\endcsname')){
    push(@toks,$token); }
  $token = T_CS("\\".ToString(Tokens(@toks)));
  Let($token, T_CS('\relax')) unless defined LookupMeaning($token);
  $token;  });

DefPrimitive('\endcsname', sub { Error(":unexpected:\\endcsname Extra \\endcsname"); return; });

DefMacro('\expandafter Token Token',sub {
  my($gullet,$tok,$xtok)=@_;
  my $defn;
  if(defined($defn=LookupDefinition($xtok)) && $defn->isExpandable){
    ($tok,$defn->invoke($gullet)); } # Expand $xtok ONCE ONLY!
  else {
    ($tok,$xtok); }});

# Insert magic token that Gullet knows not to expand the next one.
DefMacroI('\noexpand',undef,sub { Token('',CC_NOTEXPANDED); });

DefMacroI('\topmark',        undef, Tokens());
DefMacroI('\firstmark',      undef, Tokens());
DefMacroI('\botmark',        undef, Tokens());
DefMacroI('\splitfirstmark', undef, Tokens());
DefMacroI('\splitbotmark',   undef, Tokens());

DefMacro('\input TeXFileName',sub {
  my($gullet,$path)=@_;
  $path = ToString($path);
  $path = $1 if $path =~ /^\{(.*)\}$/; # just in case
  my ($dir,$name,$type) = pathname_split($path);
  my $newpath;
  if(my $file = FindFile($path)){
    $gullet->input($file); }
  # Possibly we should be trying for a style file (& we may end up in latex mode)
  elsif((! $dir) && (!$type || ($type eq 'tex')) # Attempt to load raw TeX
     && ($newpath=FindFile($name,type=>'sty'))){	# and there IS such a style file
    Info(":override Overriding input of $path with $newpath");
    RequirePackage($name); }
  else {
    $STATE->noteStatus(missing=>$path);
    Error(":missing_file:$path Cannot find $path in paths "
	  .join(', ',@{$STATE->lookupValue('SEARCHPATHS')})); }
  return; });

DefMacroI('\endinput',undef,sub { $_[0]->closeMouth(1); return; });

# \the<internal quantity>
DefMacro('\the Variable',sub {
  my($gullet,$variable)=@_;
  return () unless $variable;
  my($defn,@args) = @$variable;
  my $type = $defn->isRegister;
  if(!$type){ 
    my $cs = ToString($defn->getCS);
    Error(":unexpected:\\the$cs You can't use $cs after \\the"); return (); }
  my $value = $defn->valueOf(@args);
  my @tokens = ($type eq 'Tokens' ? ($value ? $value->unlist :()) : Explode(ToString($value)));
  if($LaTeXML::NOEXPAND_THE){	# See \the for the sense in this.
    $gullet->neutralizeTokens(@tokens); }
  else {
    @tokens; }});

#**********************************************************************
# Primitives
# See The TeXBook, Chapter 24, Summary of Vertical Mode
#  and Chapter 25, Summary of Horizontal Mode.
# Parsing of basic types (pp.268--271) is (mostly) handled in Gullet.pm
#**********************************************************************

#======================================================================
# Registers & Parameters
# See Chapter 24, Summary of Vertical Mode
# Define a whole mess of useless registers here ...
# Values are from Appendix B, pp. 348-349 (for whatever its worth)
#======================================================================

#======================================================================
# Integer registers; TeXBook p. 272-273
our %iparms= (pretolerance=>100, tolerance=>200, hbadness=>1000, vbadness=>1000,
	      linepenalty=>10, hyphenpenalty=>50, exhyphenpenalty=>50,
	      binoppenalty=>700, relpenalty=>500,
	      clubpenalty=>150, widowpenalty=>150, displaywidowpenalty=>50,
	      brokenpenalty=>100, predisplaypenalty=>10000,
	      postdisplaypenalty=>0, interlinepenalty=>0, 
	      floatingpenalty=>0, outputpenalty=>0, 
	      doublehyphendemerits=>10000, finalhyphendemerits=>5000, adjdemerits=>10000,
	      looseness=>0, pausing=>0, 
	      holdinginserts=>0, tracingonline=>0, tracingmacros=>0, tracingstats=>0, 
	      tracingparagraphs=>0, tracingpages=>0, tracingoutput=>0, tracinglostchars=>1,
	      tracingcommands=>0, tracingrestores=>0, language=>0, uchyph=>1, lefthyphenmin=>0,
	      righthyphenmin=>0, globaldefs=>0, defaulthyphenchar=>ord('-'), defaultskewchar=>-1,
	      escapechar=>0, endlinechar=>0, newlinechar=>-1, maxdeadcycles=>0, hangafter=>0, 
	      fam=>-1, mag=>1000, magnification=>1000, delimiterfactor=>0,
	      time=>0, day=>0, month=>0, year=>0,
	      showboxbreadth=>5, showboxdepth=>3, errorcontextlines=>5);
foreach my $p (keys %iparms){
  DefRegister("\\$p",Number($iparms{$p})); }

{  my ($sec,$min,$hour,$mday,$mon,$year)=localtime();
   AssignValue('\day'  =>Number($mday), 'global');
   AssignValue('\month'=>Number($mon+1), 'global');
   AssignValue('\year' =>Number(1900+$year), 'global'); 
   AssignValue('\time' =>Number(60*$hour+$min),'global'); }

our @MonthNames=(qw( January February March April May June
		     July August September October November December));

# Return a string for today's date.
sub today {
  $MonthNames[LookupValue('\month')->valueOf-1]
    ." ".LookupValue('\day')->valueOf
      .', '.LookupValue('\year')->valueOf; }

# Read-only Integer registers
our %ro_iparms=(lastpenalty=>0, inputlineno=>0, badness=>0);
foreach my $p (keys %ro_iparms){
  DefRegister("\\$p",Number($ro_iparms{$p}),readonly=>1); }

# Special integer registers (?)
# <special integer> = \spacefactor | \prevgraf | \deadcycles | \insertpenalties
our %sp_iparms=(spacefactor=>0, prevgraf=>0, deadcycles=>0, insertpenalties=>0);
foreach my $p (keys %sp_iparms){
  DefRegister("\\$p",Number($sp_iparms{$p})); }

#======================================================================
# Dimen registers; TeXBook p. 274
our %dparms=(hfuzz=>'0.1pt', vfuzz=>'0.1pt', overfullrule=>'5pt',
	     emergencystretch=>0,
	     hsize=>'6.5in', vsize=>'8.9in',
	     maxdepth=>'4pt', splitmaxdepth=>'16383.99999pt', boxmaxdepth=>'16383.99999pt',
	     lineskiplimit=>0, 
	     delimitershortfall=>'5pt', nulldelimiterspace=>'1.2pt', scriptspace=>'0.5pt',
	     mathsurround=>0, 
	     predisplaysize=>0, displaywidth=>0, displayindent=>0, parindent=>'20pt',
	     hangindent=>0, hoffset=>0, voffset=>0,);
foreach my $p (keys %dparms){
  DefRegister("\\$p",Dimension($dparms{$p})); }

# Read-only dimension registers.
our %ro_dparms=(lastkern=>0);
foreach my $p (keys %ro_dparms){
  DefRegister("\\$p",Dimension($ro_dparms{$p}), readonly=>1); }


# Special dimension registers (?)
# <special dimen> = \prevdepth | \pagegoal | \pagetotal | \pagestretch | \pagefilstretch
#    | \pagefillstretch | \pagefilllstretch | pageshrink | \pagedepth
our %sp_dparms=(prevdepth=>0, pagegoal=>0, pagetotal=>0, pagestretch=>0, pagefilstretch=>0,
		pagefillstretch=>0, pagefilllstretch=>0, pageshrink=>0, pagedepth=>0);
foreach my $p (keys %sp_dparms){
  DefRegister("\\$p",Dimension($sp_dparms{$p})); }

#======================================================================
# Glue registers; TeXBook p.274
our %gparms=(baselineskip=>0, lineskip=>0, 
	     parskip=>'0pt plus 1pt', 
	     abovedisplayskip=>'12pt plus 3pt minus 9pt',
	     abovedisplayshortskip=>'0pt plus 3pt',
	     belowdisplayskip=>'12pt plus 3pt minus 9pt',
	     belowdisplayshortskip=>'0pt plus 3pt',
	     leftskip=>0, rightskip=>0, topskip=>'10pt', splittopskip=>'10pt',
	     tabskip=>0, spaceskip=>0, xspaceskip=>0, parfillskip=>'0pt plus 1fil');
foreach my $p (keys %gparms){
  DefRegister("\\$p",Glue($gparms{$p})); }

#======================================================================
# MuGlue registers; TeXBook p.274
our %mparms=(thinmuskip=>'3mu', medmuskip=>'4mu plus 2mu minus 4mu', thickmuskip=>'5mu plus 5mu');
foreach my $p (keys %mparms){
  DefRegister("\\$p",Glue($mparms{$p})); }

#======================================================================
# Token registers; TeXBook p.275
our @tparms= qw(output everypar everymath everydisplay everyhbox everyvbox
		everyjob everycr everyhelp);
foreach my $p (@tparms){
  DefRegister("\\$p",Tokens()); }

#======================================================================
# Assignment, TeXBook Ch.24, p.275
#======================================================================
# <assignment> = <non-macro assignment> | <macro assignment>

#======================================================================
# Macros
# See Chapter 24, p.275-276
# <macro assignment> = <definition> | <prefix><macro assignment>
# <definition> = <def><control sequence><definition text>
# <def> = \def | \gdef | \edef | \xdef
# <definition text> = <register text><left brace><balanced text><right brace>

sub parseDefParameters {
  my($cs, $params)=@_;
  my @tokens = $params->unlist;
  # Now, recognize parameters and delimiters.
  my @params=();
  my $n=0;
  while(@tokens){
    my $t=shift(@tokens);
    if($t->getCatcode == CC_PARAM){
      $n++; $t=shift(@tokens); 
	Fatal(":expected:#$n Parameters for \"".ToString($cs)."\" not in order in ".ToString($params))
	  unless $n == (ord($t->getString)-ord('0'));
      # Check for delimiting text following the parameter #n
      my @delim=();
      my($pc,$cc)=(-1,0);
      while(@tokens && (($cc=$tokens[0]->getCatcode) != CC_PARAM)){
	my $d = shift(@tokens);
	push(@delim,$d) unless $cc == $pc && $cc==CC_SPACE; # BUT collapse whitespace!
	$pc = $cc;}
      # Found text that marks the end of the parameter
      if(@delim){ 
	my $expected=Tokens(@delim);
	push(@params, LaTeXML::Parameters::newParameter('Until',
							'Until:'.ToString($expected),
							extra=>[$expected])); }
      
      # Special case: trailing sole # => delimited by next opening brace.
      elsif((scalar(@tokens)==1) && ($tokens[0]->getCatcode == CC_PARAM)){
	shift(@tokens);
	push(@params, LaTeXML::Parameters::newParameter('UntilBrace','UntilBrace')); }
      # Nothing? Just a plain parameter.
      else {
	push(@params, LaTeXML::Parameters::newParameter('Plain','{}')); }}
    else {
      # Initial delimiting text is required.
      my @lit=($t);
      while(@tokens && ($tokens[0]->getCatcode != CC_PARAM)){
	push(@lit,shift(@tokens)); }
      my $expected = Tokens(@lit);
      push(@params,LaTeXML::Parameters::newParameter('Match',
						     'Match:'.ToString($expected),
						     extra=>[$expected],
						     novalue=>1)); }
  }
  (@params ? LaTeXML::Parameters->new(@params) : undef); }

sub do_def {
  my($globally, $expanded, $gullet,$cs,$params,$body)=@_;
  $params = parseDefParameters($cs,$params);
  if($expanded){
    local $LaTeXML::NOEXPAND_THE = 1;
    $body = Expand($body); }
  DefMacroI($cs,$params,$body,($globally ? (scope=>'global'):()));
  return; }

DefPrimitive('\def  SkipSpaces Token UntilBrace {}', sub { do_def(0,0,@_); });
DefPrimitive('\gdef SkipSpaces Token UntilBrace {}', sub { do_def(1,0,@_); });
DefPrimitive('\edef SkipSpaces Token UntilBrace {}', sub { do_def(0,1,@_); });
DefPrimitive('\xdef SkipSpaces Token UntilBrace {}', sub { do_def(1,1,@_); });

# <prefix> = \global | \long | \outer
# See Stomach.pm & Stomach.pm
DefPrimitiveI('\global',undef,sub { $STATE->setPrefix('global'); return; }, isPrefix=>1);
DefPrimitiveI('\long',  undef,sub { $STATE->setPrefix('long');   return; }, isPrefix=>1);
DefPrimitiveI('\outer', undef,sub { $STATE->setPrefix('outer');  return; }, isPrefix=>1);

#======================================================================
# Non-Macro assignments; TeXBook Ch.24, pp 276--277
# <non-macro assignment> = <simple assignment> | \global <non-macro assignment>

# <filler> = <optional spaces> | <filler>\relax<optional spaces>
# <general text> = <filler>{<balanced text><right brace>

# <simple assignment> = <variable assignment> | <arithmetic>
#    | <code assignment> | <let assignment> | <shorthand definition>
#    | <fontdef token> | <family assignment> | <shape assignment>
#    | \read <number> to <optional spaces><control sequence>
#    | \setbox<8bit><equals><filler><box>
#    | \font <control sequence><equals><file name><at clause>
#    | <global assignment>
# <variable assignment> = <integer variable><equals><number>
#    | <dimen variable><equals><dimen>
#    | <glue variable><equals><dimen>
#    | <muglue variable><equals><muglue>
#    | <token variable><equals><general text>
#    | <token variable><equals><token variable>
# <at clause> = at <dimen> | scaled <number> | <optional spaces>
# <code assignment> = <codename><8bit><equals><number>

DefMacro('\read Number SkipKeyword:to Token',sub {
  my($gullet,$fh,$token)=@_;
  DefMacroI($token,undef,Tokens()); });


##our %font_family=(r=>'serif',''=>'serif',ss=>'sansserif',tt=>'typewriter',
##		  vtt=>'typewriter',fib=>'fibonacci',fr=>'funnyroman',
##		  dh=>'dunhill',m=>'math',sy=>'symbol');

# Given a font, we'd like to map it to the "logical" names derived from LaTeX,
# (w/ loss of fine grained control).
# I'd like to use Karl Berry's font naming scheme
# (See http://www.tug.org/fontname/html/)
# but it seems to be a one-way mapping, and moreover, doesn't even fit CM fonts!
# We'll assume a sloppier version:
#   family + series + variant + size

our %font_family=(cmr=>{family=>'serif'},      cmss=>{family=>'sansserif'},
		  cmtt=>{family=>'typewriter'},cmvtt=>{family=>'typewriter'},
		  cmti=>{family=>'typewriter', shape=>'italic'},
		  cmfib=>{family=>'serif'},    cmfr=>{family=>'serif'},
		  cmdh=>{family=>'serif'},     cm=>{family=>'serif'},
		  ptm=>{family=>'serif'},      ppl=>{family=>'serif'},
		  pnc=>{family=>'serif'},      pbk=>{family=>'serif'},
		  phv=>{family=>'sansserif'},  pag=>{family=>'serif'},
		  pcr=>{family=>'typewriter'}, pzc=>{family=>'script'},
		  put=>{family=>'serif'},      bch=>{family=>'serif'},
		  psy=>{family=>'symbol'},     pzd=>{family=>'dingbats'},
		  ccr=>{family=>'serif'},      ccy=>{family=>'symbol'},
		  cmbr=>{family=>'sansserif'}, cmtl=>{family=>'typewriter'},
		  cmbrs=>{family=>'symbol'},   ul9=>{family=>'typewriter'},
		  txr=>{family=>'serif'},      txss=>{family=>'sansserif'},
		  txtt=>{family=>'typewriter'},txms=>{family=>'symbol'},
		  txsya=>{family=>'symbol'},   txsyb=>{family=>'symbol'},
		  pxr=>{family=>'serif'},      pxms=>{family=>'symbol'},
		  pxsya=>{family=>'symbol'},   pxsyb=>{family=>'symbol'},
		  futs=>{family=>'serif'},
		  uaq=>{family=>'serif'},      ugq=>{family=>'sansserif'},
		  eur=>{family=>'serif'},      eus=>{family=>'script'},
		  euf=>{family=>'fraktur'},    euex=>{family=>'symbol'},
		  # The following are actually math fonts.
		  ms=>{family=>'symbol'},
		  ccm=>{family=>'serif', shape=>'italic'},
		  cmex=>{family=>'symbol'}, # Not really symbol, but...
		  cmsy=>{family=>'symbol'},
		  ccitt=>{family=>'typewriter', shape=>'italic'},
		  cmbrm=>{family=>'sansserif', shape=>'italic'},
		  futm=>{family=>'serif', shape=>'italic'},
		  futmi=>{family=>'serif', shape=>'italic'},
		  txmi=>{family=>'serif', shape=>'italic'},
		  pxmi=>{family=>'serif', shape=>'italic'},
		  bbm=>{family=>'blackboard'},
		  bbmss=>{family=>'blackboard'},
		  );

our %font_series=(''=>'medium',m=>'medium', mc=>'medium', 
		  b=>'bold',bc=>'bold', bx=>'bold', sb=>'bold', sbc=>'bold', bm=>'bold');
our %font_shape =(''=>'upright',n=>'upright', i=>'italic',it=>'italic',
		  sl=>'slanted', sc=>'smallcaps', csc=>'smallcaps');

our $FONTREGEXP
  = '('.join('|',sort { -($a cmp $b) } keys %font_family).')'
  . '('.join('|',sort { -($a cmp $b) } keys %font_series).')'
  . '('.join('|',sort { -($a cmp $b) } keys %font_shape).')'
  . '(\d*)';
  
# Decode a font size in points into a "logical" size.
our @font_size_map=(0.60=>'tiny',   0.75=>'script', 0.85=>'footnote',0.95=>'small',
		    1.10=>'normal', 1.30=>'large',  1.55=>'Large',   1.85=>'LARGE',
		    2.25=>'huge',   1000.0=>'Huge');
sub abstract_font_size {
  my($size)=@_;
  if(defined $size){
    my $scaled = $size/10.0;	# ASSUMED nominal font 10pt!!! Set from doc!!!
    my @map = @font_size_map;
    while(@map){
      return shift(@map) if($scaled <= shift(@map));
      shift(@map); }
    'Huge'; }
  else {
    'normal'; }}

sub lookupFontFamily { $font_family{ToString($_[0])}; }
sub lookupFontSeries { $font_series{ToString($_[0])}; }
sub lookupFontShape  { $font_shape{ToString($_[0])}; }

# Need to handle "at" too!!!
DefPrimitive('\font Token SkipMatch:= SkipSpaces TeXFileName', sub {
  my($stomach,$cs,$name)=@_;
  my $gullet = $stomach->getGullet;
  $name = ToString($name);
  my %props=();
  my $size = 1;
  if($name =~ /^$FONTREGEXP$/o){
    my($fm,$sr,$sh)=($1,$2,$3);
    $size = $4;
    %props = %{$font_family{$fm}||{}};
    $props{series}= $font_series{$sr} unless $props{series};
    $props{shape} = $font_shape{$sh}  unless $props{shape}; }
  else {
    Info(":unexpected:$name Unrecognized font \"$name\"; ".ToString($cs)." will have no effect"); }

  if($gullet->readKeyword('at')){
    $size = $gullet->readDimension->ptValue; }
  if($gullet->readKeyword('scaled')){
    $size *= ($gullet->readNumber->valueOf/1000); }
  $props{size}  = abstract_font_size($size);
  $gullet->skipSpaces;
##  print STDERR "Font $name == ".join(', ',map("$_=>$props{$_}",sort keys %props))."\n";
  DefConstructorI($cs,undef,'',font=>{%props});

});

DefRegister('\count Number' => Number(0));
DefRegister('\dimen Number' => Dimension(0));
DefRegister('\skip Number'  => Glue(0));
DefRegister('\muskip Number'=> MuGlue(0));
DefRegister('\toks Number'  => Tokens());

# <integer variable> = <integer parameter> | <countdef token> | \count<8bit>
# <dimen var> = <dimen parameter> | <dimendef token> | \dimen<8bit>
# <glue variable> = <glue parameter> | <skipdef token> | \skip<8bit>
# <muglue variable> = <muglue parameter> | <muskipdef token> | \muskip<8bit>

# <arithmetic> = \advance <integer variable><optional by><number>
#    | \advance <dimen variable><optional by><dimen>
#    | \advance <glue variable><optional by><glue>
#    | \advance <muglue variable><optional by><muglue>
#    | \multiply <numeric variable><optional by><number>
#    | \divide <numeric variable><optional by><number>

DefPrimitive('\advance Variable SkipKeyword:by',sub {
  my($stomach,$var)=@_;
  return () unless $var;
  my($defn,@args)=@$var;
  $defn->setValue($defn->valueOf(@args)->add($stomach->getGullet->readValue($defn->isRegister)),@args); });

DefPrimitive('\multiply Variable SkipKeyword:by Number',sub {
  my($stomach,$var,$scale)=@_;
  return () unless $var;
  my($defn,@args)=@$var;
  $defn->setValue($defn->valueOf(@args)->multiply($scale->valueOf),@args); });

DefPrimitive('\divide Variable SkipKeyword:by Number',sub {
  my($stomach,$var,$scale)=@_;
  return () unless $var;
  my($defn,@args)=@$var;
  my $denom = $scale->valueOf;
  if($denom == 0){
    Error(":misdefined:$scale Illegal \\divide by 0; assuming 1");
    $denom = 1; }
  $defn->setValue($defn->valueOf(@args)->multiply(1/$denom),@args); });

# <let assignment> = \futurelet <control sequence><token><token> 
#   | \let<control sequence><equals><one optional space><token>
DefPrimitive('\let Token SkipMatch:= SkipSpaces Token', sub {
  my($stomach,$token1,$token2)=@_;
  Let($token1, $token2);
  return;  });

DefMacro('\futurelet Token Token Token',sub{
  my($stomach,$cs,$token1,$token2)=@_;
  Let($cs, $token2);
  ($token1,$token2); });

# <shorthand definition> = \chardef<control sequence><equals><8bit>
#    | \mathchardef <control sequence><equals><15bit>
#    | <registerdef><control sequence><equals><8bit>
# <registerdef> = \countdef | \dimendef | \skipdef | \muskipdef | toksdef

# Almost like a register, but different...
DefPrimitive('\chardef Token SkipMatch:= Number', sub {
  my($stomach,$newcs,$value)=@_;
  $STATE->installDefinition(LaTeXML::CharDef->new($newcs,$value));
  return; });

# \mathchardef ?

DefPrimitive('\countdef Token SkipMatch:= Number',sub {
  my($stomach,$cs,$num)=@_;
  my $count = '\count'.$num->valueOf;
  DefRegisterI($cs,undef, Number(0),
	       getter=>sub { LookupValue($count); },
	       setter=>sub { AssignValue($count=>$_[0]); }); });

DefPrimitive('\dimendef Token SkipMatch:= Number',sub {
  my($stomach,$cs,$num)=@_;
  my $dimen = '\dimen'.$num->valueOf;
  DefRegisterI($cs,undef,Dimension(0),
	       getter=>sub { LookupValue($dimen); },
	       setter=>sub { AssignValue($dimen=>$_[0]); }); });

DefPrimitive('\skipdef Token SkipMatch:= Number',sub {
  my($stomach,$cs,$num)=@_;
  my $glue = '\skip'.$num->valueOf;
  DefRegisterI($cs,undef, Glue(0),
	       getter=>sub { LookupValue($glue); },
	       setter=>sub { AssignValue($glue=>$_[0]); }); });

DefPrimitive('\muskipdef Token SkipMatch:= Number',sub {
  my($stomach,$cs,$num)=@_;
  my $muglue = '\muskip'.$num->valueOf;
  DefRegisterI($cs,undef, MuGlue(0),
	       getter=>sub { LookupValue($muglue); },
	       setter=>sub { AssignValue($muglue=>$_[0]); }); });

DefPrimitive('\toksdef Token SkipMatch:= Number',sub {
  my($stomach,$cs,$num)=@_;
  my $toks = '\toks'.$num->valueOf;
  DefRegisterI($cs,undef, Tokens(),
	       getter=>sub { LookupValue($toks); },
	       setter=>sub { AssignValue($toks=>$_[0]); }); });


# NOTE: Get all these handled as registers
# <internal integer> = <integer parameter> | <special integer> | \lastpenalty
#   | <countdef token> | \count<8bit> | <codename><8bit>
#   | <chardef token> | <mathchardef token> | \parshape | \inputlineno
#   | \hyphenchar<font> | \skewchar<font> | \badness

DefRegister('\lastpenalty',Number(0), readonly=>1);

# \parshape !?!??

#DefRegister('\inputlineno',Number(0),
#            readonly=>1,
#	     getter=>{ Number($stomach->getGullet->getMouth????? ->lineno); });

# \skewchar ???

DefRegister('\badness',Number(0), readonly=>1);

# <codename> = \catcode | \mathcode | \lccode | \uccode | \sfcode | \delcode

DefRegister('\catcode Number',Number(0),
	     getter=>sub { my $cc = LookupCatcode(chr($_[0]->valueOf));
			   $cc = CC_OTHER unless defined $cc;
			   Number($cc); },
	     setter=>sub { AssignCatcode(chr($_[1]->valueOf) => $_[0]->valueOf); });
# Stub definitions ???
DefRegister('\mathcode Number',Number(0));
DefRegister('\sfcode Number',Number(0));
DefRegister('\lccode Number',Number(0));
DefRegister('\uccode Number',Number(0));
DefRegister('\delcode Number',Number(0));
DefRegister('\hyphenchar{}',Number(ord('-')));
DefRegister('\skewchar{}',Number(0)); # no idea what the default is here

DefMacro('\hyphenation{}', Tokens()); # Well, what ?


# <font> = <fontdef token> | \font | <family member>
# <family member> | <font range><4bit>
# <font range> = \textfont | \scriptfont | \scriptscriptfont

# Doubtful that we can do anything useful with these.
DefMacro('\textfont Number SkipMatch:= Token', sub { return; });
DefMacro('\scriptfont Number SkipMatch:= Token', sub { return; });
DefMacro('\scriptscriptfont Number SkipMatch:= Token', sub { return; });

# <internal dimen> = <dimen parameter> | <special dimen> | \lastkern
#    | <dimendef token> | \dimen<8bit> | <box dimension><8bit> | \fontdimen<number><font>

DefRegister('\lastkern'=>Dimension(0), readonly=>1);

# <box dimension> = \ht | \wd | \dp

# Need some clever getter & setters!
DefRegister('\ht Number',Dimension(0));
DefRegister('\wd Number',Dimension(0));
DefRegister('\dp Number',Dimension(0));

#   Could be handled by setting dimensions whenever the box itself is set?

# <internal glue> = <glue parameter> | \lastskip | <skipdef token> | \skip<8bit>

DefRegister('\lastskip'=>Glue(0), readonly=>1);

# <internal muglue> = <muglue parameter> | \lastskip | <muskipdef token> | \muskip<8bit>

# <family assignment> = <family member><equals><font>
# <shape assignment> = \parshape<equals><number><shape dimensions>
#  <shape dimensions> is 2n <dimen>

# <global assignment> = <font assignment> | <hyphenation assignment>
#   | <box size assignment> | <interaction mode assignment>
#   | <intimate assignment>
# <font assignment> = \fontdimen <number><font><equals><dimen>
#   | \hyphenchar<font><equals><number> | \skewchar<font><equals><number>
# <hyphenation assignment> = \hyphenation<general text>
#   | \patterns<general text>
# <box size assignment> = <box dimension><8bit><equals><dimen>
# <interaction mode assignment> = \errorstopmode | \scrollmode | \nonstopmode | \batchmode
# These are no-ops; Basically, LaTeXML runs in scrollmode
DefPrimitiveI('\errorstopmode', undef,undef);
DefPrimitiveI('\scrollmode',    undef,undef);
DefPrimitiveI('\nonstopmode',   undef,undef);
DefPrimitiveI('\batchmode',     undef,undef);

# <intimate assignment> = <special integer><equals><number>
#   | <special dimension><equals><dimen>

DefPrimitive('\char Number', sub { $_[0]->invokeToken(T_OTHER(chr($_[1]->valueOf))); });
# <box> = \box <8bit> | \copy <8bit> | \lastbox | \vsplit <8bit> to <dimen>
#   | \hbox <box specification>{<horizontal mode material>}
#   | \vbox <box specification>{<vertical mode material>}
#   | \vtop <box specification>{<vertical mode material>}
# <box specification> = to <dimen><filler> | spread <dimen><filler> | <filler>

# \setbox<number>=\hbox to <dimen>{<horizontal mode material>}

DefPrimitive('\setbox Number SkipMatch:=', sub { 
  my($stomach)=@_;
  my @stuff = $stomach->invokeToken($stomach->getGullet->readXToken);
  AssignValue('box'.$_[1]->valueOf => shift(@stuff));
  @stuff;});
# Is this the same ??
DefPrimitive('\sbox Number SkipMatch:= {}', sub { 
  my($stomach)=@_;
  my @stuff = $stomach->invokeToken($stomach->getGullet->readXToken);
  AssignValue('box'.$_[1]->valueOf => shift(@stuff));
  @stuff;});

DefPrimitive('\box Number', sub { LookupValue('box'.$_[1]->valueOf) || (); });

sub revert_spec {
  my($whatsit,$keyword)=@_;
  my $value = $whatsit->getProperty($keyword);
  ($value ? (Explode($keyword),$value->revert): ()); }

DefParameterType('BoxSpecification',sub {
  my($gullet)=@_;
  if(my $key = $gullet->readKeyword('to','spread')){
    LaTeXML::BoxDimensions->new($key=>$gullet->readDimension); }},
		 optional=>1, undigested=>1);

# Risky: I think this needs to be digested as a body to work like TeX (?)
# but parameter think's it's just parsing from gullet...
DefParameterType('BoxContents',sub {
  my($gullet)=@_;
  my $t;
  while(($t=$gullet->readToken) && !Equals($t,T_BEGIN)){} # Skip till { or \bgroup
  my($contents,@stuff) = $STATE->getStomach->invokeToken(T_BEGIN);
  $contents; },
		 undigested=>1); # Cause it already is digested!

sub REF { $_[0] && $_[0]->{$_[1]}; }

DefConstructor('\hbox BoxSpecification BoxContents',
	       "<ltx:text width='&REF(#1,to)' pad-width='&REF(#1,spread)' _noautoclose='1'>#2</ltx:text>",
 	       mode=>'text',
 	       # Workaround for $ in alignment; an explicit \hbox gives us a normal $.
 	       # And also things like \centerline that will end up bumping up to block level!
 	       beforeDigest=>sub { Let(T_MATH,T_CS('\@text@alignment$'));
 				   Let(T_CS('\centerline'),T_CS('\relax')); });


# SHOULD Be a little smarter: if we're already in a block level context, don't open inline-block
# but that doesn't really quite capture the logic....
DefConstructor('\vbox BoxSpecification BoxContents', 
 "<ltx:inline-block vattach='bottom' height='&REF(#1,to)' pad-height='&REF(#1,spread)'>#2</ltx:inline-block>",
# sub {		 my($document,$spec,$content)=@_;
# 		 if($document->isOpenable('ltx:block')){ # If already at block level
# 		   $document->absorb($content); }
# 		 else {
# 		   $document->insertElement('ltx:inline-block',
# 					    $content,
# 					    vattach=>'bottom',
# 					    height=>$spec && $$spec{height},
# 					    'pad-height'=>$spec && $$spec{spread}); }},
 	       mode=>'text');

DefConstructor('\vtop BoxSpecification BoxContents', 
 "<ltx:inline-block vattach='top' height='&REF(#1,to)' pad-height='&REF(#1,spread)'>#2</ltx:inline-block>",
# sub {		 my($document,$spec,$content)=@_;
# 		 if($document->isOpenable('ltx:block')){ # If already at block level
# 		   $document->absorb($content); }
# 		 else {
# 		   $document->insertElement('ltx:inline-block',
# 					    $content,
# 					    vattach=>'top',
# 					    height=>$spec && $$spec{height},
# 					    'pad-height'=>$spec && $$spec{spread}); }},
	       mode=>'text');

sub read_rule_spec {
  my($stomach,$whatsit)=@_;
  my $gullet = $stomach->getGullet;
  while(my $key = $gullet->readKeyword('height','depth','width')){
    $whatsit->setProperty($key=>$gullet->readDimension); }
  return; }

DefConstructorI('\vrule', undef,"<ltx:rule height='#height' depth='#depth' width='#width'/>",
	       afterDigest=>sub { read_rule_spec(@_); },
	       properties=>{isVerticalRule=>1},
	       reversion=>sub { (T_CS('\vbox'),revert_spec($_[0],'height'),
				 revert_spec($_[0],'depth'),revert_spec($_[0],'width'));});

DefConstructorI('\hrule', undef,"<ltx:rule height='#height' depth='#depth' width='#width'/>",
	       afterDigest=>sub { read_rule_spec(@_); },
	       properties=>{isHorizontalRule=>1},
	       reversion=>sub { (T_CS('\hbox'),revert_spec($_[0],'height'),
                                revert_spec($_[0],'depth'),revert_spec($_[0],'width'));});

#======================================================================
# Remaining Mode independent primitives in Ch.24, pp.279-280
# \relax was done as expandable (isn't that right?)
# }
# Note, we don't bother making sure begingroup is ended by endgroup.

# These define the handler for { } (or anything of catcode BEGIN, END)

# These are actually TeX primitives, but we treat them as a Whatsit so they
# remain in the constructed tree.
DefConstructor('{','#body', beforeDigest=>sub{$_[0]->bgroup;}, captureBody=>1);
DefConstructor('}',  '',    beforeDigest=>sub{$_[0]->egroup;});

# These are for those screwy cases where you need to create a group like box,
# more than just bgroup, egroup,
# BUT you DON'T want extra {, } showing up in any untex-ing.
DefConstructor('\@hidden@bgroup','#body', beforeDigest=>sub{$_[0]->bgroup;}, captureBody=>1,
	       reversion=>sub { $_[0]->getProperty('body')->revert; });
DefConstructor('\@hidden@egroup','',afterDigest=>sub { $_[0]->egroup; },
	      reversion=>'');

DefPrimitive('\begingroup',sub { $_[0]->begingroup; });
DefPrimitive('\endgroup',  sub { $_[0]->endgroup; });

# Debugging aids; Ignored!
DefPrimitive('\show Token',undef);
DefPrimitive('\showbox Number',undef);
DefPrimitive('\showlists',undef);
DefPrimitive('\showthe Token',undef);

# DefPrimitive('\shipout ??
DefPrimitive('\ignorespaces SkipSpaces',undef);

# \afterassignment<token>   ??
DefPrimitive('\aftergroup Token', sub { UnshiftValue(afterGroup=>$_[1]); });

# \uppercase<general text>, \lowercase<general text>
DefMacro('\uppercase {}',sub {
  my($gullet,$arg)=@_;
  map( ($_->getCharcode == CC_LETTER ? T_LETTER(uc($_->getString)):$_),
       ($arg ? $arg->unlist :())); });
DefMacro('\lowercase {}',sub {
  my($gullet,$arg)=@_;
  map( ($_->getCharcode == CC_LETTER ? T_LETTER(lc($_->getString)):$_),
       ($arg ? $arg->unlist :())); });

DefPrimitive('\message{}',sub {
  my($stomach,$stuff)=@_;
  print STDERR ToString(Expand($stuff));
  return; });

## I'm thinking we want to mostly ignore the I/O stuff.
## Generally, it's used to move info around for multiple passes
## of aux, index, etc.
DefPrimitive('\openin Number SkipMatch:= TeXFileName', undef);
DefPrimitive('\openout Number SkipMatch:= TeXFileName', undef);
DefPrimitive('\closein Number',undef);
DefPrimitive('\closeout Number',undef);
DefPrimitive('\immediate',undef);
# \immediate\write<number><general text>
DefPrimitive('\write Number {}', undef);

#======================================================================
# Remaining semi- Vertical Mode primitives in Ch.24, pp.280--281

DefPrimitive('\special {}', undef);
DefPrimitive('\penalty Number',undef);
DefPrimitive('\kern Dimension',undef);
DefPrimitive('\mkern MuGlue',undef);
DefPrimitiveI('\unpenalty',undef,undef);
DefPrimitiveI('\unkern',   undef,undef);
DefPrimitiveI('\unskip',   undef,undef);
DefPrimitive('\mark{}',undef);
# \insert<8bit><filler>{<vertical mode material>}
DefPrimitive('\insert Number',undef); # Just let the insertion get processed(?)
# \vadjust<filler>{<vertical mode material>}
DefPrimitive('\vadjust {}',undef); # Ignorable?

#======================================================================
# Remaining Vertical Mode primitives in Ch.24, pp.281--283
# \vskip<glue>, \vfil, \vfill, \vss, \vfilneg
# <leaders> = \leaders | \cleaders | \xleaders
# <box or rule> = <box> | <vertical rule> | <horizontal rule>
# <vertical rule> = \vrule<rule specification>
# <horizontal rule> = \hrule<rule specification>
# <rule specification> = <optional spaces> | <rule dimension><rule specification>
# <rule dimension> = width <dimen> | height <dimen> | depth <dimen>

# Stuff to ignore for now...
foreach my $op ('\vskip Glue', '\vfil', '\vfill', '\vss', '\vfilneg',
		'\leaders', '\cleaders', '\xleaders'){
  DefPrimitive($op,undef); }

# \moveleft<dimen><box>, \moveright<dimen><box>
DefConstructor('\moveleft Dimension MoveableBox',
	       "<ltx:text xoffset='#x' _noautoclose='1'>#2</ltx:text>",
	       afterDigest=>sub{
		 $_[1]->setProperty(x=>$_[1]->getArg(1)->multiply(-1)); });
DefConstructor('\moveright Dimension MoveableBox',
	       "<ltx:text xoffset='#x' _noautoclose='1'>#2</ltx:text>",
	       afterDigest=>sub{
		 $_[1]->setProperty(x=>$_[1]->getArg(1)); });

# \unvbox<8bit>, \unvcopy<8bit>
DefMacro('\unvbox Number',sub {
  my $box = 'box'.$_[1]->valueOf;
  my $stuff = LookupValue($box); 
  AssignValue($box,undef);
  $stuff; });
DefMacro('\unvcopy Number',sub {
  LookupValue('box'.$_[1]->valueOf); });

#======================================================================
# Basic alignment support needed by most environments & commands.
#======================================================================

Tag('ltx:td',        afterClose=>\&trimNodeWhitespace);

#----------------------------------------------------------------------
# Primitive column types;
# This is really LaTeX, but the mechanisms are used behind-the-scenes here, too.
DefColumnType('|',sub {
  $LaTeXML::BUILD_TEMPLATE->addBetween(T_CS('\vrule')); return; });
DefColumnType('l',sub {
  $LaTeXML::BUILD_TEMPLATE->addColumn(after=>Tokens(T_CS('\hfil'))); return; });
DefColumnType('c',sub {
  $LaTeXML::BUILD_TEMPLATE->addColumn(before=>Tokens(T_CS('\hfil')),
						after=>Tokens(T_CS('\hfil'))); return; });
DefColumnType('r',sub {
  $LaTeXML::BUILD_TEMPLATE->addColumn(before=>Tokens(T_CS('\hfil'))); return; });

DefColumnType('p{Dimension}',sub {
  $LaTeXML::BUILD_TEMPLATE->addColumn(before=>Tokens(T_CS('\hbox'),T_BEGIN),#Tokens(T_BEGIN),
				      after=>Tokens(T_END),
				      align=>'justify', width=>$_[1]); return; });
DefColumnType('*{Number}{}', sub {
		my($gullet,$n,$pattern)=@_;
		map( $pattern->unlist, 1..$n->valueOf); });

DefColumnType('@{}',sub {
  my($gullet,$filler)=@_;
  $LaTeXML::BUILD_TEMPLATE->addBetween($filler->unlist);  return; });

#----------------------------------------------------------------------
DefMacroI('\@start@alignment',undef,
	  '\@open@alignment\@open@row\@open@column\@open@inner@column');
DefMacroI('\@finish@alignment',undef,
	  '\@close@inner@column\@close@column\@close@row\@close@alignment');

#----------------------------------------------------------------------
# These are to be bound to &, \span, \cr and \\
# The macro layer expands into appropriate begin & end markers for rows & columns;
# The constructor layer carries out any side effect and records a token for reversion.
DefMacroI('\@alignment@align',undef,
	  '\@close@inner@column\@close@column'
	  .'\@alignment@align@marker'
	  .'\@open@column\@open@inner@column');
DefConstructorI('\@alignment@align@marker',undef,'',reversion=>'&');

#DefMacro('\@alignment@span',
DefMacroI('\span',undef,
	  '\@close@inner@column'
	  .'\@alignment@span@marker'
	  .'\@open@inner@column');
DefConstructorI('\@alignment@span@marker',undef,'',reversion=>'\span',
	      properties=>{alignmentSkippable=>1});
DefConstructorI('\omit',undef,'',properties=>{alignmentSkippable=>1});

DefMacroI('\@alignment@cr',undef,
	  '\@close@inner@column\@close@column\@close@row'
	  .'\@alignment@cr@marker'
	  .'\@open@row\@open@column\@open@inner@column');
DefConstructorI('\@alignment@cr@marker',undef,'',reversion=>'\cr');
DefConstructorI('\default@cr',undef, "\n"); # Default binding.
Let(T_CS('\cr'),  T_CS('\default@cr'));
Let(T_CS('\crcr'),T_CS('\cr'));

# NOTE that this does NOT skip spaces before * or []!!!!!
#  As if: \@alignment@newline OptionalMatch:* [Dimension]
sub readNewlineArgs {
  my($gullet)=@_;
  my $next = $gullet->readToken;
  my($star,$optional);
  if($next && $next->equals(T_OTHER('*'))){
    $star = 1;
    $next = $gullet->readToken; }
  if($next && $next->equals(T_OTHER('['))){
    $optional = $gullet->readUntil(T_OTHER(']')); 
    $next = undef; }
  $gullet->unread($next) if $next; 
  ($star,$optional); }

DefMacroI('\@alignment@newline', undef,sub {
  my($gullet)=@_;
  readNewlineArgs($gullet);
  (T_CS('\@close@inner@column'), T_CS('\@close@column'), T_CS('\@close@row'),
   T_CS('\@alignment@newline@marker'),
   T_CS('\@open@row'), T_CS('\@open@column'), T_CS('\@open@inner@column')); });

DefConstructorI('\@alignment@newline@marker',undef,'',reversion=>Tokens(T_CS("\\\\"),T_CR));

DefConstructorI('\@alignment@hline',undef,'',
	       afterDigest=>sub { LookupValue('Alignment')->addLine('t'); },
	       properties=>{isHorizontalRule=>1},
	       alias=>'\hline');
DefConstructorI('\@alignment@hrule',undef,'',
	       afterDigest=>sub { LookupValue('Alignment')->addLine('t'); },
	       properties=>{isHorizontalRule=>1},
	       alias=>'\hrule');
DefConstructorI('\@alignment@vrule',undef,'',
	       properties=>{isVerticalRule=>1},
	       alias=>'\vrule');

# Special forms for $ appearing within alignments.
# Note that $ within a math alignment (eg array environment),
# switches to text mode! There's no $$ for display math.

# This is the "normal" case: $ appearing with an alignment that is in text mode.
# It's just like regular $, except it doesn't look for $$ (no display math).
DefPrimitiveI('\@text@alignment$', undef, sub {
  $_[0]->invokeToken(T_CS((LookupValue('IN_MATH') ?'\@@ENDINLINEMATH':'\@@BEGININLINEMATH'))); });

# This one is for $ appearing within an alignment that's already math.
# This should switch to text mode (because it's balancing the hidden $
# wrapping each alignment cell!!!!!!)
# However, it should be like a normal $ if it's inside something like \mbox
# that itself makes a text box!!!!!!
# Thus, we need to know at what boxing level we started the last math or text.
# This is all complicated by the need to know _how_ we got into or out of math mode!
# Gawd, this is awful!
# NOTE: Probably the most "Right" thing to do would be to process
# alignments in text mode only (like TeX), sneaking $'s in where needed,
# but then afterwards, morph them into math arrays?
# This would be complicated by the need to hide these $ from untex.
DefPrimitiveI('\@math@alignment$', undef, sub {
  my($stomach)=@_;
  my $level = $stomach->getBoxingLevel;
  if((LookupValue('MATH_ALIGN_$_BEGUN')||0) == $level){ # If we're begun making _something_ with $.
      my @l=();
    if(LookupValue('IN_MATH')){	# But we're somehow in math?
      @l = $stomach->invokeToken(T_CS('\@@ENDINLINEMATH')); }
    else {
      @l = $stomach->invokeToken(T_CS('\@@ENDINLINETEXT')); }
      AssignValue('MATH_ALIGN_$_BEGUN'=>0); # Reset this AFTER finishing the something
      @l; }
  else {
    AssignValue('MATH_ALIGN_$_BEGUN'=>$level+1); # Note that we've begun something
    if(LookupValue('IN_MATH')){	# If we're "still" in math
      $stomach->invokeToken(T_CS('\@@BEGININLINETEXT')); }
    else {
      $stomach->invokeToken(T_CS('\@@BEGININLINEMATH')); }}});

DefConstructorI('\@@BEGININLINETEXT',undef,
	       "<ltx:XMText>"
	       .  "#body"
	       ."</ltx:XMText>",
	       alias=>'$', beforeDigest=> sub{ $_[0]->beginMode('text'); }, captureBody=>1);
DefConstructorI('\@@ENDINLINETEXT',undef  ,"", alias=>'$',
	       beforeDigest=> sub{ $_[0]->endMode('text');});

# ????
DefPrimitiveI('\noalign', undef,undef);
DefMacroI('\hidewidth',   undef, Tokens());

DefMacro('\multispan{Number}', sub {
  my($gullet,$span)=@_;
  $span = $span->valueOf;
  (T_CS('\omit'),map((T_CS('\span'),T_CS('\omit')), 1..$span-1)); });

DefRegisterI('\@alignment@ncolumns',undef,Dimension(0),
	       getter=>sub { Number(scalar(LookupValue('Alignment')->getTemplate->columns)); });
DefRegisterI('\@alignment@column',undef,Dimension(0),
	       getter=>sub { Number(LookupValue('Alignment')->currentColumnNumber); });

DefMacro('\@multicolumn {Number}  AlignmentTemplate {}', sub {
  my($gullet,$span,$template,$tokens)=@_;
  my $column = $template->column(1);
  $span = $span->valueOf;
  # First part, like \multispan
  (T_CS('\omit'),map((T_CS('\span'),T_CS('\omit')), 1..$span-1),
   # Next part, just put the template in-line, since it's only used once.
   ($column ? beforeCellUnlist($$column{before}) : ()),
   $tokens->unlist,
   ($column ? afterCellUnlist($$column{after}) : ()) ); });

DefMacroI('\if@in@alignment', undef,sub { ifHandler($_[0],LookupValue('Alignment')); }, isConditional=>1);

sub alignmentBindings {
  my($template,$mode)=@_;
  if($template && !ref $template){
    $template = parseAlignmentTemplate($template); }
  $mode = LookupValue('MODE') unless $mode;
  my $ismath = $mode =~/math$/;
  my $container = ($ismath ? 'ltx:XMArray' : 'ltx:tabular');
  my $rowtype   = ($ismath ? 'ltx:XMRow'   : 'ltx:tr');
  my $coltype   = ($ismath ? 'ltx:XMCell'  : 'ltx:td');
#  my $alignment = LaTeXML::Util::Alignment->new(containerElement=>$container,
#						rowElement=>$rowtype,
#						colElement=>$coltype);
  my $alignment = LaTeXML::Util::Alignment
    ->new(openContainer =>sub{ $_[0]->openElement($container,@_[1..$#_]); },
	  closeContainer=>sub{ $_[0]->closeElement($container); },
	  openRow       =>sub{ $_[0]->openElement($rowtype,@_[1..$#_]); },
	  closeRow      =>sub{ $_[0]->closeElement($rowtype); },
	  openColumn    =>sub{ $_[0]->openElement($coltype,@_[1..$#_]); },
	  closeColumn   =>sub{ $_[0]->closeElement($coltype); });
  AssignValue(Alignment=>$alignment);
  $alignment->setTemplate($template) if $template;
  Let(T_ALIGN,        T_CS('\@alignment@align'));
  Let(T_CS("\\\\"),   T_CS('\@alignment@newline'));
  Let(T_CS('\tabularnewline'),   T_CS('\@alignment@newline'));
  Let(T_CS('\cr'),    T_CS('\@alignment@cr'));
  Let(T_CS('\crcr'),  T_CS('\@alignment@cr'));
  Let(T_CS('\hline'), T_CS('\@alignment@hline'));
  Let(T_CS('\hrule'), T_CS('\@alignment@hrule'));
  Let(T_CS('\vrule'), T_CS('\@alignment@vrule'));
  Let(T_MATH,  ($ismath ? T_CS('\@math@alignment$') : T_CS('\@text@alignment$')));
  Let(T_CS('\@open@row'),T_CS('\default@open@row'));
  Let(T_CS('\@close@row'),T_CS('\default@close@row'));

  return; }

DefPrimitive('\@alignment@bindings AlignmentTemplate []',sub {
  my($stomach,$template,$mode)=@_;
  alignmentBindings($template,$mode); });

#----------------------------------------------------------------------
# To recognize where rows & columns start and stop, we need to
# recognize things that have expanded into &, \cr, etc.
# Additionally, \span creates a single column out of several.

#----------------------------------------------------------------------
# Overall Alignment;
DefPrimitive('\@close@alignment',sub { $_[0]->egroup;} );
DefConstructor('\@open@alignment SkipSpaces DigestedBody',
	       "#1",
	       reversion=>'#1',
	       beforeDigest=>sub {$_[0]->bgroup; },
	       afterDigest=>sub { $_[1]->setProperty(alignment=>LookupValue('Alignment')); });

#----------------------------------------------------------------------
# Row; The content is stuffed into the Alignment, so we don't construct anything here.
DefPrimitive('\default@close@row',sub { $_[0]->egroup;} );
DefConstructor('\default@open@row SkipSpaces DigestedBody',
	       "",
	       reversion=>'#1',
	       beforeDigest=>sub { $_[0]->bgroup; LookupValue('Alignment')->newRow; return;});

#----------------------------------------------------------------------
# Column
# Here, a column represents 1 or more "inner columns".
# inner columns can be separated by \span's yielding a single column with
# rowspan > 1. Also, inner columns recognize \omit which removes the
# before & after tokens which would otherwise wrap the inner column.

DefPrimitiveI('\@tabular@begin@heading', undef,sub { AssignValue(IN_TABULAR_HEAD=>1,'global'); });
DefPrimitiveI('\@tabular@end@heading',   undef,sub { AssignValue(IN_TABULAR_HEAD=>0,'global'); });

DefPrimitiveI('\@close@column',  undef,sub { $_[0]->egroup;} );
DefPrimitive('\@open@column SkipSpaces DigestedBody', sub {
  my($stomach,$boxes)=@_;
  my $alignment = LookupValue('Alignment');
  my $n0 = LookupValue('alignmentStartColumn')+1;
  my $n1 = $alignment->currentColumnNumber;
  my $colspec = $alignment->getColumn($n0);
  my $align=  $$colspec{align} || 'left';
  my $border= '';
  # Peel off any boxes from both sides until we get the "meat" of the column.
  # from this we can establish borders, alignment and emptiness.
  # But we, of course, immediately put them back...
  my @boxes = $boxes->unlist;
  my @saveleft=();
  my @saveright=();
  while(@boxes){
    if($boxes[0]->getProperty('isFill')){
      $align='right'; shift(@boxes); last; }
    elsif($boxes[0]->getProperty('isVerticalRule')){
      $border .= 'l'; shift(@boxes); }
    elsif($boxes[0]->getProperty('isHorizontalRule')){
      push(@saveleft,shift(@boxes)); }
    elsif($boxes[0]->getProperty('alignmentSkippable')){
      push(@saveleft,shift(@boxes)); }
    elsif(ref $boxes[0] eq 'LaTeXML::Comment'){
      push(@saveleft,shift(@boxes)); }
#    elsif($boxes[0]->getProperty('isSpace')){
    elsif(isEmpty($boxes[0])){
      shift(@boxes);}
    else {
      last; }}
  while(@boxes){
    if($boxes[$#boxes]->getProperty('isFill')){
      if($align eq 'right'){ $align='center'; }
      pop(@boxes); last; }
    elsif($boxes[$#boxes]->getProperty('isVerticalRule')){
      $border .= 'r'; pop(@boxes); }
    elsif($boxes[$#boxes]->getProperty('isHorizontalRule')){
      unshift(@saveright,pop(@boxes)); }
    elsif($boxes[$#boxes]->getProperty('alignmentSkippable')){
      unshift(@saveright,pop(@boxes)); }
    elsif(ref $boxes[$#boxes] eq 'LaTeXML::Comment'){
      unshift(@saveleft,pop(@boxes)); }
#    elsif($boxes[$#boxes]->getProperty('isSpace')){
    elsif(isEmpty($boxes[$#boxes])){
      pop(@boxes);}
    else {
      last; }}
  delete $$colspec{width} unless $align eq 'justify';
  my $empty = scalar(@boxes) == 0;
  $align = undef if $empty;
  @boxes = (@saveleft,@boxes,@saveright);
  $boxes = (scalar(@boxes) == 1 ? $boxes[0] 
	    : ($boxes->isMath ? LaTeXML::MathList->new(@boxes) : LaTeXML::List->new(@boxes)));

  # record relevant info in the Alignment.
  $$colspec{align} = $align;
  $$colspec{border}= $border = ($$colspec{border}||'').$border;
  $$colspec{boxes} = $boxes;
  $$colspec{span}  = $n1-$n0+1;
  $$colspec{empty} = 1 if $empty;
  $$colspec{head}  = LookupValue('IN_TABULAR_HEAD');
  for(my $i = $n0+1; $i <= $n1; $i++){
    my $c = $alignment->getColumn($i);
    $c->{skipped}=1 if $c; }
#  $stomach->egroup;
  $boxes; },
	     beforeDigest=>sub {
	       AssignValue(alignmentStartColumn=>LookupValue('Alignment')->currentColumnNumber); 
	       $_[0]->bgroup; });

AssignValue(ALIGNMENT_LINE_COMMANDS=>[]);
PushValue(ALIGNMENT_LINE_COMMANDS=>T_CS('\hline'));
PushValue(ALIGNMENT_LINE_COMMANDS=>T_CS('\cline'));
PushValue(ALIGNMENT_LINE_COMMANDS=>T_CS('\label'));

DefMacroI('\@close@inner@column', undef,sub {
	   (T_CS('\@@eat@space'),
	    afterCellUnlist(LookupValue('Alignment')->currentColumn->{after}),
	    T_CS('\@@close@inner@column')); });
DefPrimitiveI('\@@close@inner@column', undef,sub { $_[0]->egroup; });
DefPrimitiveI('\@open@inner@column',   undef,sub {
  my($stomach)=@_;
  my $colspec =  LookupValue('Alignment')->nextColumn;
  $stomach->bgroup;
  my $gullet = $stomach->getGullet;
  my @lines=();
  my @line_tokens = @{LookupValue('ALIGNMENT_LINE_COMMANDS')};
  my @savedtokens=();
  $$colspec{empty}=0;		# Assume the column isn't empty
  # Scan for leading \omit, skipping over (& saving) \hline.
  while(my $tok=$gullet->readXToken){
    if($tok->equals(T_SPACE)){ } # Skip leading space
    elsif(grep($tok->equals($_),@line_tokens)){ # Save line commands
      push(@lines,$stomach->invokeToken($tok)); }
    elsif(Equals($tok,T_BEGIN)){ # Crazy... seems { doesn't "block" \omit!
      push(@savedtokens,$tok); }
    else {
##      if($tok->equals(T_CS('\omit'))){ # \omit removes the before/after tokens for this column.
      if(Equals($tok,T_CS('\omit'))){ # \omit removes the before/after tokens for this column.
	$$colspec{before}=$$colspec{after}=Tokens(); }
##      elsif($tok->equals(T_CS('\@@eat@space'))){ # First non-empty token implies column is empty.
      elsif(Equals($tok,T_CS('\@@eat@space'))){ # First non-empty token implies column is empty.
	$$colspec{empty}=1; }
      $gullet->unread($tok); last; }}
  $gullet->unread(@savedtokens);
  $gullet->unread(beforeCellUnlist($$colspec{before}));
  (@lines,$STATE->getStomach->digestNextBody()); });

# NOTE: Watch here for problems with alignments.
# The previous version threw away too much stuff (esp. metadata).
# This one, I think, is more careful.
# The issue is that it should throw away spaces (or things like spaces?)
# so that various omit/span/fill/etc is properly recognized when analyzing columns.
DefPrimitiveI('\@@eat@space',undef,sub {
	       my $box;
	       my @save=();
	       while($box = $LaTeXML::LIST[$#LaTeXML::LIST]){
		 if($box->getProperty('alignmentSkippable')
		       || $box->getProperty('isFill')){
		   push(@save,pop(@LaTeXML::LIST)); }
		 elsif(isEmpty($box)){
		   pop(@LaTeXML::LIST); }
		 else {
		   last; }}
	       push(@LaTeXML::LIST,@save);
	       return; });

# Yet more special case hacking. Sometimes the order of tokens works for
# TeX, but confuses us... In particular the order of $ and \hfil!
# \@open@column is too late, since the stuff is already digested.
# Could _almost_  handle the extractions here, but there are several
# rule operators that digest into whatsits with certain properties...
sub beforeCellUnlist {
  my($tokens)=@_; 
  return () unless $tokens;
  my @toks = $tokens->unlist;
  my @new = ();
  while(my $t = shift(@toks)){
##    if($t->equals(T_MATH) && @toks && $toks[0]->equals(T_CS('\hfil'))){
    if(Equals($t,T_MATH) && @toks && Equals($toks[0],T_CS('\hfil'))){
      push(@new,shift(@toks)); unshift(@toks,$t); }
    else {
      push(@new, $t); }}
  @new; }

sub afterCellUnlist {
  my($tokens)=@_; 
  return () unless $tokens;
  my @toks = $tokens->unlist;
  my @new = ();
  while(my $t = pop(@toks)){
##    if($t->equals(T_MATH) && @toks && $toks[$#toks]->equals(T_CS('\hfil'))){
    if(Equals($t,T_MATH) && @toks && Equals($toks[$#toks],T_CS('\hfil'))){
      unshift(@new,pop(@toks)); push(@toks,$t); }
    else {
      unshift(@new, $t); }}
  @new; }


#----------------------------------------------------------------------
# \halign, See Chap.22

DefPrimitive('\halign BoxSpecification', sub {
  my($stomach,$spec,$contents)=@_;
  my $gullet=$stomach->getGullet;
  my $t = $gullet->readNonSpace;
  Error(":expected:\\bgroup Missing \\halign box") unless Equals($t,T_BEGIN);

  $stomach->bgroup; 
  Let(T_ALIGN,      T_CS('\undefined'));	# KEEP these from being expanded here!!!
  Let(T_CS('\span'),T_CS('\undefined'));
  Let(T_CS('\omit'),T_CS('\undefined'));
  Let(T_CS('\cr'),  T_CS('\default@cr'));
  Let(T_CS('\crcr'),T_CS('\default@cr'));
  Let(T_CS("\\\\"), T_CS('\default@cr'));
  # Read the template up till something equivalent to \cr
  my @template = ();
  while(($t=$gullet->readXToken) && !Equals($t,T_CS('\cr'))){
    push(@template,$t); }

  # Read the rest until balanced }
  my @tokens = ();
  my $level=1;
  while($level && ($t=$gullet->readXToken)){
    if(Equals($t,T_BEGIN)){ $level++; }
    elsif(Equals($t,T_END)){ $level--; }
    push(@tokens,$t) if $level; }
  $stomach->egroup; 

  my $ismath     = $STATE->lookupValue('IN_MATH');
  my $b=1;			# true if we're before a # in current column
  my @pre=();
  my @post=();
  my @cols=();
  my @nonreps=();
  foreach my $t (@template, T_ALIGN){ # put & at end, to save column!
    if($t->equals(T_PARAM)){
      $b=0; }
    elsif($t->equals(T_ALIGN)){
      if($b){ @nonreps=@cols; @cols=(); } # A & while we're before a column means Repeated columns
      else {				  # Finished column spec; add it
	# Try some magic for math, so we can create a valid math matrix (maybe!)
	# DAMN \halign can't be in math, anyway.
	# So, to get a matrix, we'll have to rewrite the alignment!
 	if($ismath){
	  push(@pre,T_MATH); unshift(@post,T_MATH); }
	push(@cols,{before=>Tokens(stripDupMath(beforeCellUnlist(Tokens(@pre)))),
		    after =>Tokens(stripDupMath(afterCellUnlist(Tokens(@post))))});
	@pre=@post=(); $b=1; }}
    elsif($b){
      push(@pre,$t) unless !@pre && $t->equals(T_SPACE); }
    else {
      push(@post,$t) unless !@post && $t->equals(T_SPACE); }}

  my $template = LaTeXML::AlignmentTemplate->new((@nonreps ?
						  (columns=>[@nonreps],repeated=>[@cols])
						  :(columns=>[@cols])));
#  print STDERR "Template = ".Stringify(Tokens(@template))."\n => ".$template->show."\n";

  $stomach->bgroup;
  alignmentBindings($template);
  my @boxes = Digest(Invocation(T_CS('\@halign'),
				$spec,Tokens(@template),
				Tokens(T_CS('\@start@alignment'),
				       @tokens,
				       T_CS('\@finish@alignment'))));
  $stomach->egroup; 
  @boxes; });

# Cleanup the pre & post tokens for halign columns in math mode.
# If a pair of $..$ enclose stuff that is "OK" in math mode, we don't need the $.
# Note that the 1st $ is switching OUT of math mode!
sub stripDupMath {
   my(@tokens)=@_;
   my @poss = grep(Equals($tokens[$_],T_MATH), 0..$#tokens);
###   pop(@poss) if scalar(@poss) % 2; # Get pairs!
   shift(@poss) if scalar(@poss) % 2; # Get pairs!
   while(@poss){
     my($p2,$p1)=(pop(@poss),pop(@poss));
     splice(@tokens,$p1,2) if $p2==$p1+1; }
   @tokens; }

DefConstructor('\@halign BoxSpecification Undigested {}', 
	       sub {
		 my($document,$spec,$template,$body,%props)=@_;
		 constructAlignment($document,$body,attributes=>{width=>$$spec{to},
								 'pad-width'=>$$spec{spread}}); },
	       reversion=>'\halign #1{#2\cr#3}',
	       bounded=>1);

# "Initialized" alignment; presets spacing, but since we're ignoring it anyway...
Let(T_CS('\ialign'),T_CS('\halign'));

# Overlapping alignments ???
DefMacro('\oalign{}',
	 '\@@oalign{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@oalign{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body); },
	       reversion=>'\oalign{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings('l'); });
# This is actually different; the lines should lie ontop of each other.
# How should this be represented?
DefMacro('\ooalign{}',
	 '\@@ooalign{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@ooalign{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body); },
	       reversion=>'\ooalign{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings('l'); });


#----------------------------------------------------------------------
DefConstructorI('\indent',   undef,"");
DefConstructorI('\noindent', undef,"");

# <ltx:para> represents a Logical Paragraph, whereas <ltx:p> is a `physical paragraph'.
# A para can contain both p and displayed equations and such.

# Remember; \par closes, not opens, paragraphs!
# Here, we want to close both an open p and para (if either are open).
DefConstructorI('\par', undef,
	       sub { $_[0]->maybeCloseElement('ltx:p');
		     $_[0]->maybeCloseElement('ltx:para'); },
	       alias=>"\\par\n");
Tag('ltx:para', autoClose=>1, autoOpen=>1);

sub trimNodeWhitespace {
  my($document,$node)=@_;
  trimNodeLeftWhitespace($document,$node);
  trimNodeRightWhitespace($document,$node); }

sub trimNodeLeftWhitespace {
  my($document,$node)=@_;
  if(my(@children)=$node->childNodes){
    my $child = $children[0];
    my $type = $child->nodeType;
    if($type == XML_TEXT_NODE){
      my $string = $child->data;
#      if($string =~ s/^\s+//){
#      with some trepidation, I don't think we want to trim nbsp!
      if($string =~ s/^ +//){
	$child->setData($string); }}
    elsif($type == XML_ELEMENT_NODE){
      trimNodeLeftWhitespace($document,$child); }}}

sub trimNodeRightWhitespace {
  my($document,$node)=@_;
  if(my(@children)=$node->childNodes){
    my $child = $children[-1];
    my $type = $child->nodeType;
    if($type == XML_TEXT_NODE){
      my $string = $child->data;
      if($string =~ s/\s+$//){
	$child->setData($string); }}
    elsif($type == XML_ELEMENT_NODE){
      trimNodeRightWhitespace($document,$child); }}}


Tag('ltx:p', autoClose=>1, autoOpen=>1, afterClose=>\&trimNodeWhitespace);

# \dump ???

DefPrimitiveI('\end',undef,sub { $_[0]->getGullet->flush; return; });

#======================================================================
# Horizontal Mode primitives in Ch.25, pp.285--287

# The following cause tex to start a new paragraph -- they switch to horizontal mode.
# <horizontal command> = <letter> | <other> | \char | <chardef token>
#    | \noboundary | \unhbox | \unhcopy | \valign | \vrule 
#    | \hskip | \hfil | \hfill | \hss | \hfilneg 
#    | \accent | \discretionary | \- | \<space> | $

DefPrimitiveI('\noboundary',undef,undef);
DefPrimitive('\hskip Glue',undef);
DefPrimitive('\mskip MuGlue', undef);
DefPrimitiveI('\hss',undef,undef);
DefConstructorI('\hfil',  undef,"?#isMath(<ltx:XMHint name='hfil'/>)( )",
	      properties=>{isSpace=>1, isFill=>1});
DefConstructorI('\hfill', undef,"?#isMath(<ltx:XMHint name='hfill'/>)( )",
	      properties=>{isSpace=>1, isFill=>1});
DefPrimitiveI('\hfilneg', undef,undef);

# \lower <dimen> <box>
# \raise <dimen> <box>
# But <box> apparently must really explicitly be an \hbox, \vbox or \vtop (?)
# OR something that expands into one!!

DefConstructor('\lower Dimension MoveableBox',
	       "<ltx:text yoffset='#y'  _noautoclose='1'>#2</ltx:text>",
	       afterDigest=>sub{
		 $_[1]->setProperty(y=>$_[1]->getArg(1)->multiply(-1)); });
DefConstructor('\raise Dimension MoveableBox',
	       "<ltx:text yoffset='#y' _noautoclose='1'>#2</ltx:text>",
	       afterDigest=>sub{
		 $_[1]->setProperty(y=>$_[1]->getArg(1)); });

# \unhbox<8bit>, \unhcopy<8bit>
DefPrimitive('\unhbox Number',sub {
  my $box = 'box'.$_[1]->valueOf;
  my $stuff = LookupValue($box); 
  AssignValue($box,undef);
  ($stuff); });
DefPrimitive('\unhcopy Number',sub {
  (LookupValue('box'.$_[1]->valueOf)); });

# \vrule
# \valign ???

DefConstructor('\vspace{}',"");

# \indent, \noindent, \par; see above.

# \accent Number ????
DefMacro('\accent Number',Tokens());	       # How should these be translated? Depends on font??
DefMacro('\discretionary{}{}{}','#3'); # No hyphenation here!
DefPrimitiveI('\-', undef,undef);
DefPrimitive('\setlanguage Number',undef);


#======================================================================
# Math mode stuff
# See TeXBook Ch.26
#======================================================================
# Decide whether we're going into or out of math, inline or display.
Tag('ltx:XMText', autoOpen=>1,autoClose=>1);
DefPrimitiveI(T_MATH, undef,sub {
  my($stomach)=@_;
  my $gullet = $stomach->getGullet;
  my $mode = LookupValue('MODE');
  my $op = '\@@BEGININLINEMATH';
  if($mode eq 'display_math'){
    if($gullet->ifNext(T_MATH)){ $gullet->readToken; }
    else { Fatal(":expected:\$ Missing \$ closing display math."); }
    $op = '\@@ENDDISPLAYMATH'; }
  elsif($mode eq 'inline_math'){
    $op = '\@@ENDINLINEMATH'; }
#  elsif(!LookupValue('Alignment') && $gullet->ifNext(T_MATH)){
  elsif($gullet->ifNext(T_MATH)){
    $gullet->readToken;
    $op = '\@@BEGINDISPLAYMATH'; }
  $stomach->invokeToken(T_CS($op)); });


# Effectively these are the math hooks, redefine these to do what you want with math?
DefConstructorI('\@@BEGINDISPLAYMATH',undef,
	       "<ltx:equation>"
	       . "<ltx:Math mode='display'>"
	       .  "<ltx:XMath>"
	       .   "#body"
	       .  "</ltx:XMath>"
	       . "</ltx:Math>"
	       ."</ltx:equation>",
	       alias=>'$$',
	       beforeDigest=> sub{ $_[0]->beginMode('display_math'); },
	       captureBody=>1);
DefConstructorI('\@@ENDDISPLAYMATH',undef, "", alias=>'$$',
	       beforeDigest=> sub{ $_[0]->endMode('display_math'); });

DefConstructorI('\@@BEGININLINEMATH', undef,
	       "<ltx:Math mode='inline'>"
	       . "<ltx:XMath>"
	       .  "#body"
	       . "</ltx:XMath>"
	       ."</ltx:Math>",
	       alias=>'$', beforeDigest=> sub{ $_[0]->beginMode('inline_math'); }, captureBody=>1);
DefConstructorI('\@@ENDINLINEMATH', undef,"", alias=>'$',
	       beforeDigest=> sub{ $_[0]->endMode('inline_math');});

# Add the TeX code from the object that created this node,
# unless it has already been recorded on another node.
sub add_TeX {
  my($document,$node,$thing)=@_;
  if($thing && (ref $thing eq 'LaTeXML::Whatsit') && !$thing->getProperty('_added_tex')){
    local $LaTeXML::DUAL_BRANCH = 'presentation';
    my $tex = UnTeX($thing);
    $LaTeXML::DUAL_BRANCH = 'content';
    my $ctex = UnTeX($thing);
    $document->setAttribute($node,tex=>$tex);
    $document->setAttribute($node,'content-tex'=>$ctex) if $ctex ne $tex;
    $thing->setProperty('_added_tex',1); }}

# Same as add_TeX, but add the code from the body of the object.
sub add_body_TeX {
  my($document,$node,$thing)=@_;
  if($thing && !$thing->getProperty('_added_body_tex')){
    if(defined(my $body = $thing->getProperty('body'))){
      local $LaTeXML::DUAL_BRANCH = 'presentation';
      my $tex = UnTeX($body);
      $LaTeXML::DUAL_BRANCH = 'content';
      my $ctex = UnTeX($body);
      $document->setAttribute($node,tex=>$tex);
      $document->setAttribute($node,'content-tex'=>$ctex) if $ctex ne $tex;
    }
    $thing->setProperty('_added_body_tex',1); }}

Tag('ltx:Math',   afterOpen=>\&add_body_TeX);
Tag('ltx:Math',  afterClose=>\&cleanup_Math);

sub cleanup_Math {
  my($document,$mathnode)=@_;
  # If the Math contains ONLY XMath that ONLY contain XMText,
  # it apparently isn't math at all!?!  So pull up the contents of the XMText's.
  foreach my $xnode ($mathnode->childNodes){
    return unless $document->getModel->getNodeQName($xnode) eq 'ltx:XMath';
    foreach my $mnode ($xnode->childNodes){
      return unless $document->getModel->getNodeQName($mnode) eq 'ltx:XMText';
    }}
  # Replace Math/XMath/XMText/* by *
  replace_node($mathnode,
	       map($_->childNodes,map($_->childNodes,$mathnode->childNodes))); }

sub replace_node {
  my($oldnode,@newnodes)=@_;
  my $parent = $oldnode->parentNode;
  if(!@newnodes){
    $parent->removeChild($oldnode); }
  else {
    my $new = shift(@newnodes);
    $parent->replaceChild($new,$oldnode);
    foreach my $n (@newnodes){
      $parent->insertAfter($n,$new);
      $new = $n; }}}

Let(T_CS('\vcenter'), T_CS('\vbox'));

# \eqno & \leqno are really bizzare.
# They should seemingly digest until $ (or while still in math mode),
# and use that stuff as the reference number.
# However, since people abuse this, and we're really not quite TeX,
# we really can't do it Right.
# Even a \begin{array} ends up expanding into a $ !!!
DefMacroI('\eqno',undef,sub {
  my($gullet)=@_;
  my @stuff=();
  # This is risky!!!
  while(my $t = $gullet->readXToken){
    if(! defined $t){
      Fatal(":unexpected:\eqno fell of the end!"); }
    # What do I need to explicitly list here!?!?!? UGGH!
    elsif(Equals($t,T_MATH) || Equals($t,T_CS('\]'))
	  || Equals($t,T_CS('\begingroup')) # Totally wrong, but to catch expanded environments
	  || ( ToString($t)=~ /^\\(?:begin|end)\{/)){ # any sort of environ begin or end???
#       || Equals($t,T_CS('\end{equation}'))|| Equals($t,T_CS('\end{equation*}'))){
      return (Invocation(T_CS('\@@eqno'),Tokens(@stuff)), $t); }
    else {
      push(@stuff,$t); }}});

Let(T_CS('\leqno'),T_CS('\eqno'));
# Revert to nothing, since it really doesn't belong in the TeX string(?)
DefConstructor('\@@eqno{}',"^ refnum='#eqnum'",
	       reversion=>'',
	      afterDigest=>sub { my $num = ToString($_[1]->getArg(1));
				 $num=~ s/^\{(.*)\}$/$1/;
				 $num=~ s/^\((.*)\)$/$1/;
				 $_[1]->setProperties(eqnum=>$num); });

#======================================================================
# Scripts are a bit of a strange beast, with respect to when the arguments
# are processed, and what kind of object should be created.
#
# While scripts look like they take a normal TeX argument, they really
# take the next BOX (AFTER expansion & digestion)!  Thus, while
#   a^\frac{b}{c} and a^\mathcal{B} 
# DO work in TeX, other things like
#   a^\sqrt{3} or a^\acute{b}
# DO NOT! (Hint: consider the expansions)
# Note that with
#  \def\xyz{xyz}
#   a^\xyz   =>  a^{x}yz
# So, we try to mimic, but note that our boxes don't correspond 100% to TeX's
#
# Normally, sub/super scripts should be turned into a sort of postfix operator:
# The parser will attach the script to the appropriate preceding object.
# However, there are a few special cases involving empty boxes {}.
# If the argument is an empty box $x^{}$, the whole script should just disappear.
# If the PRECEDING box is {} (in ${}^{p}$, a sort of `floating' script should be created.
# This may combine, in the parser, with the following object to generate
# a prescript.

our %scriptstylemap=(display=>'script', text=>'script', 
		     script=>'scriptscript', scriptscript=>'scriptscript');

# Note that this is also being used by alignment.
sub isEmpty {
  my($box)=@_;
  my $ref = ref $box;
  if(! $box ){ return 1; }
  elsif($ref eq 'LaTeXML::Comment'){ return 1; }
  elsif($ref eq 'LaTeXML::Box'){ return $box->getString =~ /^\s*$/; }
  elsif($ref eq 'LaTeXML::MathBox'){ return $box->getString =~ /^\s*$/; }
  elsif($ref eq 'LaTeXML::Whatsit'){
    return 1 if $box->getProperty('isSpace'); # A space-like Whatsit
    if(($box->getDefinition eq $STATE->lookupMeaning(T_BEGIN))
       && !grep(!isEmpty($_), $box->getBody->unlist)){
      return 1; }}
  return 0; }
    
# my $is=
#   (!$box
#    || ($ref eq 'LaTeXML::Comment')
#    || $box->getProperty('isSpace') # A space-like Whatsit
#    || (($ref =~ /^LaTeXML::(Math)?Box$/) && ($box->getString =~ /^\s*$/)) # Space plain box
#    || (($ref eq 'LaTeXML::Whatsit') # Whatsit with nothing in it.
#        && ($box->getDefinition eq $STATE->lookupMeaning(T_BEGIN))
#        && !grep(!isEmpty($_), $box->getBody->unlist))); 

# ##print STDERR "Box ".Stringify($box)." is empty: ".($is ? "yes":"no")."\n";
#   $is;
# }
  
sub scriptHandler {
  my($stomach,$op)=@_;
  my $gullet = $stomach->getGullet;
  $gullet->skipSpaces;
  my $style = LookupValue('mathstyle'); # Bump the math style smaller.
  if(defined $style){
    my $cs = '\@@POST'.$op;
    # Check if preceding box, if any, is empty {}
    my $prev = $LaTeXML::LIST[$#LaTeXML::LIST];
    if(! $prev){		# No box at all?
      $cs = '\@@FLOATING'.$op; }
    elsif(isEmpty($prev)){	# Or empty (remove it)
      pop(@LaTeXML::LIST);
      $cs = '\@@FLOATING'.$op; }
    elsif((ref $prev eq 'LaTeXML::Whatsit') # Or previous sub/supscript.
	  && (($prev->getDefinition eq $STATE->lookupMeaning(T_CS('\@@FLOATINGSUPERSCRIPT')))
	      || ($prev->getDefinition eq $STATE->lookupMeaning(T_CS('\@@FLOATINGSUBSCRIPT'))))){
      $cs = '\@@FLOATING'.$op; }


    AssignValue(mathstyle=>$scriptstylemap{$style});
    # Now, get following boxes
#    my @stuff = $stomach->invokeToken($gullet->readXToken);
    my @stuff=();
    while(my $tok = $gullet->readXToken){
      @stuff = $stomach->invokeToken($tok);
      last if @stuff; }
    Fatal(":expected:{ Missing { in sub/super-script argument") unless @stuff;
    my $script = shift(@stuff);	# ONLY this box is the script!
    my $level = $stomach->getBoxingLevel;
    unshift(@stuff,
	    LaTeXML::Whatsit->new(LookupDefinition(T_CS($cs)),[$script],
				  locator=>$gullet->getLocator,
				  font=>$script->getFont,isMath=>1,
				  level=>$level))
      if ToString($script) ne '{}';    # ONLY If Script is not empty!!!
    AssignValue(mathstyle=>$style); # revert
    @stuff; }
  else {			# Non math use of _ ??
    my $c = (($op eq 'SUPERSCRIPT') ? '^' : '_');
    Error(":unexpected:$c Script $c can only appear in math mode");
    LaTeXML::Box->new($c,
		      $STATE->lookupValue('font'),$gullet->getLocator,
		      (($op eq 'SUPERSCRIPT') ? T_SUPER : T_SUB));
  }}

DefPrimitiveI(T_SUPER, undef,sub{ scriptHandler($_[0],'SUPERSCRIPT'); });
DefPrimitiveI(T_SUB, undef,sub{ scriptHandler($_[0],'SUBSCRIPT'); });

DefConstructor('\@@POSTSUPERSCRIPT{}',
	       "<ltx:XMApp role='POSTSUPERSCRIPT' scriptpos='#level'>"
	       . "<ltx:XMArg rule='Superscript'>#1</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       alias=>'^');
DefConstructor('\@@POSTSUBSCRIPT{}',
	       "<ltx:XMApp role='POSTSUBSCRIPT' scriptpos='#level'>"
	       . "<ltx:XMArg rule='Subscript'>#1</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       alias=>'_');
DefConstructor('\@@FLOATINGSUPERSCRIPT{}',
	       "<ltx:XMApp role='FLOATSUPERSCRIPT' scriptpos='#level'>"
	       . "<ltx:XMArg rule='Superscript'>#1</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       alias=>'{}^');
DefConstructor('\@@FLOATINGSUBSCRIPT{}',
	       "<ltx:XMApp role='FLOATSUBSCRIPT' scriptpos='#level'>"
	       . "<ltx:XMArg rule='Subscript'>#1</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       alias=>'{}_');

## NOTE: Strictly speaking, these two should examine the scriptpos
# attribute of the base (it may have \limits,\nolimits)
## OR, alternatively, there could be separate \@OVERSCRIPT/\@UNDERSCRIPT macros ?
DefConstructor('\@SUPERSCRIPT{}{}', 
	       "?#2(<ltx:XMApp>"
	       .     "<ltx:XMTok role='SUPERSCRIPTOP' scriptpos='#scriptpos'/>"
	       .     "<ltx:XMArg>#1</ltx:XMArg>"
	       .     "<ltx:XMArg rule='Superscript'>#2</ltx:XMArg>"
	       .   "</ltx:XMApp>)"
	       .   "(#1)",
	       reversion=> sub {
		 my($whatsit,$base,$sup)=@_;
		 ($sup && $sup->unlist 
		  ? (T_BEGIN,$base->revert,T_END,
		     T_SUPER,T_BEGIN,$sup->revert,T_END)
		  : $base->revert); },
	       properties=>{scriptpos=>sub{ "post".$_[0]->getBoxingLevel; }});
DefConstructor('\@SUBSCRIPT{}{}', 
	       "?#2(<ltx:XMApp>"
	       .     "<ltx:XMTok role='SUBSCRIPTOP' scriptpos='#scriptpos'/>"
	       .     "<ltx:XMArg>#1</ltx:XMArg>"
	       .     "<ltx:XMArg rule='Subscript'>#2</ltx:XMArg>"
	       .   "</ltx:XMApp>)"
	       .   "(#1)",
	       reversion=>sub {
		 my($whatsit,$base,$sub)=@_;
		 ($sub && $sub->unlist 
		  ? (T_BEGIN,$base->revert,T_END,
		     T_SUB,T_BEGIN,$sub->revert,T_END)
		  : $base->revert); },
	       properties=>{scriptpos=>sub{ "post".$_[0]->getBoxingLevel; }});

# This only becomes active in math mode.
AssignCatcode('math:\''=>1, 'global');
DefMacroI('\'',undef,sub{
  my($gullet)=@_;
  my $n = 1;
  while($gullet->ifNext(T_OTHER('\''))){ $gullet->readToken; $n++; };
  (T_SUPER,T_BEGIN,map(T_CS('\prime'), 1..$n),T_END); });

#======================================================================
# \choose & friends, also need special argument handling

# After digesting the \choose (or whatever), grab the previous and following material
# and store as args in the whatsit.
our %fracstylemap=(display=>'text', text=>'script',
		   script=>'scriptscript', scriptscript=>'scriptscript');
sub chooseArgHandler {
  my($stomach,$whatsit)=@_;
  my $style = LookupValue('mathstyle');
  AssignValue(mathstyle=>$fracstylemap{$style}); # Bump the style smaller.
  # Unfortunately, the way we've done it, it's too late for the numerator!!!
  my @top = $stomach->regurgitate;
  my @bot = $stomach->digestNextBody();
  my $closing = pop(@bot);	# We'll leave whatever closed the list (endmath, endgroup...)
#  $whatsit->setArgs(LaTeXML::MathList->new(@top),LaTeXML::MathList->new(@bot));
  $whatsit->setProperty(top=>LaTeXML::MathList->new(@top));
  $whatsit->setProperty(bottom=>LaTeXML::MathList->new(@bot));
  $whatsit->setProperty(style=>LookupValue('mathstyle'));

  AssignValue(mathstyle=>$style);
  $closing; }			# leave the closing bit

# Support here: Used by LaTeX, amsmath, but also amstex
sub beforeFrac {
  $_[0]->bgroup;
  AssignValue(mathstyle=>$fracstylemap{LookupValue('mathstyle')}); }
sub afterFrac  {
  $_[0]->egroup;
  $_[1]->setProperty('style',LookupValue('mathstyle')); }

# Needed by amsmath (at least)
sub beforeTFrac { $_[0]->bgroup; AssignValue(mathstyle=>'text');
		  beforeFrac(@_); }
sub afterTFrac  { afterFrac(@_); $_[0]->egroup; }

sub beforeDFrac { $_[0]->bgroup; AssignValue(mathstyle=>'display'); 
		  beforeFrac(@_); }
sub afterDFrac  { afterFrac(@_); $_[0]->egroup; }


# WHY did I change to top/bottom instead of #1,#2 ????
# This will inhibit comparisons!!!!
# Is it because of the delims forms already having args???
# No, because constructor doesn't know there are 2 extra args!!
DefConstructorI('\choose',undef,
	       "<ltx:XMApp open='(' close=')'>"
	       . "<ltx:XMTok meaning='binomial' role='STACKED' style='#style'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=>sub {
		 my($whatsit)=@_;
		 ($whatsit->getProperty('top')->revert,
		  T_CS('\choose'),
		  $whatsit->getProperty('bottom')->revert); });
DefConstructorI('\brace',undef,
	       "<ltx:XMApp open='{' close='}'>"
	       . "<ltx:XMTok role='STACKED' style='#style'/>" # what meaning?
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=>sub {
		 my($whatsit)=@_;
		 ($whatsit->getProperty('top')->revert,
		  T_CS('\choose'),
		  $whatsit->getProperty('bottom')->revert); });
DefConstructorI('\brack',undef,
	       "<ltx:XMApp open='[' close=']'>"
	       . "<ltx:XMTok role='STACKED' style='#style'/>" # what meaning?
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=>sub {
		 my($whatsit)=@_;
		 ($whatsit->getProperty('top')->revert,
		  T_CS('\choose'),
		  $whatsit->getProperty('bottom')->revert); });
DefConstructorI('\over',undef,
	       "<ltx:XMApp>"
	       . "<ltx:XMTok meaning='divide' style='#style' role='MULOP'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=>sub {
		 my($whatsit)=@_;
		 ($whatsit->getProperty('top')->revert,
		  T_CS('\over'),
		  $whatsit->getProperty('bottom')->revert); });
DefConstructorI('\atop',undef,
	       "<ltx:XMApp>"
	       . "<ltx:XMTok role='STACKED' style='#style'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=>sub {
		 my($whatsit)=@_;
		 ($whatsit->getProperty('top')->revert,
		  T_CS('\atop'),
		  $whatsit->getProperty('bottom')->revert); });
DefConstructor('\overwithdelims Token Token',
	       "<ltx:XMApp open='#1' close='#2'>"
	       . "<ltx:XMTok meaning='divide' style='#style' role='MULOP'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=> sub {
		 my($whatsit,@args)=@_;
		 ($whatsit->getProperty('top')->revert,
		  $whatsit->getDefinition->invocation(@args),
		  $whatsit->getProperty('bottom')->revert); });
# But, wonder what the _meaning_ is!
DefConstructor('\atopwithdelims Token Token',
	       "<ltx:XMApp open='#1' close='#2'>"
	       . "<ltx:XMTok role='STACKED'  style='#style'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=>sub {
		 my($whatsit,@args)=@_;
		 ($whatsit->getProperty('top')->revert,
		  $whatsit->getDefinition->invocation(@args),
		  $whatsit->getProperty('bottom')->revert); });

sub IsZero {
  my($dim)=@_;
  (!defined $dim) || ($dim->valueOf == 0); }

DefConstructor('\above Dimension',
	       "<ltx:XMApp>"
	       . "<ltx:XMTok ?&IsZero(#1)(role='STACKED')(role='MULOP' meaning='divide')"
	       .           " style='#style' thickness='#1'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=> sub {
		 my($whatsit,@args)=@_;
		 ($whatsit->getProperty('top')->revert,
		  $whatsit->getDefinition->invocation(@args),
		  $whatsit->getProperty('bottom')->revert); });

DefConstructor('\abovewithdelims Token Token Dimension',
	       "<ltx:XMApp open='#1' close='#2'>"
	       . "<ltx:XMTok ?&IsZero(#3)(role='STACKED')(role='MULOP' meaning='divide')"
	       .           " style='#style' thickness='#3'/>"
	       . "<ltx:XMArg>#top</ltx:XMArg>"
	       . "<ltx:XMArg>#bottom</ltx:XMArg>"
	       ."</ltx:XMApp>",
	       afterDigest=>\&chooseArgHandler,
	       reversion=> sub {
		 my($whatsit,@args)=@_;
		 ($whatsit->getProperty('top')->revert,
		  $whatsit->getDefinition->invocation(@args),
		  $whatsit->getProperty('bottom')->revert); });

#======================================================================
DefConstructorI('\cal', undef,'', font=>{family=>'caligraphic'});

# In principle, <ltx:emph> is a nice markup for emphasized.
# Unfortunately, TeX really just treats it as a font switch.
# Something like:  \em et.al. \rm more stuff
# works in TeX, but in our case, since there is no explicit {},
# the <ltx:emph> stays open!  Ugh!
# This could still be made to work, but merge font would
# need to look at any open <ltx:emph>, and then somehow close it!
## DefConstructor('\@end@emph','</ltx:emph>');
## DefConstructor('\em',"<ltx:emph>", 
## 	       afterDigest=>sub { UnshiftValue(beforeAfterGroup=>T_CS('\@end@emph')); });
# So, we'll just toggle between shape = italic and normal, for now. Sigh.
DefConstructorI('\em',undef, '',
	       beforeDigest=>sub {
		 my $font = LookupValue('font');
		 my $shape = $font->getShape;
		 AssignValue(font=>$font->merge(shape=>($shape eq 'italic' ? 'normal' : 'italic')),
			     'local'); });

# Change math font while still in text!
DefPrimitiveI('\boldmath',  undef,sub { AssignValue(mathfont=>LookupValue('mathfont')->merge(forcebold=>1), 'local'); },
	     forbidMath=>1);
DefPrimitiveI('\unboldmath',undef,sub { AssignValue(mathfont=>LookupValue('mathfont')->merge(forcebold=>0), 'local'); },
	     forbidMath=>1);

#======================================================================
# Alignments

# & gives an error except within the right context 
# (which should redefine it!)
DefConstructorI('&', undef,sub { Error(":unexpected:& Stray alignment \"&\""); });

#**********************************************************************
# Plain;  Extracted from Appendix B.
#**********************************************************************

#======================================================================
# TeX Book, Appendix B, p. 344
#======================================================================
# \dospecials ??

# Set up the mathcode table

# Normally, the content branch contains the pure structure and meaning of a construct,
# and the presentation is generated from lower level TeX macros that only concern
# themselves with how to display the object.
# Nevertheless, it is sometimes useful to know where the tokens in the presentation branch
# came from;  particularly what their presumed "meaning" is.
# For example, when search-indexing pmml, or providing links to definitions from the pmml.
#
# The following constructor (see how it's used in DefMath), adds meaning attributes
# whereever it seems sensible on the presentation branch, after it has been generated.
DefConstructor('\@ASSERT@MEANING{}{}','#2',reversion=>'#2',
	       afterConstruct=>sub {
		 my($document,$whatsit)=@_;
		 my $node=$document->getNode; # This should be the wrapper just added.
		 my $meaning = ToString($whatsit->getArg(1));
		 addMeaningRec($document,$node,$meaning);
		 $node; });

#======================================================================
# Properties for plain characters.
# These are allowed in plain text, but need to act a bit special in math.
DefMathI('=',undef,'=', role=>'RELOP', meaning=>'equals');
DefMathI('+',undef,'+', role=>'ADDOP', meaning=>'plus');
DefMathI('-',undef,'-', role=>'ADDOP', meaning=>'minus');
DefMathI('*',undef,'*', role=>'MULOP', meaning=>'times');
DefMathI('/',undef,'/', role=>'MULOP', meaning=>'divide', style=>'inline');
DefMathI('!',undef,'!', role=>'POSTFIX', meaning=>'factorial');
DefMathI(',',undef,',', role=>'PUNCT');
DefMathI('.',undef,'.', role=>'PERIOD');
DefMathI(';',undef,';', role=>'PUNCT');
DefMathI('(',undef,'(', role=>'OPEN'); DefMathI(')',undef,')', role=>'CLOSE');
DefMathI('[',undef,'[', role=>'OPEN'); DefMathI(']',undef,']', role=>'CLOSE');
DefMathI('|',undef,'|', role=>'VERTBAR');

#DefMath(':',';', role=>'METARELOP', name=>'colon'); # Seems like good default role

# It seems that "MathLigature" should be adapted to make this sort of thing easy!
AssignCatcode('math::'=>1, 'global');
DefMacroI(':',undef,sub{
  my($gullet)=@_;
  if($gullet->ifNext(T_OTHER('='))){
    $gullet->readToken;
    T_CS('\@colonequal'); }
  else {
    T_CS('\@colon'); }});
DefMathI('\@colon',     undef,':', role=>'METARELOP', name=>'colon',reversion=>':'); # Seems like good default role
DefMathI('\@colonequal',undef,':=', role=>'RELOP', meaning=>'assign',reversion=>':=');

AssignCatcode('math:<'=>1, 'global');
AssignCatcode('math:>'=>1, 'global');
DefMacroI('<',undef,sub{
  my($gullet)=@_;
  if($gullet->ifNext(T_OTHER('<'))){
    $gullet->readToken;
    T_CS('\ll'); }
  # leq ? hmmmm
  else {
    T_CS('\@@<'); }});

DefMacroI('>',undef,sub{
  my($gullet)=@_;
  if($gullet->ifNext(T_OTHER('>'))){
    $gullet->readToken;
    T_CS('\gg'); }
  elsif($gullet->ifNext(T_OTHER('='))){
    $gullet->readToken;
    T_CS('\geq'); }
  else {
    T_CS('\@@>'); }});

DefMathI('\@@>',undef, '>', role=>'RELOP', meaning=>'greater-than', name=>'', reversion=>'>');
DefMathI('\@@<',undef, '<', role=>'RELOP', meaning=>'less-than',    name=>'', reversion=>'<');

#======================================================================
# Combine letters, when the fonts are right. (sorta related to mathcode)

DefMathLigature(sub { my($document,@nodes)=@_;
		      my @chars = ();
		      my $font = $document->getNodeFont($nodes[$#nodes]);
		      if($font->isSticky){
			my $node;
			my $n=0;
			my $string='';
			my $s='';
			while(($node = pop(@nodes))
			      && ($document->getModel->getNodeQName($node) eq 'ltx:XMTok')
			      && ($document->getNodeFont($node)->equals($font))
			      && (!$node->hasAttribute('name'))
			      && (($node->getAttribute('role')||'UNKNOWN') eq 'UNKNOWN')
			      && (($s=$node->textContent.$s) =~ /^[a-zA-Z]+$/)){
			  $n++; $string = $s; }
			($n > 1 ? ($n, $string, role=>'UNKNOWN') : undef); }});

#======================================================================
# Combine digits in math.

foreach my $digit (qw(0 1 2 3 4 5 6 7 8 9)){
  DefMathI($digit,undef,$digit, role=>'NUMBER',meaning=>$digit); }

our %space_chars = (negthinspace=>'', thinspace=>"\x{2009}",
		    medspace=>"\x{2005}", thickspace=>"\x{2004}");

DefMathLigature(sub { my($document,@nodes)=@_;
		      my @chars = ();
		      my $font = $document->getNodeFont($nodes[$#nodes]);
		      my ($n,$node,$string,$number)=(0,undef,'','');
		      while($node = pop(@nodes)){
			my $qn = $document->getModel->getNodeQName($node);
			if($qn =~ /^(ltx:XMTok|ltx:XMWrap)$/){
			  my $r=($node->getAttribute('role')||'');
			  if($r eq 'NUMBER'){
			    $string = $node->textContent . $string;
			    $number = $node->getAttribute('meaning') . $number; }
			  elsif($r eq 'PERIOD'){
			    $string = $node->textContent . $string;
			    $number = '.'.$number; }
			  else {
			    last; }}
			elsif($qn eq 'ltx:XMHint'){
			  my $s;
			  if(($s = $node->getAttribute('name'))&&($s = $space_chars{$s})){
			    $string = $s . $string; }
			  else {
			    last; }}
			else {
			  last; }
			$n++; }
		      if(($n > 1) && ($number =~ /\d/)){
			($n, $string, meaning=>$number, role=>'NUMBER'); }});


#======================================================================
# TeX Book, Appendix B, p. 345

RawTeX('
  \chardef\active=13
  \chardef\@ne=1
  \chardef\tw@=2
  \chardef\thr@@=3
  \chardef\sixt@@n=16
  \chardef\@cclv=255
  \chardef\@cclvi=256
  \chardef\@m=1000
  \chardef\@M=10000
  \chardef\@MM=20000');

#======================================================================
# TeX Book, Appendix B, p. 346

RawTeX('
  \countdef\count@=255
  \toksdef\toks@=0
  \skipdef\skip@=0
  \dimendef\dimen@i=1
  \dimendef\dimen@ii=2');
# Various \count's are set; should we?

#======================================================================
# TeX Book, Appendix B, p. 347
# \wlog ??
# From plain.tex
DefPrimitive('\newcount  Token',sub { DefRegisterI($_[1],undef,Number(0)); });
DefPrimitive('\newdimen  Token',sub { DefRegisterI($_[1],undef,Dimension(0)); });
DefPrimitive('\newskip   Token',sub { DefRegisterI($_[1],undef,Glue(0)); });
DefPrimitive('\newmuskip Token',sub { DefRegisterI($_[1],undef,MuGlue(0)); });
AssignValue(allocated_boxes=>0);
DefPrimitive('\newbox    Token',sub {
  my $n = LookupValue('allocated_boxes');
  AssignValue(allocated_boxes=>$n+1,'global');
  AssignValue("box$n",LaTeXML::List->new());
  DefRegisterI($_[1],undef,Number($n));  });
DefPrimitive('\newhelp Token {}', sub { AssignValue(ToString($_[1])=>$_[2]); });
DefPrimitive('\newtoks Token',    sub { DefRegisterI($_[1],undef,Tokens()); });
DefPrimitive('\newread Token',    sub { DefRegisterI($_[1],undef,Number(0)); });
DefPrimitive('\newwrite Token',   sub { DefRegisterI($_[1],undef,Number(0)); });
DefPrimitive('\newfam Token',     sub { DefRegisterI($_[1],undef,Number(0)); });
DefPrimitive('\newlanguage Token',sub { DefRegisterI($_[1],undef,Number(0)); });
DefPrimitive('\newinsert Token',  sub { DefRegisterI($_[1],undef,Number(0)); });
# \alloc@, \ch@ck

# TeX plain uses \newdimen, etc. for these.
# Is there any advantage to that?
DefRegister('\maxdimen',Dimension(16383.99999*65536));
DefRegister('\hideskip',Glue(-1000*65536,'1fill'));
DefRegister('\centering',Glue('0pt plus 1000pt minus 1000pt'));
DefRegister('\p@',Dimension(65536));
DefRegister('\z@',Dimension(0));
DefRegister('\z@skip',Glue(0,0,0));

# First approximation. till I figure out \newbox
#  \newbox\voidb@x
DefMacroI('\voidb@x',undef,Tokens());

#======================================================================
# TeX Book, Appendix B, p. 348

DefMacro('\newif DefToken',sub {
  my($ignore,$cs)=@_;
  my $name = ToString($cs); $name =~ s/^\\if//;
  DefMacroI($cs,undef, sub { ifHandler($_[0],LookupValue('Boolean:'.$name)); }, isConditional=>1);
  DefPrimitiveI(T_CS('\\'.$name.'true'),undef,, sub { AssignValue('Boolean:'.$name => 1); });
  DefPrimitiveI(T_CS('\\'.$name.'false'),undef,sub { AssignValue('Boolean:'.$name => 0); });  });

# See the section Registers & Parameters, above for setting default values.
#======================================================================
# TeX Book, Appendix B, p. 349
# See the section Registers & Parameters, above for setting default values.

# These are originally defined with \newskip, etc
DefRegister('\smallskipamount' => Glue('3pt plus1pt minus1pt'));
DefRegister('\medskipamount'   => Glue('6pt plus2pt minus2pt'));
DefRegister('\bigskipamount'   => Glue('12pt plus4pt minus4pt'));
DefRegister('\normalbaselineskip' => Glue('12pt'));
DefRegister('\normallineskip'  => Glue('1pt'));
DefRegister('\normallineskiplimit'=> Dimension('0pt'));
DefRegister('\jot'  => Dimension('3pt'));
DefRegister('\interdisplaylinepenalty' => Number(100));
DefRegister('\interfootnotelinepenalty' => Number(100));

DefMacroI('\magstephalf',undef,'1095');
our @mags=(1000,1200,1440,1728,2074,2488);
DefMacro('\magstep{}', sub { Explode($mags[ToString($_[1])]);});

#======================================================================
# TeX Book, Appendix B, p. 350

# Font stuff ...
RawTeX('
 \font\tenrm=cmr10
 \font\sevenrm=cmr7
 \font\fiverm=cmr5
 \font\teni=cmmi10
 \font\seveni=cmmi7
 \font\fivei=cmmi7
 \font\tensy=cmsy10
 \font\sevensy=cmsy7
 \font\fivesy=cmsy5
 \font\tenex=cmex10
 \font\tenbf=cmbx10
 \font\sevenbf=cmbx7
 \font\fivebf=cmbx5
 \font\tensl=cmsl10
 \font\tentt=cmtt10
 \font\tenit=cmti10
 \newfam\itfam
 \newfam\slfam
 \newfam\bffam
 \newfam\ttfam
 ');
# Note: \newfam in math should be font switching(?)

#======================================================================
# TeX Book, Appendix B, p. 351

# Old style font styles.
# The trick is to create an empty Whatsit preserved till assimilation (for reversion'ing)
# but to change the current font used in boxes.
# (some of these were defined on different pages? or even latex...)
Tag('ltx:text',autoOpen=>1,autoClose=>1);

# Note that these, unlike \rmfamily, should set the other attributes to the defaults!
DefConstructorI('\rm', undef,'',  font=>{family=>'serif', series=>'medium', shape=>'upright'});
DefConstructorI('\sf', undef,'',  font=>{family=>'sansserif', series=>'medium', shape=>'upright'});
DefConstructorI('\bf', undef, '', font=>{series=>'bold',  family=>'serif', shape=>'upright'});
DefConstructorI('\it', undef, '', font=>{shape=>'italic', family=>'serif', series=>'medium'});
DefConstructorI('\tt', undef, '', font=>{family=>'typewriter', series=>'medium', shape=>'upright'});
# No effect in math for the following 2 ?
DefConstructorI('\sl', undef, '', font=>{shape=>'slanted',   family=>'serif', series=>'medium'});
DefConstructorI('\sc', undef, '', font=>{shape=>'smallcaps', family=>'serif', series=>'medium'});

DefConstructorI('\tiny',          undef,'', font=>{size=>'tiny'});
DefConstructorI('\scriptsize',    undef,'', font=>{size=>'script'});
DefConstructorI('\footnotesize',  undef,'', font=>{size=>'footnote'});
DefConstructorI('\small',         undef,'', font=>{size=>'small'});
DefConstructorI('\normalsize',    undef,'', font=>{size=>'normal'});
DefConstructorI('\large',         undef,'', font=>{size=>'large'});
DefConstructorI('\Large',         undef,'', font=>{size=>'Large'});
DefConstructorI('\LARGE',         undef,'', font=>{size=>'LARGE'});
DefConstructorI('\huge',          undef,'', font=>{size=>'huge'});
DefConstructorI('\Huge',          undef,'', font=>{size=>'Huge'});

DefConstructor('\mit',       '',   requireMath=>1, font=>{family=>'italic'});

DefPrimitiveI('\frenchspacing',   undef,undef);
DefPrimitiveI('\nonfrenchspacing',undef,undef);
DefMacroI('\normalbaselines',undef,
	 '\lineskip=\normallineskip\baselineskip=\normalbaselineskip\lineskiplimit=\normallineskiplimit');
DefMacroI('\space',undef,Tokens(T_SPACE));
DefMacroI('\lq',undef,"`");
DefMacroI('\rq',undef,"'");
Let(T_CS('\empty'),T_CS('\@empty'));
DefMacroI('\null',undef,'\hbox{}');
Let(T_CS('\bgroup'),T_BEGIN);
Let(T_CS('\egroup'),T_END);

DefPrimitiveI('\endline', undef,undef);

#======================================================================
# TeX Book, Appendix B, p. 352

DefPrimitiveI('\obeyspaces', undef,undef);
DefPrimitiveI('\obeylines',  undef,undef);

RawTeX(' \def\loop#1\repeat{\def\body{#1}\iterate}
         \def\iterate{\body \let\next=\iterate \else\let\next=\relax\fi \next}
         \let\repeat=\fi ');

DefConstructorI('\enskip',  undef,"?#isMath(<ltx:XMHint name='enskip'/>)(\x{2000})",
	       properties=>{isSpace=>1}); # EN QUAD
DefConstructorI('\enspace', undef,"?#isMath(<ltx:XMHint name='enskip'/>)(\x{2000})",
	      properties=>{isSpace=>1}); # EN QUAD
DefConstructorI('\quad',   undef,"?#isMath(<ltx:XMHint name='quad'/>)(\x{2001})",
	      properties=>{isSpace=>1}); # EM QUAD
DefConstructorI('\qquad',  undef,"?#isMath(<ltx:XMHint name='qquad'/>)(\x{2001}\x{2001})",
	      properties=>{isSpace=>1}); # EM QUAD

DefConstructorI('\thinspace',   undef,"?#isMath(<ltx:XMHint name='thinspace'/>)( )",
	      properties=>{isSpace=>1});
DefConstructorI('\negthinspace',undef,"?#isMath(<ltx:XMHint name='negthinspace'/>)()",
	      properties=>{isSpace=>1});
DefConstructorI('\thickspace',  undef,"?#isMath(<ltx:XMHint name='thickspace'/>)( )",
	      properties=>{isSpace=>1});

DefConstructor('\hglue Glue', "?#isMath(<ltx:XMHint name='#1'/>)(\x{2001})",
	      properties=>{isSpace=>1}); # EM QUAD
DefPrimitive('\vglue Glue', undef);
DefPrimitiveI('\topglue',         undef,undef);
DefPrimitiveI('\nointerlineskip', undef,undef);
DefPrimitiveI('\offinterlineskip',undef,undef);

DefConstructorI('\smallskip', undef,"");
DefConstructorI('\medskip',   undef,"");
DefConstructorI('\bigskip',   undef,"");

#======================================================================
# TeX Book, Appendix B, p. 353

DefPrimitiveI('\break',     undef,undef);
DefPrimitiveI('\nobreak',   undef,undef);
DefPrimitiveI('\allowbreak',undef,undef);
DefConstructorI("~",  undef,"?#isMath(<ltx:XMHint name='nobreakspace'/>)(".UTF(0xA0).")",
	      properties=>{isSpace=>1}); # NO-BREAK SPACE
DefConstructorI('\nobreakspace',undef,"?#isMath(<ltx:XMHint name='nobreakspace'/>)(".UTF(0xA0).")",
	      properties=>{isSpace=>1}); # NO-BREAK SPACE

DefMacroI('\slash',undef,'/');
DefPrimitiveI('\filbreak',undef,undef);
DefMacroI('\goodbreak',undef,'\par');
DefMacroI('\eject',undef,'\par');
Let(T_CS('\newpage'),'\eject');
DefMacroI('\supereject',undef,'\par');
DefPrimitiveI('\removelastskip',undef,undef);
DefMacroI('\smallbreak',undef,'\par');
DefMacroI('\medbreak',  undef,'\par');
DefMacroI('\bigbreak',  undef,'\par');

DefMacroI('\line',undef,'\hbox to \hsize');
DefMacro('\leftline{}','\line{#1\hss}');
DefMacro('\rightline{}','\line{\hss#1}');
DefConstructor('\centerline{}',sub {
#		 "<ltx:p class='centering'>#1</ltx:p>"
  my($document,$line)=@_;
  if($document->isOpenable('ltx:p')){
    $document->insertElement('ltx:p',$line,class=>'centering'); }
  elsif($document->isOpenable('ltx:text')){
    $document->insertElement('ltx:text',$line,class=>'centering');
    $document->insertElement('ltx:break');  }
  else {
    $document->absorb($line); }},
	       bounded=>1);
  

DefMacro('\llap{}','\hbox{#1}');
DefMacro('\rlap{}','\hbox{#1}');
DefMacroI('\m@th',undef,'\mathsurround=0pt ');

# \strutbox
DefMacroI('\strut',undef,Tokens());
RawTeX('\newbox\strutbox');

#======================================================================
# TeX Book, Appendix B. p. 354

# TODO: Not yet done!!
# tabbing stuff!!!

DefMacroI('\settabs', undef,undef);

#======================================================================
# TeX Book, Appendix B. p. 355

DefPrimitive('\hang',undef);

# TODO: \item, \itemitem not done!
# This could probably be adopted from LaTeX, if the <itemize> could auto-open
# and close!
DefConstructor('\item{}','#1');
DefConstructor('\itemitem{}','#1');
DefConstructor('\makelabel{}','#1');

DefMacro('\textindent{}','#1');

# Conceivably this should enclose the next para in a block?
# Or add attribute to it? Or...
DefPrimitiveI('\narrower',undef,undef);

#----------------------------------------------------------------------
# General support for Front Matter.
# Not (yet) used by TeX (finish plain?)
# But provides support for LaTeX (and other formats?) for handling frontmatter.
#
# The idea is to accumulate any frontmatter material (title, author,...)
# rather than directly drop it into the digested stream.
# When we begin constructing the document, all accumulated material is output.
# See LaTeX.ltxml for usage.

# Note: could be circumstances where you'd want modular frontmatter?
# (ie. frontmatter for each sectional unit)
AssignValue(frontmatter=>{},'global');

# Add a new frontmatter item that will be enclosed in <$tag %attr>...</$tag>
# The content is the result of digesting $tokens.
# \@add@frontmatter{tag}[attributes]{content}
DefPrimitive('\@add@frontmatter {} OptionalKeyVals {}', sub {
  my($stomach,$tag,$keys,$tokens)=@_;
  $tag = ToString($tag);
  my $attr = ($keys ? {$keys->beDigested($stomach)->getHash} : undef);

  my $frontmatter = LookupValue('frontmatter');
  my $inpreamble = LookupValue('inPreamble');
  AssignValue(inPreamble=>0);
  my $datum = Digest(Tokens(T_BEGIN,$tokens,T_END));
  AssignValue(inPreamble=>$inpreamble);
  push(@{$$frontmatter{$tag}},[$tag,$attr,$datum]);
  return; });

# Append a piece of data to an existing frontmatter item that is contained in <$tag>
# If $label is given, look for an item which has label=>$label,
# otherwise, just append to the last item in $tag.

# \@add@to@frontmatter{tag}[label]{content}
DefPrimitive('\@add@to@frontmatter {} [] {}', sub {
  my($stomach,$tag,$label,$tokens)=@_;
  $tag   = ToString($tag);
  $label = ToString($label) if $label;
  my $frontmatter = LookupValue('frontmatter');

  my $inpreamble = LookupValue('inPreamble');
  AssignValue(inPreamble=>0);
  my $datum = Digest(Tokens(T_BEGIN,$tokens,T_END));
  AssignValue(inPreamble=>$inpreamble);

  if($label){
    my $entry;
    foreach my $item (@{$$frontmatter{$tag} || []}){
      my($itag,$iattr,@stuff)=@$item;
      if($label eq ($$iattr{label}||'')){
	push(@$item,$datum); 
	return; }}}
  elsif(my $list = $$frontmatter{$tag}){
    push(@{$$list[-1]},$datum); 
    return;}
  push(@{$$frontmatter{$tag}},[$tag,($label ? {label=>$label} : undef),$datum]);
  return; });

# This is called by afterOpen (by default on <ltx:document>) to
# output any frontmatter that was accumulated.

my @frontmatter_elements = (qw(ltx:title ltx:toctitle ltx:subtitle
			     ltx:creator ltx:date
			   ltx:abstract ltx:keywords ltx:classification ltx:acknowledgements));
my %frontmatter_elements = map(($_=>1),@frontmatter_elements);
sub insertFrontMatter {
  my($document)=@_;
  my $frontmatter = LookupValue('frontmatter');
  foreach my $key (@frontmatter_elements,grep(!$frontmatter_elements{$_},keys %$frontmatter)){
    if(my $list = $$frontmatter{$key}){
      # Dubious, but assures that frontmatter appears in text mode...
      local $LaTeXML::BOX = LaTeXML::Box->new('', $STATE->lookupValue('font'),'',T_SPACE);
      foreach my $item (@$list){
	my($tag,$attr,@stuff)=@$item;
	$document->openElement($tag,($attr ? %$attr : ()));
	map($document->absorb($_), @stuff);
	$document->closeElement($tag); }}}
  return; }

Tag('ltx:document', afterOpen=>\&insertFrontMatter);

DefConstructor('\beginsection{}',
	       "<ltx:section><ltx:title>#1</ltx:title>");
# \proclaim ?
# TODO: Not yet done; actually is much like LaTeX's theorem env.

#======================================================================
# TeX Book, Appendix B. p. 356

DefPrimitiveI('\raggedright',   undef,undef);
DefPrimitiveI('\raggedleft',    undef,undef);  # this is actually LaTeX
DefPrimitiveI('\ttraggedright', undef,undef);
DefPrimitiveI('\leavevmode',    undef,undef);

#----------------------------------------------------------------------
# Actually from LaTeX; Table 3.2. Non-English Symbols, p.39

# The following shouldn't appear in math.
DefConstructorI('\OE', undef,"\x{0152}");	# LATIN CAPITAL LIGATURE OE
DefConstructorI('\oe', undef,"\x{0153}");	# LATIN SMALL LIGATURE OE
DefConstructorI('\AE', undef,UTF(0xC6));	# LATIN CAPITAL LETTER AE
DefConstructorI('\ae', undef,UTF(0xE6));	# LATIN SMALL LETTER AE
DefConstructorI('\AA', undef,UTF(0xC5));	# LATIN CAPITAL LETTER A WITH RING ABOVE
DefConstructorI('\aa', undef,UTF(0xE5));	# LATIN SMALL LETTER A WITH RING ABOVE
DefConstructorI('\O',  undef,UTF(0xD8));	# LATIN CAPITAL LETTER O WITH STROKE
DefConstructorI('\o',  undef,UTF(0xF8));	# LATIN SMALL LETTER O WITH STROKE
DefConstructorI('\L',  undef,"\x{0141}");	# LATIN CAPITAL LETTER L WITH STROKE
DefConstructorI('\l',  undef,"\x{0142}");	# LATIN SMALL LETTER L WITH STROKE
DefConstructorI('\ss', undef,UTF(0xDF));	# LATIN SMALL LETTER SHARP S

# apparently the rest can appear in math.
DefConstructorI('\dag',   undef,"\x{2020}");	# DAGGER
DefConstructorI('\ddag',  undef,"\x{2021}");	# DOUBLE DAGGER
DefConstructorI('\S',     undef,UTF(0xA7));	# SECTION SIGN
DefConstructorI('\P',     undef,UTF(0xB6));	# PILCROW SIGN
DefConstructorI('\copyright',undef,UTF(0xA9)); # COPYRIGHT SIGN
DefConstructorI('\pounds',undef,UTF(0xA3));	# POUND SIGN

#----------------------------------------------------------------------
# Accents.  LaTeX Table 3.1, p.38
#----------------------------------------------------------------------
# All of TeX's accents can (sorta) be handled by Unicode's combining accents
# (which follow the character to be accented).
# We'll let unicode normalization do the combination, if needed.
# Also, note that \t is intended to combine multiple chars, but it appears to
# work (via mozilla !?) best when the combining char is after the 1st char.
# Further, the accents \d and \b seem to center the under dot or bar under multiple
# chars --- how should this be handled in Unicode?

# Since people sometimes try to get fancy by using an empty argument,
# for each, I'm providing the combining code and an equivalent(?) spacing one.
our %accents
  = ('\`' => ["\x{0300}",UTF(0x60)], # COMBINING GRAVE ACCENT & GRAVE ACCENT
     "\\'"=> ["\x{0301}",UTF(0xB4)], # COMBINING ACUTE ACCENT & ACUTE ACCENT
     '\^' => ["\x{0302}",UTF(0x5E)], # COMBINING CIRCUMFLEX ACCENT & CIRCUMFLEX ACCENT
     '\"' => ["\x{0308}",UTF(0xA8)], # COMBINING DIAERESIS & DIAERESIS
     '\~' => ["\x{0303}","~"],	      # COMBINING TILDE
     '\=' => ["\x{0304}",UTF(0xAF)], # COMBINING MACRON & MACRON
     '\.' => ["\x{0307}",UTF(0xB7)], # COMBINING DOT ABOVE & MIDDLE DOT (?)
     '\u' => ["\x{0306}","\x{02D8}"], # COMBINING BREVE & BREVE
     '\v' => ["\x{030C}","\x{02C7}"], # COMBINING CARON & CARON
     '\@ringaccent' => ["\x{030A}","o"], # COMBINING RING ABOVE & non-combining
     '\r' => ["\x{030A}","o"], # COMBINING RING ABOVE & non-combining
     '\H' => ["\x{030B}","\x{02DD}"], # COMBINING DOUBLE ACUTE ACCENT & non-combining
     '\c' => ["\x{0327}",UTF(0xB8)], # COMBINING CEDILLA & CEDILLA
     '\d' => ["\x{0323}",UTF(0xB7)], # COMBINING DOT BELOW & MIDDLE DOT (?)
     '\b' => ["\x{0331}",UTF(0xAF)], # COMBINING MACRON BELOW  & MACRON
     '\t' => ["\x{0361}","-"],	# COMBINING DOUBLE INVERTED BREVE & ???? What????
     # this one's actually defined in mathscinet.sty, but just stick it here!
     '\lfhook'=>["\x{0326}",","], # COMBINING COMMA BELOW
     # I doubt that latter covers multiple chars...?
     #		'\bar'=>"\x{0304}", # COMBINING MACRON or is this the longer overbar?
		);

# Defines an accent command using a combining char that follows the
# 1st char of the argument.  In cases where there is no argument, $standalonechar is used.
sub DefAccent {
  my ($accent, $combiningchar, $standalonechar) = @_;
  DefConstructor($accent."{}", sub {
    my($document,$letter,%props)=@_;
    if(my @letters = map(($_ eq '\i' ? 'i':$_),map(ToString($_),$letter->unlist))){
      # Put the accent AFTER the first char(?)
      $document->openText(NFC($letters[0].$combiningchar.join('',@letters[1..$#letters])),
			  $letter->getFont);}
    else {			# Empty argument???
       $document->openText($standalonechar,$props{font}); }} ); }

foreach my $accent (keys %accents) {
  DefAccent($accent, @{$accents{$accent}}); }

#======================================================================
# TeX Book, Appendix B. p. 357

foreach my $op ('\hrulefill','\dotfill','\rightarrowfill','\leftarrowfill',
		'\upbracefill','\downbracefill'){
  DefPrimitive($op,undef); }

Let(T_CS('\bye'),T_CS('\end'));

Let(T_CS('\sp'),T_SUPER);
Let(T_CS('\sb'),T_SUB);
DefConstructorI('\,',undef,"?#isMath(<ltx:XMHint name='thinspace'/>)(\x{2009})",
	      properties=>{isSpace=>1}); # THIN SPACE
DefConstructorI('\!',undef,"?#isMath(<ltx:XMHint name='negthinspace'/>)()",
	       properties=>{isSpace=>1});
DefConstructorI('\>',undef,"?#isMath(<ltx:XMHint name='\>'/>)()",
	      properties=>{isSpace=>1});	# ??
DefConstructorI('\;',undef,"?#isMath(<ltx:XMHint name='thickspace'/>)(\x{2004})",
	      properties=>{isSpace=>1}); # THREE-PER-EM SPACE
DefConstructorI('\:',undef,"?#isMath(<ltx:XMHint name='medspace'/>)(\x{2005})",
	      properties=>{isSpace=>1});  # FOUR-PER-EM SPACE
DefConstructorI('\ ',undef,"?#isMath(<ltx:XMHint name='medspace'/>)(".UTF(0xA0).")",
	      properties=>{isSpace=>1}); # EN SPACE
DefConstructorI('\/',undef,"?#isMath(<ltx:XMHint name='italiccorr'/>)()",
	      properties=>{isSpace=>1});

# What kind of magic might allow \mskip to translate these back into the above?
DefMacroI('\thinmuskip',undef, "3 mu");
DefMacroI('\medmuskip',  undef,"4mu plus 2mu minus 4mu");
DefMacroI('\thickmuskip',undef,"5mu plus 5mu");

#======================================================================
# TeX Book, Appendix B. p. 358

#----------------------------------------------------------------------
#  Actually from LaTeX; Table 3.3, Greek, p.41
#----------------------------------------------------------------------
DefMathI('\alpha',     undef,"\x{03B1}");
DefMathI('\beta',      undef,"\x{03B2}");
DefMathI('\gamma',     undef,"\x{03B3}");
DefMathI('\delta',     undef,"\x{03B4}");
DefMathI('\epsilon'  , undef,"\x{03F5}");
DefMathI('\varepsilon',undef,"\x{03B5}");
DefMathI('\zeta',      undef,"\x{03B6}");
DefMathI('\eta',       undef,"\x{03B7}");
DefMathI('\theta',     undef,"\x{03B8}");
DefMathI('\vartheta',  undef,"\x{03D1}");
DefMathI('\iota',      undef,"\x{03B9}");
DefMathI('\kappa',     undef,"\x{03BA}");
DefMathI('\lambda',    undef,"\x{03BB}");
DefMathI('\mu',        undef,"\x{03BC}");
DefMathI('\nu',        undef,"\x{03BD}");
DefMathI('\xi',        undef,"\x{03BE}");
DefMathI('\pi',        undef,"\x{03C0}");
DefMathI('\varpi',     undef,"\x{03D6}");
DefMathI('\rho',       undef,"\x{03C1}");
DefMathI('\varrho',    undef,"\x{03F1}");
DefMathI('\sigma',     undef,"\x{03C3}");
DefMathI('\varsigma',  undef,"\x{03C2}");
DefMathI('\tau',       undef,"\x{03C4}");
DefMathI('\upsilon',   undef,"\x{03C5}");
DefMathI('\phi',       undef,"\x{03D5}");
DefMathI('\varphi',    undef,"\x{03C6}");
DefMathI('\chi',       undef,"\x{03C7}");
DefMathI('\psi',       undef,"\x{03C8}");
DefMathI('\omega',     undef,"\x{03C9}");
DefMathI('\Gamma',     undef,"\x{0393}");
DefMathI('\Delta',     undef,"\x{0394}");
DefMathI('\Theta',     undef,"\x{0398}");
DefMathI('\Lambda',    undef,"\x{039B}");
DefMathI('\Xi',        undef,"\x{039E}");
DefMathI('\Pi',        undef,"\x{03A0}");
DefMathI('\Sigma',     undef,"\x{03A3}");
DefMathI('\Upsilon',   undef,"\x{03A5}");
DefMathI('\Phi',       undef,"\x{03A6}");
DefMathI('\Psi',       undef,"\x{03A8}");
DefMathI('\Omega',     undef,"\x{03A9}");

#----------------------------------------------------------------------
# Actually from LaTeX; Table 3.7. Miscellaneous Symbols, p.43
#----------------------------------------------------------------------
# Some should be differential operators, qualifiers, ...
DefMathI('\aleph',    undef,"\x{2135}");
DefMathI('\hbar',     undef,"\x{210F}", role=>'ID', meaning=>'Planck-constant-over-2-pi');
DefMathI('\imath',    undef,"\x{0131}");
DefMathI('\jmath',    undef,"j");
DefMathI('\ell',      undef,"\x{2113}");
DefMathI('\wp',       undef,"\x{2118}", meaning=>'Weierstrass-p');
DefMathI('\Re',       undef,"\x{211C}", role=>'OPFUNCTION', meaning=>'real-part');
DefMathI('\Im',       undef,"\x{2111}", role=>'OPFUNCTION', meaning=>'imaginary-part');
DefMathI('\mho',      undef,"\x{2127}");

DefMathI('\prime',    undef,"\x{2032}", role=>'SUPOP');
DefMathI('\emptyset', undef,"\x{2205}", role=>'ID', meaning=>'empty-set');
DefMathI('\nabla',    undef,"\x{2207}", role=>'OPERATOR');
DefMathI('\surd',     undef,"\x{221A}", role=>'OPERATOR', meaning=>'root');
DefMathI('\top',      undef,"\x{22A4}", role=>'ADDOP', meaning=>'top');
DefMathI('\bot',      undef,"\x{22A5}", role=>'ADDOP', meaning=>'bottom');
DefMathI('\|',        undef,"\x{2225}", role=>'VERTBAR', name=>'||', meaning=>'parallel-to');
DefMathI('\angle',    undef,"\x{2220}");

# NOTE: This is probably the wrong role.
# Also, should probably carry info about Binding for OpenMath
DefMathI('\forall',   undef,"\x{2200}", role=>'BIGOP', meaning=>'for-all');
DefMathI('\exists',   undef,"\x{2203}", role=>'BIGOP', meaning=>'exists');
DefMathI('\neg',      undef,UTF(0xAC), role=>'FUNCTION', meaning=>'not');
DefMathI('\lnot',     undef,UTF(0xAC), role=>'FUNCTION', meaning=>'not');
DefMathI('\flat',     undef,"\x{266D}");
DefMathI('\natural',  undef,"\x{266E}");
DefMathI('\sharp',    undef,"\x{266F}");
DefMathI('\backslash',undef,UTF(0x5C), role=>'MULOP');
DefMathI('\partial',  undef,"\x{2202}", role=>'OPERATOR', meaning=>'partial-differential');

DefMathI('\infty',    undef,"\x{221E}", role=>'ID', meaning=>'infinity');
DefMathI('\Box',      undef,"\x{25A1}");
DefMathI('\Diamond',  undef,"\x{25C7}");
DefMathI('\triangle', undef,"\x{25B3}");
DefMathI('\clubsuit', undef,"\x{2663}");
DefMathI('\diamondsuit',undef,"\x{2662}");
DefMathI('\heartsuit',undef,"\x{2661}");
DefMathI('\spadesuit',undef,"\x{2660}");

#----------------------------------------------------------------------
DefMath('\smallint',"\x{222B}", meaning=>'integral', role=>'INTOP', 
	font=>{size=>'small'}, scriptpos=>\&doScriptpos); # INTEGRAL

#----------------------------------------------------------------------
# Actually LaTeX; Table 3.8. Variable-sized Symbols, p.44.
#----------------------------------------------------------------------
sub doScriptpos { (LookupValue('mathstyle') eq 'display' ? 'mid' : 'post'); }

DefMathI('\sum',      undef,"\x{2211}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'sum');
DefMathI('\prod',     undef,"\x{220F}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'product');
DefMathI('\coprod',   undef,"\x{2210}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'coproduct');
DefMathI('\int',      undef,"\x{222B}", role=>'INTOP',	                          meaning=>'integral');
DefMathI('\oint',     undef,"\x{222E}", role=>'INTOP',	                          meaning=>'contour-integral');
DefMathI('\bigcap',   undef,"\x{22C2}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'intersection');
DefMathI('\bigcup',   undef,"\x{22C3}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'union');
DefMathI('\bigsqcup', undef,"\x{2294}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'square-union');
DefMathI('\bigvee',   undef,"\x{22C1}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'or');
DefMathI('\bigwedge', undef,"\x{22C0}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'and');
DefMathI('\bigodot',  undef,"\x{22A1}", role=>'SUMOP',	scriptpos=>\&doScriptpos); #meaning=> ?
DefMathI('\bigotimes',undef,"\x{2297}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'tensor-product');
DefMathI('\bigoplus', undef,"\x{2295}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'direct-sum');
DefMathI('\biguplus', undef,"\x{2296}", role=>'SUMOP',	scriptpos=>\&doScriptpos, meaning=>'symmetric-difference');

DefConstructorI('\limits', undef,sub {
   my $node = $_[0]->getNode;
   $_[0]->setAttribute($_[0]->getLastChildElement($node)||$node,scriptpos=>'mid'); });
DefConstructorI('\nolimits',undef,sub {
   my $node = $_[0]->getNode;
   $_[0]->setAttribute($_[0]->getLastChildElement($node)||$node,scriptpos=>'post'); });

#----------------------------------------------------------------------
# Actually from LaTeX; Table 3.4. Binary Operation Symbols, p.42
#----------------------------------------------------------------------
DefMathI('\pm',        undef,UTF(0xB1),  role=>'ADDOP', meaning=>'plus-or-minus');
DefMathI('\mp',        undef,"\x{2213}", role=>'ADDOP', meaning=>'minus-or-plus');
DefMathI('\times',     undef,UTF(0xD7),  role=>'MULOP', meaning=>'times');
DefMathI('\div',       undef,UTF(0xF7),  role=>'MULOP', meaning=>'divide');
DefMathI('\ast',       undef,"\x{2217}", role=>'MULOP');
DefMathI('\star',      undef,"\x{22C6}", role=>'MULOP');
DefMathI('\circ',      undef,"\x{2218}", role=>'MULOP', meaning=>'compose');
DefMathI('\bullet',    undef,"\x{2219}", role=>'MULOP');
DefMathI('\cdot',      undef,"\x{22C5}", role=>'MULOP');
   ##  , meaning=>'inner-product');  that's pushing it a bit far...

# Need to classify set operations more carefully....
DefMathI('\cap',       undef,"\x{2229}", role=>'ADDOP', meaning=>'intersection');
DefMathI('\cup',       undef,"\x{222A}", role=>'ADDOP', meaning=>'union');
DefMathI('\uplus',     undef,"\x{228C}", role=>'ADDOP');
DefMathI('\sqcap',     undef,"\x{2293}", role=>'ADDOP', meaning=>'square-intersection');
DefMathI('\sqcup',     undef,"\x{2294}", role=>'ADDOP', meaning=>'square-union');
DefMathI('\vee',       undef,"\x{2228}", role=>'ADDOP', meaning=>'or');
DefMathI('\lor',       undef,"\x{2228}", role=>'ADDOP', meaning=>'or');
DefMathI('\wedge',     undef,"\x{2227}", role=>'ADDOP', meaning=>'and');
DefMathI('\land',      undef,"\x{2227}", role=>'ADDOP', meaning=>'and');
DefMathI('\setminus',  undef,"\x{2216}", role=>'ADDOP', meaning=>'set-minus');
DefMathI('\wr',        undef,"\x{2240}", role=>'MULOP');

# Should this block be ADDOP or something else?
DefMathI('\diamond',        undef,"\x{22C4}", role=>'ADDOP');
DefMathI('\bigtriangleup',  undef,"\x{25B3}", role=>'ADDOP');
DefMathI('\bigtriangledown',undef,"\x{25BD}", role=>'ADDOP');
DefMathI('\triangleleft',   undef,"\x{25C1}", role=>'ADDOP');
DefMathI('\triangleright',  undef,"\x{25B7}", role=>'ADDOP');
DefMathI('\lhd',            undef,"\x{22B2}", role=>'ADDOP', meaning=>'subgroup-of');
DefMathI('\rhd',            undef,"\x{22B3}", role=>'ADDOP', meaning=>'contains-as-subgroup');
DefMathI('\unlhd',          undef,"\x{22B4}", role=>'ADDOP', meaning=>'subgroup-of-or-equals');
DefMathI('\unrhd',          undef,"\x{22B5}", role=>'ADDOP', meaning=>'contains-as-subgroup-or-equals');

DefMathI('\oplus',     undef,"\x{2295}", role=>'ADDOP', meaning=>'direct-sum');
DefMathI('\ominus',    undef,"\x{2296}", role=>'ADDOP', meaning=>'symmetric-difference');
DefMathI('\otimes',    undef,"\x{2297}", role=>'MULOP', meaning=>'tensor-product');
DefMathI('\oslash',    undef,"\x{2298}", role=>'MULOP');
DefMathI('\odot',      undef,"\x{2299}", role=>'MULOP', meaning=>'direct-product');
DefMathI('\bigcirc',   undef,"\x{25CB}", role=>'MULOP');
DefMathI('\dagger',    undef,"\x{2020}", role=>'MULOP');
DefMathI('\ddagger',   undef,"\x{2021}", role=>'MULOP');
DefMathI('\amalg',     undef,"\x{2210}", role=>'MULOP', meaning=>'coproduct');

#----------------------------------------------------------------------
# LaTeX; Table 3.5. Relation Symbols, p.43
#----------------------------------------------------------------------
DefMathI('\le',        undef,"\x{2264}", role=>'RELOP', meaning=>'less-than-or-equals');
DefMathI('\ge',        undef,"\x{2265}", role=>'RELOP', meaning=>'greater-than-or-equals');
DefMathI('\ne',        undef,"\x{2260}", role=>'RELOP', meaning=>'not-equals');
 
DefMathI('\leq',       undef,"\x{2264}", role=>'RELOP', meaning=>'less-than-or-equals');
DefMathI('\prec',      undef,"\x{227A}", role=>'RELOP', meaning=>'precedes');
DefMathI('\preceq',    undef,"\x{2AAF}", role=>'RELOP', meaning=>'precedes-or-equals');
DefMathI('\ll',        undef,"\x{226A}", role=>'RELOP', meaning=>'much-less-than');
DefMathI('\subset',    undef,"\x{2282}", role=>'RELOP', meaning=>'subset-of');
DefMathI('\subseteq',  undef,"\x{2286}", role=>'RELOP', meaning=>'subset-of-or-equals');
DefMathI('\sqsubset',  undef,"\x{228F}", role=>'RELOP', meaning=>'square-image-of');
DefMathI('\sqsubseteq',undef,"\x{2291}", role=>'RELOP', meaning=>'square-image-of-or-equals');
DefMathI('\in',        undef,"\x{2208}", role=>'RELOP', meaning=>'element-of');
DefMathI('\vdash',     undef,"\x{22A2}", role=>'METARELOP', meaning=>'proves');

DefMathI('\geq',       undef,"\x{2265}", role=>'RELOP', meaning=>'greater-than-or-equals');
DefMathI('\succ',      undef,"\x{227B}", role=>'RELOP', meaning=>'succeeds');
DefMathI('\succeq',    undef,"\x{2AB0}", role=>'RELOP', meaning=>'succeeds-or-equals');
DefMathI('\gg',        undef,"\x{226B}", role=>'RELOP', meaning=>'much-greater-than');
DefMathI('\supset',    undef,"\x{2283}", role=>'RELOP', meaning=>'superset-of');
DefMathI('\supseteq',  undef,"\x{2287}", role=>'RELOP', meaning=>'superset-of-or-equals');
DefMathI('\sqsupset',  undef,"\x{2290}", role=>'RELOP', meaning=>'square-original-of');
DefMathI('\sqsupseteq',undef,"\x{2292}", role=>'RELOP', meaning=>'square-original-of-or-equals');
DefMathI('\ni',        undef,"\x{220B}", role=>'RELOP', meaning=>'contains');
DefMathI('\dashv',     undef,"\x{22A3}", role=>'METARELOP', meaning=>'does-not-prove');

DefMathI('\equiv',     undef,"\x{2261}", role=>'RELOP', meaning=>'identical-to');
DefMathI('\sim',       undef,"\x{223C}", role=>'RELOP', meaning=>'similar-to');
DefMathI('\simeq',     undef,"\x{2243}", role=>'RELOP', meaning=>'similar-to-or-equals');
DefMathI('\asymp',     undef,"\x{224D}", role=>'RELOP', meaning=>'asymptotically-equals');
DefMathI('\approx',    undef,"\x{2248}", role=>'RELOP', meaning=>'almost-equals');
DefMathI('\cong',      undef,"\x{2245}", role=>'RELOP', meaning=>'approximately-equals');
DefMathI('\neq',       undef,"\x{2260}", role=>'RELOP', meaning=>'not-equals');
DefMathI('\doteq',     undef,"\x{2250}", role=>'RELOP', meaning=>'approaches-limit');
DefMathI('\notin',     undef,"\x{2209}", role=>'RELOP', meaning=>'not-element-of');

DefMathI('\models',    undef,"\x{22A7}", role=>'RELOP', meaning=>'models');
DefMathI('\perp',      undef,"\x{27C2}", role=>'RELOP', meaning=>'perpendicular-to');
DefMathI('\mid',       undef,"\x{2223}", role=>'VERTBAR'); # DIVIDES (RELOP?) ?? well, sometimes...
DefMathI('\parallel',  undef,"\x{2225}", role=>'VERTBAR', meaning=>'parallel-to');
DefMathI('\bowtie',    undef,"\x{22C8}", role=>'RELOP');   # BOWTIE
DefMathI('\Join',      undef,"\x{2A1D}", role=>'RELOP', meaning=>'join');
DefMathI('\smile',     undef,"\x{2323}", role=>'RELOP');   # SMILE
DefMathI('\frown',     undef,"\x{2322}", role=>'RELOP');   # FROWN
DefMathI('\propto',    undef,"\x{221D}", role=>'RELOP', meaning=>'proportional-to');

DefMathI('\not',undef,"not", role=>'PREFIX', meaning=>'not');

#----------------------------------------------------------------------
# Match negations of many operators

our %NOTS = ('='=>"\x{2260}", '<'=> "\x{226E}", '>'=>"\x{226F}",
	     "\x{2208}"=>"\x{2209}", #\in=>\notin
	     "\x{2264}"=>"\x{2270}", "\x{2265}"=>"\x{2271}", # Less eq, greater eq.
	     "\x{227A}"=>"\x{2280}", "\x{227B}"=>"\x{2281}", # prec, succ
	     "\x{2AAF}"=>"\x{22E0}", "\x{2AB0}"=>"\x{22E1}", # preceq, succeq
	     "\x{2282}"=>"\x{2284}", "\x{2283}"=>"\x{2285}", # subset, supset
	     "\x{2286}"=>"\x{2288}", "\x{2287}"=>"\x{2289}", # subseteq, supseteq
	     "\x{2291}"=>"\x{22E2}", "\x{2290}"=>"\x{22E3}", # sqsubseteq, sqsupseteq
	     "\x{2261}"=>"\x{2262}",			     # equiv
	     "\x{224D}"=>"\x{226D}", "\x{2248}"=>"\x{2249}", # asymp, approx
	     "\x{22B2}"=>"\x{22EA}", "\x{22B3}"=>"\x{22EB}", # lhd, rhd
	     "\x{22B4}"=>"\x{22EC}", "\x{22B5}"=>"\x{22ED}", # unlhd, unrhd
	     "\x{2203}"=>"\x{2204}",			     # Exists
	     );

DefMathLigature(sub { my($document,@nodes)=@_;
		      my ($n1,$n2,$op)=($nodes[$#nodes-1],$nodes[$#nodes]);
		      my $c;
		      if(defined $n1
			 && (($document->getModel->getNodeQName($n1) eq 'ltx:XMTok')
			     && ($n1->textContent eq 'not'))
			 && (($document->getModel->getNodeQName($n2) eq 'ltx:XMTok')
			     &&  $NOTS{$c=$n2->textContent})){
			my $m = $n2->getAttribute('meaning');
			my $n = $n2->getAttribute('name') || $c;
			my $r = $n2->getAttribute('role');
			(2,  $NOTS{$c}, 
###			 ($m ? (meaning=>"not-$m"):(name=>"not-$n"))); }});
			 ($m ? (meaning=>"not-$m"):()),
			 ($n ? (name=>"not-$n") :()),
			 ($r ? (role=>$r):()),
			); }});

#----------------------------------------------------------------------
# \joinrel
DefMathI('\relbar',undef, "-", role=>'RELOP'); # ???
DefMathI('\Relbar',undef, "=", role=>'RELOP'); # ???

# \joinrel is \mathrel{\mkern-3\mu}
# Ah, but the Effect is to join 2 "relations" into one!
DefPrimitiveI('\joinrel',undef,sub {
  my($stomach,$op)=@_;
  my $gullet = $stomach->getGullet;
  $gullet->skipSpaces;
  my $left = $LaTeXML::LIST[$#LaTeXML::LIST];
  if(!$left){			# Nothing there?...
    return (); }		# I guess this becomes a no-op???
  else {
    pop(@LaTeXML::LIST);
    my @stuff=();
    while(my $tok = $gullet->readXToken){
      @stuff = $stomach->invokeToken($tok);
      last if @stuff; }
    return () unless @stuff;	# no-op ????
    my $right = shift(@stuff);
    (@stuff,
     LaTeXML::Whatsit->new(LookupDefinition(T_CS('\@@joinrel')),[$left,$right],
			   locator=>$gullet->getLocator,
			   font=>$right->getFont,isMath=>1)); }});

DefConstructor('\@@joinrel{}{}', sub {
  my($document,$left,$right)=@_;
  $document->absorb($left);
  $document->absorb($right);
  # Now if last 2 items are XMTok, replace by a single token with joined content (& attr?)
  my $node= $document->getNode;
  my @nodes = $document->getChildElements($node);
  if(scalar(@nodes) >= 2){
    my @rels= ($nodes[$#nodes-1],$nodes[$#nodes]);
    if(grep($document->getNodeQName($_) eq 'ltx:XMTok',@rels )){
      my %roles=();
      map($roles{$_->getAttribute('role')}=1, @rels);
      my $role = (scalar(keys %roles)==1 ? [keys %roles]->[0] : ($roles{ARROW} ? 'ARROW' : 'RELOP'));
      map($node->removeChild($_), @rels);
      $document->insertElement('ltx:XMTok',[map($_->textContent,@rels)], role=>$role);
    }}},
	      reversion=>'#1\joinrel #2');

#----------------------------------------------------------------------
# LaTeX; Table 3.6. Arrow Symbols, p.43
#----------------------------------------------------------------------
# Arrows get treated somewhat like relations (or meta-relations),
# but it's hard to associate any particular "meaning" to them.

DefMathI('\leftarrow',         undef,"\x{2190}", role=>'ARROW'); # LEFTWARDS ARROW
DefMathI('\Leftarrow',         undef,"\x{21D0}", role=>'ARROW'); # LEFTWARDS DOUBLE ARROW
DefMathI('\rightarrow',        undef,"\x{2192}", role=>'ARROW'); # RIGHTWARDS ARROW
DefMathI('\Rightarrow',        undef,"\x{21D2}", role=>'ARROW'); # RIGHTWARDS DOUBLE ARROW
DefMathI('\leftrightarrow',    undef,"\x{2194}", role=>'METARELOP');	# LEFT RIGHT ARROW
DefMathI('\Leftrightarrow',    undef,"\x{21D4}", role=>'METARELOP'); # LEFT RIGHT DOUBLE ARROW
DefMathI('\iff',               undef,"\x{21D4}", role=>'METARELOP', meaning=>'iff'); # LEFT RIGHT DOUBLE ARROW
DefMathI('\mapsto',            undef,"\x{21A6}", role=>'ARROW', meaning=>'maps-to');
DefMathI('\hookleftarrow',     undef,"\x{21A9}", role=>'ARROW'); # LEFTWARDS ARROW WITH HOOK
DefMathI('\leftharpoonup',     undef,"\x{21BC}", role=>'ARROW'); # LEFTWARDS HARPOON WITH BARB UPWARDS
DefMathI('\leftharpoondown',   undef,"\x{21BD}", role=>'ARROW'); # LEFTWARDS HARPOON WITH BARB DOWNWARDS
DefMathI('\rightleftharpoons', undef,"\x{21CC}", role=>'METARELOP');	# RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON
DefMathI('\longleftarrow',     undef,"\x{27F5}", role=>'ARROW'); # LONG LEFTWARDS ARROW
DefMathI('\Longleftarrow',     undef,"\x{27F8}", role=>'ARROW'); # LONG LEFTWARDS DOUBLE ARROW
DefMathI('\longrightarrow',    undef,"\x{27F6}", role=>'ARROW'); # LONG RIGHTWARDS ARROW
DefMathI('\Longrightarrow',    undef,"\x{27F9}", role=>'ARROW'); # LONG RIGHTWARDS DOUBLE ARROW
DefMathI('\longleftrightarrow',undef,"\x{27F7}", role=>'METARELOP');	# LONG LEFT RIGHT ARROW
DefMathI('\Longleftrightarrow',undef,"\x{27FA}", role=>'METARELOP'); # LONG LEFT RIGHT DOUBLE ARROW
DefMathI('\longmapsto',        undef,"\x{27FC}", role=>'ARROW'); # LONG RIGHTWARDS ARROW FROM BAR
DefMathI('\hookrightarrow',    undef,"\x{21AA}", role=>'ARROW'); # RIGHTWARDS ARROW WITH HOOK
DefMathI('\rightharpoonup',    undef,"\x{21C0}", role=>'ARROW'); # RIGHTWARDS HARPOON WITH BARB UPWARDS
DefMathI('\rightharpoondown',  undef,"\x{21C1}", role=>'ARROW'); # RIGHTWARDS HARPOON WITH BARB DOWNWARDS
DefMathI('\leadsto',           undef,"\x{219D}", role=>'ARROW', meaning=>'leads-to');

DefMathI('\uparrow',           undef,"\x{2191}", role=>'ARROW'); # UPWARDS ARROW
DefMathI('\Uparrow',           undef,"\x{21D1}", role=>'ARROW'); # UPWARDS DOUBLE ARROW
DefMathI('\downarrow',         undef,"\x{2193}", role=>'ARROW'); # DOWNWARDS ARROW
DefMathI('\Downarrow',         undef,"\x{21D3}", role=>'ARROW'); # DOWNWARDS DOUBLE ARROW
DefMathI('\updownarrow',       undef,"\x{2195}", role=>'ARROW'); # UP DOWN ARROW
DefMathI('\Updownarrow',       undef,"\x{21D5}", role=>'ARROW'); # UP DOWN DOUBLE ARROW
DefMathI('\nearrow',           undef,"\x{2197}", role=>'ARROW'); # NORTH EAST ARROW
DefMathI('\searrow',           undef,"\x{2198}", role=>'ARROW'); # SOUTH EAST ARROW
DefMathI('\swarrow',           undef,"\x{2199}", role=>'ARROW'); # SOUTH WEST ARROW
DefMathI('\nwarrow',           undef,"\x{2196}", role=>'ARROW'); # NORTH WEST ARROW

# \mapstochar (3237), \lhook(312C), \rhook(312D)
# These are really wrong; I can't find the right Unicode Glyphs.
# These are only fragments intended to be assembled into meaningful(?) symbols.
DefMathI('\mapstochar',        undef,"\x{2E20}"); # TeX 3237
DefMathI('\lhook',             undef,"\x{2E26}"); # TeX 312C
DefMathI('\rhook',             undef,"\x{2E27}"); # TeX 312D

#======================================================================
# TeX Book, Appendix B. p. 359

#DefMath('\ldots',"\x{2026}", role=>'ID'); # HORIZONTAL ELLIPSIS
# Ah, since \ldots can appear in text....
DefConstructorI('\ldots',undef,
	       "?#isMath(<ltx:XMTok name='ldots' font='#font' role='ID'>\x{2026}</ltx:XMTok>)(\x{2026})",
	       properties=>{font=>sub{ LookupValue('font')->specialize("\x{2026}");}}); # Since not DefMath!
DefMathI('\cdots',undef,"\x{22EF}", role=>'ID'); # MIDLINE HORIZONTAL ELLIPSIS
DefMathI('\vdots',undef,"\x{22EE}", role=>'ID'); # VERTICAL ELLIPSIS
DefMathI('\ddots',undef,"\x{22F1}", role=>'ID'); # DOWN RIGHT DIAGONAL ELLIPSIS
DefMathI('\colon',undef,':',        role=>'METARELOP'); # Seems like good default role
# Note that amsmath redefines \dots to be `smart'.
# Aha, also can be in text...
DefConstructorI('\dots',undef,
	       "?#isMath(<ltx:XMTok name='dots' font='#font' role='ID'>\x{2026}</ltx:XMTok>)(\x{2026})",
	       properties=>{font=>sub{ LookupValue('font')->merge(family=>'serif');}} ); # Since not DefMath!
# And while we're at it...

# Pretest for XMath to keep from interpreting math that the DOM may not allow!!
##DefMathRewrite(xpath=>'descendant-or-self::ltx:XMath',match=>'\cdot\cdot\cdot',replace=>'\cdots');

DefMathLigature(sub { my($document,@nodes)=@_;
		      if((scalar(@nodes) > 3)
			 && ! grep( ($document->getModel->getNodeQName($_) ne 'ltx:XMTok')|| ($_->textContent ne "\x{22C5}"),
				    @nodes[$#nodes-2 .. $#nodes])){
			(3, "\x{22EF}", name=>'cdots', role=>'ID'); }});
##DefRewrite(regexp=>"/\\.\\.\\./\x{2026}/"); # ldots
DefLigature("/\\.\\.\\./\x{2026}/", fontTest=>sub { $_[0]->getFamily ne 'typewriter'; }); # ldots

#DefMathRewrite(xpath=>'descendant-or-self::ltx:XMath',match=>'...',replace=>'\ldots');

DefMathLigature(sub { my($document,@nodes)=@_;
		      if((scalar(@nodes) >= 3)
			 && ! grep( ($document->getModel->getNodeQName($_) ne 'ltx:XMTok')||($_->textContent ne "."),
				    @nodes[$#nodes-2 .. $#nodes])){
			(3, "\x{2026}", name=>'ldots', role=>'ID'); }});

#----------------------------------------------------------------------
# Math Accents.
#----------------------------------------------------------------------
# LaTeX; Table 3.11. Math Mode Accents, p.50.
# Are these all TeX (or LaTeX)?

DefMath('\hat Digested',       "\x{2303}", operator_role=>'OVERACCENT');
DefMath('\check Digested',     "\x{2713}", operator_role=>'OVERACCENT'); # CHECK MARK
DefMath('\breve Digested',     "\x{02D8}", operator_role=>'OVERACCENT'); # BREVE
DefMath('\acute Digested',     UTF(0xB4),  operator_role=>'OVERACCENT'); # ACUTE ACCENT
DefMath('\grave Digested',     UTF(0x60),  operator_role=>'OVERACCENT'); # GRAVE ACCENT
DefMath('\tilde Digested',     UTF(0x7E),  operator_role=>'OVERACCENT'); # TILDE
DefMath('\bar Digested',       UTF(0xAF),  operator_role=>'OVERACCENT'); # MACRON
DefMath('\vec Digested',       "\x{2192}", operator_role=>'OVERACCENT'); # RIGHTWARDS ARROW
DefMath('\dot Digested',       "\x{02D9}", operator_role=>'OVERACCENT'); # DOT ABOVE
DefMath('\ddot Digested',      UTF(0xA8),  operator_role=>'OVERACCENT'); # DIAERESIS
DefMath('\overline Digested',  UTF(0xAF),  operator_role=>'OVERACCENT'); # MACRON
DefMath('\overbrace Digested', "\x{FE37}", operator_role=>'OVERACCENT',  # PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET
	scriptpos=>'mid');
DefMath('\widehat Digested',   UTF(0x5E),  operator_role=>'OVERACCENT'); # CIRCUMFLEX ACCENT [plain? also amsfonts]
DefMath('\widetilde Digested', UTF(0x7E),  operator_role=>'OVERACCENT');  # TILDE [plain? also amsfonts]
DefMath('\underbrace Digested',"\x{FE38}", operator_role=>'UNDERACCENT', # PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET
	scriptpos=>'mid');

# NOTE that all the above accents REQUIRE math mode
# EXCEPT underline, overrightarrow and overleftarrow!

DefMath('\math@underline{}', UTF(0xAF), operator_role=>'UNDERACCENT', 
	name=>'underline', alias=>'\underline');
DefConstructor('\text@underline{}', "<ltx:text framed='underline' _noautoclose='1'>#1</ltx:text>");
DefMath('\math@overrightarrow{}', "\x{2192}", operator_role=>'OVERACCENT', 
	name=>'overrightarrow', alias=>'\overrightarrow');
DefMath('\math@overleftarrow{}', "\x{2190}", operator_role=>'OVERACCENT', 
	name=>'overleftarrow', alias=>'\overleftarrow');

# Careful: Use \protect so that it doesn't expand too early in alignments, etc.
DefMacro('\underline{}',      '\protect\ifmmode\math@underline{#1}\else\text@underline{#1}\fi');
Let(T_CS('\underbar'),T_CS('\underline'));	# Will anyone notice?

DefMacro('\overrightarrow{}', '\protect\ifmmode\math@overrightarrow{#1}\else$\math@overrightarrow{#1}$\fi');
DefMacro('\overleftarrow{}',  '\protect\ifmmode\math@overleftarrow{#1}\else$\math@overleftarrow{#1}$\fi');

#----------------------------------------------------------------------
# LaTeX; Table 3.10. Delimiters, p.47
#----------------------------------------------------------------------
# The meaning of OPEN/CLOSE tends to depend upon the pairing,
# rather than the individual tokens.
# This meaning is handled in MathParser (for now)

DefConstructorI('\{',undef,
	       "?#isMath(<ltx:XMTok font='#font' role='OPEN'>{</ltx:XMTok>)({)");
DefConstructorI('\}',undef,
	       "?#isMath(<ltx:XMTok font='#font' role='CLOSE'>}</ltx:XMTok>)(})");
Let(T_CS('\lbrace'),T_CS('\{'));
Let(T_CS('\lbrack'),T_CS('['));
Let(T_CS('\rbrace'),T_CS('\}'));
Let(T_CS('\rbrack'),T_CS(']'));
DefMathI('\lfloor',   undef,"\x{230A}", role=>'OPEN'); # LEFT FLOOR
DefMathI('\lceil',    undef,"\x{2308}", role=>'OPEN'); # LEFT CEILING
DefMathI('\langle',   undef,"\x{2329}", role=>'OPEN');   # LEFT-POINTING ANGLE BRACKET
DefMathI('\rfloor',   undef,"\x{230B}", role=>'CLOSE'); # RIGHT FLOOR
DefMathI('\rceil',    undef,"\x{2309}", role=>'CLOSE'); # RIGHT CEILING
DefMathI('\rangle',   undef,"\x{232A}", role=>'CLOSE');	# RIGHT-POINTING ANGLE BRACKET

# Not sure these should be defined here, or latex, or even latex compat mode.
DefMathI('\lgroup',  undef,"(", font=>{series=>'bold'}, role=>'OPEN');
DefMathI('\rgroup',  undef,")", font=>{series=>'bold'}, role=>'CLOSE');
DefMathI('\bracevert', undef, "|", font=>{series=>'bold'}, role=>'VERTBAR');

## DefMath('\lmoustache',"???", font=>{series=>'bold'}, role=>'OPEN');
## DefMath('\rmoustache',"???", font=>{series=>'bold'}, role=>'OPEN');

# TeX marks some symbols as delimiters which can be used with \left,\right,
# but many of which have different grammatical roles otherwise, eg. arrows, <, >.
# Short of setting up TeX's complicated encoding machinery, I need an explicit
# mapping.  Unfortunately, this doesn't (yet) support people declaring thier own delimiters!

# This duplicates in slightly different way what DefMath has put together.
our %DELIMITER_MAP =
  ('('           =>{char=>"(",        lrole=>'OPEN',    rrole=>'CLOSE'},
   ')'           =>{char=>")",        lrole=>'OPEN',    rrole=>'CLOSE'},
   '['           =>{char=>"[",        lrole=>'OPEN',    rrole=>'CLOSE'},
   ']'           =>{char=>"]",        lrole=>'OPEN',    rrole=>'CLOSE'},
   '\{'          =>{char=>"{",        lrole=>'OPEN',    rrole=>'CLOSE'},
   '\}'          =>{char=>"}",        lrole=>'OPEN',    rrole=>'CLOSE'},
   '\lfloor'     =>{char=>"\x{230A}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'lfloor'},
   '\rfloor'     =>{char=>"\x{230B}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'rfloor'},
   '\lceil'      =>{char=>"\x{2308}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'lceil'},
   '\rceil'      =>{char=>"\x{2309}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'rceil'},
   '\langle'     =>{char=>"\x{2329}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'langle'},
   '\rangle'     =>{char=>"\x{232A}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'rangle'},
   '<'           =>{char=>"\x{2329}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'langle'},
   '>'           =>{char=>"\x{232A}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'rangle'},
   '/'           =>{char=>"/",        lrole=>'MULOP',   rrole=>'MULOP'},
   '\backslash'  =>{char=>UTF(0x5C), lrole=>'MULOP',   rrole=>'MULOP', name=>'backslash'},
   '|'           =>{char=>"|",        lrole=>'VERTBAR', rrole=>'VERTBAR'},
   '\|'          =>{char=>"\x{2225}", lrole=>'VERTBAR', rrole=>'VERTBAR'},
   '\uparrow'    =>{char=>"\x{2191}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'uparrow'}, # ??
   '\Uparrow'    =>{char=>"\x{21D1}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'Uparrow'}, # ??
   '\downarrow'  =>{char=>"\x{2193}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'downarrow'}, # ??
   '\Downarrow'  =>{char=>"\x{21D3}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'Downarrow'}, # ??
   '\updownarrow'=>{char=>"\x{2195}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'updownarrow'}, # ??
   '\Updownarrow'=>{char=>"\x{21D5}", lrole=>'OPEN',    rrole=>'CLOSE', name=>'Updownarrow'}, # ??
   );

sub lookup_delimiter {
  $DELIMITER_MAP{$_[0]}; }

# This is a little messier than you'd think.
# These effectively create a group between the \left,\right.
DefMacro('\left Token','\@left #1\@hidden@bgroup');
DefMacro('\right Token','\@hidden@egroup\@right #1');
DefConstructor('\@left Token',
	       "?#char(<ltx:XMTok role='#role' name='#name'>#char</ltx:XMTok>)(<ltx:XMHint/>)",
	       afterDigest=>sub { my($stomach,$whatsit)=@_;
				  my $arg   = $whatsit->getArg(1);
				  my $delim = ToString($arg);
				  if($delim eq '.'){}
				  elsif(my $entry = $DELIMITER_MAP{$delim}){
				    $whatsit->setProperties(role=>$$entry{lrole},
							    char=>$$entry{char},
							    name=>$$entry{name});
				    $whatsit->setFont($arg->getFont()); }
				  elsif(($arg->getProperty('role')||'') eq 'OPEN'){}
				  else {
				    Warn(":expected:. Missing delimiter $delim (. inserted)"); }
				  return; },
	       alias=>'\left');
DefConstructor('\@right Token',
	       "?#char(<ltx:XMTok role='#role' name='#name'>#char</ltx:XMTok>)(<ltx:XMHint/>)",
	       afterDigest=>sub { my($stomach,$whatsit)=@_;
				  my $arg   = $whatsit->getArg(1);
				  my $delim = ToString($arg);
				  if($delim eq '.'){}
				  elsif(my $entry = $DELIMITER_MAP{$delim}){
				    $whatsit->setProperties(role=>$$entry{rrole},
							    char=>$$entry{char},
							    name=>$$entry{name});
				    $whatsit->setFont($arg->getFont()); }
				  elsif(($arg->getProperty('role')||'') eq 'CLOSE'){}
				  else {
				    Warn(":expected:. Missing delimiter $delim (. inserted)"); }
				  return; },
	       alias=>'\right');

sub addDelimiterRole {
  my($document,$role)=@_;
  my $current      = $document->getNode;
  # This should be the wrapper just added.
  my $node         = $document->getLastChildElement($current) || $current;
  my $delim        = $node && $document->getFirstChildElement($node);
  my $delim_role   = (($delim && $delim->getAttribute('role')) || '');
  my $wrapper_role = ($delim_role =~ /^(OPEN|MIDDLE|CLOSE|VERTBAR)$/ ? $role : $delim_role);
  $document->setAttribute($node,role=>$wrapper_role) if $node && $wrapper_role; }

# Defined in e-Tex and other places.
DefConstructor('\middle Token ',"<ltx:XMWrap style='stretchy'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'MIDDLE'); });
DefConstructor('\bigl Token',"<ltx:XMWrap size='big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'OPEN'); });
DefConstructor('\bigm Token',"<ltx:XMWrap size='big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'MIDDLE'); });
DefConstructor('\bigr Token',"<ltx:XMWrap size='big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'CLOSE'); });
DefConstructor('\Bigl Token',"<ltx:XMWrap size='Big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'OPEN'); });
DefConstructor('\Bigm Token',"<ltx:XMWrap size='Big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'MIDDLE'); });
DefConstructor('\Bigr Token',"<ltx:XMWrap size='Big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'CLOSE'); });
DefConstructor('\biggl Token',"<ltx:XMWrap size='bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'OPEN'); });
DefConstructor('\biggm Token',"<ltx:XMWrap size='bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'MIDDLE'); });
DefConstructor('\biggr Token',"<ltx:XMWrap size='bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'CLOSE'); });
DefConstructor('\Biggl Token',"<ltx:XMWrap size='Bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'OPEN'); });
DefConstructor('\Biggm Token',"<ltx:XMWrap size='Bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'MIDDLE'); });
DefConstructor('\Biggr Token',"<ltx:XMWrap size='Bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0],'CLOSE'); });

# ?
DefConstructor('\big Token',"<ltx:XMWrap size='big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0]); });
DefConstructor('\Big Token',"<ltx:XMWrap size='Big'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0]); });
DefConstructor('\bigg Token',"<ltx:XMWrap size='bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0]); });
DefConstructor('\Bigg Token',"<ltx:XMWrap size='Bigg'>#1</ltx:XMWrap>",
	       afterConstruct=>sub { addDelimiterRole($_[0]); });

Let(T_CS('\vert'),T_OTHER('|'));
Let(T_CS('\Vert'),T_CS('\|'));


#======================================================================
# TeX Book, Appendix B. p. 360

# \choose, et al, already handle above.


DefMacro('\mathchoice{}{}{}{}',sub {
  my($gullet,$d,$t,$s,$ss)=@_;
  my $style = LookupValue('mathstyle') || 'display';
  if   ($style eq 'display'){ $d->unlist; }
  elsif($style eq 'text'   ){ $t->unlist; }
  elsif($style eq 'script' ){ $s->unlist; }
  else                      { $ss->unlist; }});
DefMacro('\mathpalette{}{}',
	 '\mathchoice{#1\displaystyle{#2}}{#1\textstyle{#2}}'
	 .'{#1\scriptstyle{#2}}{#1\scriptscriptstyle{#2}}');
  
DefConstructor('\phantom{}',"?#isMath(<ltx:XMHint name='phantom'/>)()",
	      properties=>{isSpace=>1});
DefConstructor('\hphantom{}',"?#isMath(<ltx:XMHint name='hphantom'/>)()",
	      properties=>{isSpace=>1});
DefConstructor('\vphantom{}',"?#isMath(<ltx:XMHint name='vphantom'/>)()",
	      properties=>{isSpace=>1});
DefConstructor('\mathstrut',"?#isMath(<ltx:XMHint name='mathstrut'/>)()",
	      properties=>{isSpace=>1});
DefConstructor('\smash{}',"#1"); # well, what?

#======================================================================
# TeX Book, Appendix B. p. 361

# This is actually LaTeX's definition, but let's just do it this way.
DefConstructor('\sqrt[] Digested',
	       "?#1(<ltx:XMApp><ltx:XMTok meaning='root'/>"
	       .     "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
	       .   "</ltx:XMApp>)"
	       .  "(<ltx:XMApp><ltx:XMTok meaning='square-root'/>"
	       .    "<ltx:XMArg>#2</ltx:XMArg></ltx:XMApp>)");

DefConstructor('\root Until:\of {}',
	       "<ltx:XMApp><ltx:XMTok meaning='root'/>"
	       .     "<ltx:XMArg>#1</ltx:XMArg><ltx:XMArg>#2</ltx:XMArg>"
	       ."</ltx:XMApp>");

#----------------------------------------------------------------------
# LaTeX; Table 3.9. Log-like Functions, p.44.
#----------------------------------------------------------------------
# NOTE: Classifying some as TRIGFUNCTION might clarify 'pi' ambiguities ?
DefMathI('\arccos', undef,"arccos", role=>'OPFUNCTION', meaning=>'inverse-cosine');
DefMathI('\arcsin', undef,"arcsin", role=>'OPFUNCTION', meaning=>'inverse-sine');
DefMathI('\arctan', undef,"arctan", role=>'OPFUNCTION', meaning=>'inverse-tangent');
DefMathI('\arg',    undef,"arg", role=>'OPFUNCTION',    meaning=>'argument');

DefMathI('\cos',    undef,"cos",  role=>'TRIGFUNCTION', meaning=>'cosine');
DefMathI('\cosh',   undef,"cosh", role=>'TRIGFUNCTION', meaning=>'hyperbolic-cosine');
DefMathI('\cot',    undef,"cot",  role=>'TRIGFUNCTION', meaning=>'cotangent');
DefMathI('\coth',   undef,"coth", role=>'TRIGFUNCTION', meaning=>'hyperbolic-cotangent');

DefMathI('\csc',    undef,"csc", role=>'TRIGFUNCTION',  meaning=>'cosecant');
DefMathI('\deg',    undef,"deg", role=>'OPFUNCTION',    meaning=>'degree');
DefMathI('\det',    undef,"det", role=>'LIMITOP',       meaning=>'determinant',
	scriptpos=>\&doScriptpos);
DefMathI('\dim',    undef,"dim", role=>'LIMITOP',       meaning=>'dimension');

DefMathI('\exp',    undef,"exp", role=>'OPFUNCTION',    meaning=>'exponential');
DefMathI('\gcd',    undef,"gcd", role=>'OPFUNCTION',    meaning=>'gcd',
	scriptpos=>\&doScriptpos);
DefMathI('\hom',    undef,"hom", role=>'OPFUNCTION');
DefMathI('\inf',    undef,"inf", role=>'LIMITOP',       meaning=>'infimum',
	scriptpos=>\&doScriptpos);

DefMathI('\ker',    undef,"ker", role=>'OPFUNCTION',    meaning=>'kernel');
DefMathI('\lg',     undef,"lg",  role=>'OPFUNCTION');
DefMathI('\lim',    undef,"lim", role=>'LIMITOP',       meaning=>'limit',
	scriptpos=>\&doScriptpos);
DefMathI('\liminf', undef,"lim inf", role=>'LIMITOP',   meaning=>'limit-infimum',
	scriptpos=>\&doScriptpos);

DefMathI('\limsup', undef,"lim sup", role=>'LIMITOP',   meaning=>'limit-supremum',
	scriptpos=>\&doScriptpos);
DefMathI('\ln',     undef,"ln",  role=>'OPFUNCTION',    meaning=>'natural-logarithm');
DefMathI('\log',    undef,"log", role=>'OPFUNCTION',    meaning=>'logarithm');
DefMathI('\max',    undef,"max", role=>'LIMITOP',       meaning=>'maximum',
	scriptpos=>\&doScriptpos);

DefMathI('\min',    undef,"min", role=>'LIMITOP',       meaning=>'minimum',
	scriptpos=>\&doScriptpos);
DefMathI('\Pr',     undef,"Pr",  role=>'OPFUNCTION', scriptpos=>\&doScriptpos);
DefMathI('\sec',    undef,"sec", role=>'TRIGFUNCTION',  meaning=>'secant');
DefMathI('\sin',    undef,"sin", role=>'TRIGFUNCTION',  meaning=>'sine');

DefMathI('\sinh',   undef,"sinh", role=>'TRIGFUNCTION', meaning=>'hyperbolic-sine');
DefMathI('\sup',    undef,"sup",  role=>'LIMITOP',      meaning=>'supremum',
	scriptpos=>\&doScriptpos);
DefMathI('\tan',    undef,"tan",  role=>'TRIGFUNCTION', meaning=>'tangent');
DefMathI('\tanh',   undef,"tanh", role=>'TRIGFUNCTION', meaning=>'hyperbolic-tangent');

#----------------------------------------------------------------------
# Modulo

DefMath('\pmod{}', '\;\;(\textrm{mod} #1)', role=>'MODIFIER'); #  , meaning=>'modulo');
DefMath('\bmod',   'mod', role=>'MODIFIEROP', meaning=>'modulo');

#======================================================================
# TeX Book, Appendix B. p. 362
DefMacro('\matrix{}',
	 '\@@matrix{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@matrix{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body,attributes=>{name=>'Matrix'}); },
	       reversion=>'\matrix{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings(MatrixTemplate()); });

# TODO: This is wrong; I'm not even clear how this differs from \matrix
DefMacro('\bordermatrix{}',
	 '\@@bordermatrix{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@bordermatrix{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body,attributes=>{name=>'Matrix'}); },
	       reversion=>'\bordermatrix{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings(MatrixTemplate()); });

DefMacro('\pmatrix{}',
	 '\@@pmatrix{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@pmatrix{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body,attributes=>{name=>'Matrix',
								     open=>'(',close=>')'}); },
	       reversion=>'\pmatrix{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings(MatrixTemplate()); });


# Note that 2nd column in \cases is in text mode!
DefMacro('\cases{}',
	 '\@@cases{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@cases{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body,attributes=>{meaning=>'cases'}); },
	       reversion=>'\cases{#1}', bounded=>1,
	       beforeDigest=>sub { 
 		 my $col1 = {before=>Tokens(), after=>Tokens(T_CS('\hfil'))};
 		 my $col2 = {before=>Tokens(T_CS('\hbox'),T_BEGIN),
			     after=>Tokens(T_END,T_CS('\hfil'))};
		 alignmentBindings(LaTeXML::AlignmentTemplate->new(columns=>[$col1,$col2])); });

DefPrimitive('\openup Dimension',undef);

# What should this do? (needs to work with alignments..)
DefMacro('\displaylines{}','#1');

DefMacro('\eqalign{}',
	 '\@@eqalign{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@eqalign{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body); },
	       reversion=>'\eqalign{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings('rl'); });

DefMacro('\eqalignno{}',
	 '\@@eqalignno{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@eqalignno{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body); },
	       reversion=>'\eqalignno{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings('rll'); });

DefMacro('\leqalignno{}',
	 '\@@leqalignno{\@start@alignment#1\@finish@alignment}');
DefConstructor('\@@leqalignno{}',
	       sub { my($document,$body,%props)=@_;
		     constructAlignment($document,$body); },
	       reversion=>'\leqalignno{#1}', bounded=>1,
	       beforeDigest=>sub { alignmentBindings('rll'); });


DefRegister('\pageno'=>Number(0));
DefRegister('\headline'=>Tokens());
DefRegister('\footline'=>Tokens());
DefMacroI('\folio',undef, "1"); 	# What else?

DefPrimitiveI('\nopagenumbers',undef,undef);
DefMacroI('\advancepageno',undef,'\advance\pageno1\relax');

#======================================================================
# TeX Book, Appendix B. p. 363

DefPrimitive('\raggedbottom',undef);
DefPrimitive('\normalbottom',undef);

# if the mark is not simple, we add it to the content of the note
# otherwise, to the attribute.
DefConstructor('\footnote{}{}',
             "<ltx:note class='foot' ?#mark(mark='#mark')()>?#prenote(#prenote )()#2</ltx:note>",
             afterDigest=>sub {
               my ($stomach, $whatsit) = @_;
               my $mark = $whatsit->getArg(1);
               my $change = 0;
               foreach my $token($mark->revert) {
                 unless ($token->getCatcode == CC_LETTER || $token->getCatcode == CC_SPACE ||
                         $token->getCatcode == CC_OTHER) {
                   $change = 1; last; }}
               $whatsit->setProperty(($change ? 'prenote' : 'mark')=>$mark);
               return; });
DefPrimitiveI('\footstrut', undef,undef);
DefRegister('\footins'=>Dimension(0));

DefPrimitiveI('\topinsert', undef,undef);
DefPrimitiveI('\midinsert', undef,undef);
DefPrimitiveI('\pageinsert',undef,undef);
DefPrimitiveI('\endinsert', undef,undef);
# \topins ?

#======================================================================
# TeX Book, Appendix B. p. 364

# Let's hope nobody is messing with the output routine...

DefPrimitiveI('\footnoterule',undef,undef);
DefPrimitiveI('\tracingall',  undef,undef);

#======================================================================
# End of TeX Book definitions.
#======================================================================


#**********************************************************************
# Stray stuff .... where to ?
#**********************************************************************

# Mostly ignorable, although it could add an attribute to an ancestor
# to record the desired justification.
# Spacing stuff
DefConstructor('\@','');
# Math spacing.


# Math style.
DefConstructorI('\displaystyle',     undef,'',afterDigest=>sub { AssignValue(mathstyle=>'display'); });
DefConstructorI('\textstyle',        undef,'',afterDigest=>sub { AssignValue(mathstyle=>'text'); });
DefConstructorI('\scriptstyle',      undef,'',afterDigest=>sub { AssignValue(mathstyle=>'script'); });
DefConstructorI('\scriptscriptstyle',undef,'',afterDigest=>sub { AssignValue(mathstyle=>'scriptscript'); });

#======================================================================

# Special Characters.
DefConstructorI('\#',undef,   '#');
DefConstructorI('\&', undef,  '&');
DefMacroI('\%',undef,Tokens(T_OTHER('%')));
DefConstructorI("\\\$", undef,"\$");
DefConstructorI('\_',   undef,'_');

# Discretionary times; just treat as invisible ?
DefMathI('\*',undef,"\x{2062}", role=>'MULOP', name=>'', meaning=>'times'); # INVISIBLE TIMES (or MULTIPLICATION SIGN = 00D7)


# These 3 should have some `name' assigned ... but what???

# NOTE that \mathord and \mathbin aren't really right here.
# We need a finer granularity than TeX does: an ORD could be several things,
# a BIN could be a MULOP or ADDOP.
DefConstructor('\mathord{}',"<ltx:XMWrap role='ID'>#1</ltx:XMWrap>", bounded=>1);
# This is probably wrong..
#DefConstructor('\mathop{}', "<ltx:XMWrap role='FUNCTION'>#1</ltx:XMWrap>", bounded=>1);
DefConstructor('\mathop{}', "<ltx:XMWrap role='BIGOP'>#1</ltx:XMWrap>", bounded=>1);
DefConstructor('\mathbin{}', "<ltx:XMWrap role='ADDOP'>#1</ltx:XMWrap>", bounded=>1);
DefConstructor('\mathrel{}',"<ltx:XMWrap role='RELOP'>#1</ltx:XMWrap>", bounded=>1);
DefConstructor('\mathopen{}', "<ltx:XMWrap role='OPEN'>#1</ltx:XMWrap>", bounded=>1);
DefConstructor('\mathclose{}', "<ltx:XMWrap role='CLOSE'>#1</ltx:XMWrap>", bounded=>1);
DefConstructor('\mathpunct{}', "<ltx:XMWrap role='PUNCT'>#1</ltx:XMWrap>", bounded=>1);


DefMacro('\hiderel{}', "#1"); # Just ignore, for now...

DefMathI('\to',undef,"\x{2192}", role=>'ARROW'); # RIGHTWARDS ARROW??? a bit more explicitly relation-like?


# TeX's ligatures handled by rewrite regexps.
DefLigature("/--/\x{2013}/",   fontTest=>sub { $_[0]->getFamily ne 'typewriter'; }); # EN DASH (NOTE: With digits before & aft => \N{FIGURE DASH})
DefLigature("/---/\x{2014}/",  fontTest=>sub { $_[0]->getFamily ne 'typewriter'; }); # EM DASH
DefLigature("/\`\`/\x{201C}/", fontTest=>sub { $_[0]->getFamily ne 'typewriter'; });
DefLigature("/\'\'/\x{201D}/", fontTest=>sub { $_[0]->getFamily ne 'typewriter'; });
# This sorta seems like the normal effect, but not if fontenc is T1... ???
DefLigature("/\"/\x{201D}/",   fontTest=>sub { $_[0]->getFamily ne 'typewriter'; }); # Yes, this one too!

DefConstructorI('\TeX', undef,'TeX');
DefConstructorI('\i',   undef,"\x{0131}");	# LATIN SMALL LETTER DOTLESS I
DefConstructorI('\j',   undef,"j");	# Apparently, no Unicode equivalent...

DefConstructor('\buildrel Until:\over {}',
	       "<ltx:XMApp role='RELOP'>"
	       .  "<ltx:XMTok role='SUPERSCRIPTOP'/>"
	       .  "<ltx:XMWrap scriptpos='#scriptpos'>#2</ltx:XMWrap>"
	       .  "<ltx:XMWrap>#1</ltx:XMWrap>"
	       ."</ltx:XMApp>",
	       properties=>{scriptpos=>sub{ "mid".$_[0]->getBoxingLevel; }});

#**********************************************************************
# LaTeX Hook
#**********************************************************************
# This is used for plain TeX, but needs to be undone for LaTeX (or...)!
RelaxNGSchema("LaTeXML");
Tag('ltx:section', autoClose=>1);
Tag('ltx:document', autoClose=>1, autoOpen=>1);

# No, \documentclass isn't really a primitive -- It's not even TeX!
# But we define a number of stubs here that will automatically load
# the LaTeX pool (which will presumably redefine them), and then
# stuff the token back to be reexecuted.
sub LaTeXTrigger {
  my($gullet,$cs)=@_;
  if(my $poolfile = FindFile("LaTeX.pool")){
    $gullet->input($poolfile); }
  else {
    Fatal(":missing_file:LaTeX.pool.ltxml Installation error: Cannot find LaTeX pool module!"); }
  $gullet->unread(T_CS("\\".$cs));
  (); }

foreach my $trigger (qw(documentclass documentstyle
			newcommand renewcommand newenvironment renewenvironment
			NeedsTeXFormat ProvidesPackage RequirePackage
			makeatletter makeatother)){
  DefMacroI("\\".$trigger, undef, sub { LaTeXTrigger($_[0],$trigger); }); }

#**********************************************************************
# LaTeXML Specific.
# Support for Declarations & Presentation/Semantic Duality
#**********************************************************************
DefConstructor('\DUAL[]{}{}',
	       "<ltx:XMDual ?#1(role='#1')>#2<ltx:XMWrap>#3</ltx:XMWrap></ltx:XMDual>",
	       reversion=>sub {
		 my($whatsit)=@_;
		 my($role,$content,$presentation)=$whatsit->getArgs;
		 if(!$LaTeXML::DUAL_BRANCH) {
		   $whatsit->getDefinition->invocation($role,$content,
						       $presentation); }
		 elsif($LaTeXML::DUAL_BRANCH eq 'content'){
		   $content->revert; }
		 elsif($LaTeXML::DUAL_BRANCH eq 'presentation'){
		   $presentation->revert; }
		 else{
		   Warn(":misdefined:$LaTeXML::DUAL_BRANCH Unknown DUAL_BRANCH: $LaTeXML::DUAL_BRANCH");
		   $whatsit->getDefinition->invocation($role,$content,
						       $presentation); }
	       });


sub addMeaningRec {
  my($document,$node,$meaning)=@_;
  if($node->nodeType == XML_ELEMENT_NODE){
    if($document->getModel->getNodeQName($node) eq 'ltx:XMTok'){
      if((($node->getAttribute('role')||'UNKNOWN') eq 'UNKNOWN')
	 && !$node->getAttribute('meaning')){
	$document->setAttribute($node,meaning=>$meaning); }}
    else {
      foreach my $c ($node->childNodes){
	addMeaningRec($document,$c,$meaning); }}}}

DefConstructor('\FCN{}',    "<ltx:XMWrap role='FUNCTION'>#1</ltx:XMWrap>", reversion=>'#1', alias=>'');
DefConstructor('\ROLE{}{}', "<ltx:XMWrap role='#1'>#2</ltx:XMWrap>",       reversion=>'#2', alias=>'');

# NOTE: work through this systematically!
DefConstructor('\@SYMBOL{}', "<ltx:XMWrap role='ID'>#1</ltx:XMWrap>", reversion=>'#1');
DefConstructor('\@APPLY{}',  "<ltx:XMApp>#1</ltx:XMApp>", reversion=>'#1');
DefConstructor('\@MAYBEAPPLY{}{}',
	       "?#2(<ltx:XMApp>#1#2</ltx:XMApp>)(#1)",
	       reversion=>'#1#2');
DefConstructor('\@WRAP{}',   "<ltx:XMWrap>#1</ltx:XMWrap>", reversion=>'#1');
DefConstructor('\@TOKEN{}',   "<ltx:XMTok name='#1'/>", reversion=>'');
DefMathI('\@APPLYFUNCTION', undef, "\x{2061}", reversion=>'', name=>'', role=>'APPLYOP');
DefMathI('\@INVISIBLETIMES',undef, "\x{2062}", reversion=>'', name=>'', meaning=>'times', role=>'MULOP');
DefMathI('\@INVISIBLECOMMA',undef, "\x{2063}", reversion=>'', name=>'', role=>'PUNCT');

# Would be nice to make the ID argument optional, but since these can appear in
# optional arguments to other macros, we'll end up nesting [] !!
DefConstructor('\@XMArg{}{}',"<ltx:XMArg ?#1(xml:id='#1')>#2</ltx:XMArg>", reversion=>'#2',
	       properties=>{id=>'#1'},
	       bounded=>1, beforeDigest=>sub { AssignValue(mathstyle=>'text') });
DefConstructor('\@XMRef{}',"<ltx:XMRef idref='#1'/>",
	       reversion=>sub{ 
		 my($whatsit)=@_;
		 my $id = $_[0]->getArg(1);
		 if(my $reversion = LookupValue('xref:'.ToString($id))){
		   $reversion->revert; }
		else {
		   $whatsit->getDefinition->invocation($id); }});

DefConstructor('\@ERROR{}{}', "<ltx:ERROR class='#1'>#2</ltx:ERROR>");

#**********************************************************************
DefConstructorI('\WildCard', undef,"<_WildCard_/>");
DefConstructorI('\WildCardA',undef,"<_WildCard_/>");
DefConstructorI('\WildCardB',undef,"<_WildCard_/>");
DefConstructorI('\WildCardC',undef,"<_WildCard_/>");
#**********************************************************************

# This constructor allows you to stick things in expansions for effect
# but their reversion disappears, so they don't appear in the tex attribute (eg).
DefConstructor('\@hidden{}','',reversion=>'');

#**********************************************************************
# After all other rewrites have acted, a little cleanup 

DefRewrite(xpath=>'descendant-or-self::ltx:XMWrap[count(child::*)=1]',
	  replace=>sub { my($document,$wrap)=@_;
			 if(my $node = $document->getFirstChildElement($wrap)){
			   foreach my $attribute ($wrap->attributes){
			     if($attribute->nodeType == XML_ATTRIBUTE_NODE){
			       my $attr = $attribute->nodeName;
			       $document->setAttribute($node,$attr=>$attribute->getValue)
				 unless $attr =~ /^_/; }}
			   ## WHY THIS????
			   $document->getNode->appendChild($node); }});
#**********************************************************************

1;
