# -*- mode: Perl -*-
# /=====================================================================\ #
# |  physics                                                            | #
# | 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 warnings;
use LaTeXML::Package;
use LaTeXML::Core::Token;

RequirePackage('amsmath');
# Currently physics uses xparse, which uses expl3, which is kinda large & rough to process...
# InputDefinitions('physics', type => 'sty');

#======================================================================
# Interesting package that provides a set of semantic macros
# using several parameter patterns with an illusion of consistency,
# particularly regarding fencing the argument.
# But it's NOT consistent.
#
#  Optional sizing: * (no resize fence);\big, etc (specify fence size)
#     BUT: Not all macros use this.
#  Delimited argument: the argument can often be given delimited by a
#     (), [], ... pair, which is then (maybe) used for the fence.
#     BUT: not all macros use this;
#     Some macros only accept ()
#     Usually you can also use standard {} for the argument
#     in which case there are different default fences, or sometimes none.
#  Optional exponent: a standard LaTeX optional argument, [], can be given
#     for an extra optional power(-like) argument.
#     BUT: for some macros (trigs) that ONLY works if a delimited argument is given.

#----------------------------------------------------------------------
# Parsing aids
our %physics_delimiters = (
  '(' => T_OTHER(')'),
  '[' => T_OTHER(']'),
  '|' => T_OTHER('|'),
);    # And probably more...

# returns one of (0,1,Token) meaning (no stretch, stretchy, specific size)
sub phys_readSize {
  my ($gullet) = @_;
  my $size     = 1;
  my $next     = $gullet->readToken();           # or XToken ?
  if ($next && $next->equals(T_OTHER('*'))) {    # No stretch
    $size = 0;
    $next = $gullet->readToken(); }              # or XToken ?
  if ($next && (ToString($next) =~ /^\\(b|B)igg?/)) {    # \big,\Big,\bigg,\Bigg
    $size = $next;
    $next = $gullet->readToken(); }                      # or XToken ?
  $gullet->unread($next);
  return $size; }

sub phys_revSize {
  my ($size) = @_;
  return (ref $size ? $size : ($size ? () : T_OTHER('*'))); }

sub phys_open {
  my ($size, $delim) = @_;
  return (IsEmpty($delim) ? ()
    : (ref $size ? Tokens($size, $delim) : ($size ? Tokens(T_CS('\left'), $delim) : $delim))); }

sub phys_mid {
  my ($size, $delim) = @_;
  return (IsEmpty($delim) ? ()
    : (ref $size ? Tokens($size, $delim) : ($size ? Tokens(T_CS('\middle'), $delim) : $delim))); }

sub phys_close {
  my ($size, $delim) = @_;
  return (IsEmpty($delim) ? ()
    : (ref $size ? Tokens($size, $delim) : ($size ? Tokens(T_CS('\right'), $delim) : $delim))); }

# Read a single arg, either TeX delimited {}, or delmited according to hash
# Error is signaled if $required and no arg found
# $open,$close are the fences found, or passed through for TeX {}
sub phys_readArg {
  my ($gullet, $required, %delimiters) = @_;
  my ($arg, $open, $close);
  my $next = $gullet->readToken();                   # or XToken ?
  if ($next && ($next->getCatcode == CC_BEGIN)) {    # Read regular arg, use default delimiters
    $gullet->unread($next);
    $arg = $gullet->readArg(); }
  elsif (my $c = $next && $delimiters{ ToString($next) }) {
    # Must read balanced in $open/$close, as well as(?) regular braces
    $open = $next; $close = $c;
    my @tokens = ();
    my ($token, $level, $blevel) = (undef, 1, 0);
    # Inlined readToken (we'll keep comments in the result)
    while ($token = $gullet->readToken()) {
      my $cc = $$token[1];
      if ($cc == CC_END) {
        $blevel--;
        push(@tokens, $token); }
      elsif ($cc == CC_BEGIN) {
        $blevel++;
        push(@tokens, $token); }
      elsif ($token->equals($close)) {
        $level--;
        last unless $level;
        push(@tokens, $token); }
      elsif ($token->equals($open)) {
        $level++;
        push(@tokens, $token); }
      else {
        push(@tokens, $token); } }
    $arg = Tokens(@tokens); }
  else {
    $gullet->unread($next);
    Error('unexpected', $next, $gullet, "Expected an open delimiter", "Got " . Stringify($next))
      if $required; }
  return (wantarray ? ($arg, $open, $close) : $arg); }

sub phys_revArg {
  my ($arg, $open, $close) = @_;
  return ($arg ? ($open ? ($open, $arg, $close) : (T_BEGIN, $arg, T_END))
    : ($open ? ($open, $close) : ())); }

#======================================================================
# Automatic bracing

# Fenced stuff, Optionally prefixed with Semantics & function;
# required TeX or delimited arg, default fenced with braces ()
DefMacro('\quantity', sub {
    my ($gullet) = @_;
    my $size = phys_readSize($gullet);
    my ($arg, $open, $close) = phys_readArg($gullet, 1, %physics_delimiters);
    return I_dual(
      { reversion => Tokens(T_CS('\quantity'), phys_revSize($size),
          phys_revArg(I_arg(1), $open, $close)) },
      I_arg(1),    # No apparent semantics, other than the arg.
      Tokens(phys_open($size, $open || T_CS('\lbrace')),
        I_arg(1), phys_close($size, $close || T_CS('\rbrace'))),
      $arg); });

# Fenced stuff, Optionally prefixed with Semantics & function;
# 1 Required TeX-style arg; Possibly meaning, function and fences provided
DefMacro('\lx@physics@fenced{}{}{}{}{}', sub {
    my ($gullet, $cs, $semantic, $function, $open, $close) = @_;
    $semantic = undef if IsEmpty($semantic);
    my $size = phys_readSize($gullet);
    my $arg  = $gullet->readArg();
    return I_dual(
      { reversion => Tokens($cs, phys_revSize($size), phys_revArg(I_arg(1))) },
      ($semantic
        ? I_apply({}, I_symbol({ meaning => $semantic }), I_arg(1))
        : I_arg(1)),
      Tokens(phys_open($size, $open), I_arg(1), phys_close($size, $close)),
      $arg); });

# required TeX only, fences specified
DefMacro('\pqty',          '\lx@physics@fenced{\pqty}{}{}{(}{)}');
DefMacro('\bqty',          '\lx@physics@fenced{\bqty}{}{}{[}{]}');
DefMacro('\vqty',          '\lx@physics@fenced{\vqty}{}{}{\vert}{\vert}');
DefMacro('\Bqty',          '\lx@physics@fenced{\Bqty}{}{}{\{}{\}}');
DefMacro('\absolutevalue', '\lx@physics@fenced{\absolutevalue}{absolute-value}{}{\vert}{\vert}');
DefMacro('\norm',          '\lx@physics@fenced{\norm}{norm}{}{\|}{\|}');

Let('\qty', '\quantity');
Let('\abs', '\absolutevalue');

DefMacro('\evaluated', sub {
    my ($gullet) = @_;
    my $size     = phys_readSize($gullet);
    my $c        = T_OTHER('|');
    my ($arg, $open, $close) = phys_readArg($gullet, 1, '(' => $c, '[' => $c);
    # NOW read sub & superscript!!!!
    my ($lower, $upper);
    while (my $next = $gullet->readToken()) {
      if    (!$lower && $next->equals(T_SUB))   { $lower = $gullet->readArg(); }
      elsif (!$upper && $next->equals(T_SUPER)) { $upper = $gullet->readArg(); }
      else {
        $gullet->unread($next);
        last; } }
    return I_dual(
      { reversion => Tokens(T_CS('\evaluated'), phys_revSize($size),
          phys_revArg(I_arg(1), $open, $close),
          ($lower ? (T_SUB,   phys_revArg(I_arg(2))) : ()),
          ($upper ? (T_SUPER, phys_revArg(I_arg(3))) : ())) },
      I_apply({}, I_symbol({ meaning => 'evaluated-at' }), I_arg(1),
        ($lower ? (I_arg(2)) : ()),
        ($upper ? (I_arg(3)) : ())),
      Tokens(I_wrap({}, phys_open($size, $open || T_OTHER('.')),
          I_arg(1), phys_close($size, $close || $c)),
        ($lower ? (T_SUB,   I_arg(2)) : ()),
        ($upper ? (T_SUPER, I_arg(3)) : ())),
      $arg, ($lower || $upper ? (($lower || Tokens()), $upper || Tokens()) : ())); });

Let('\eval', '\evaluated');

# required, TeX only, fences specified
DefMacro('\order',       '\lx@physics@fenced{\order}{order}{\ordersymbol}{(}{)}');
DefMacro('\ordersymbol', '\mathcal{O}');

# Fenced stuff, with semantics & function;
# 2 TeX (only) required arguments, fences specified
DefMacro('\lx@physics@fencedII{}{}{}{}{}', sub {
    my ($gullet, $cs, $semantic, $function, $open, $close) = @_;
    my $size = phys_readSize($gullet);
    my $arg1 = $gullet->readArg();
    my $arg2 = $gullet->readArg();
    return I_dual(
      { reversion => Tokens($cs, phys_revSize($size),
          phys_revArg(I_arg(1)), phys_revArg(I_arg(2))) },
      I_apply({}, I_symbol({ meaning => $semantic }), I_arg(1), I_arg(2)),
      Tokens(phys_open($size, $open), I_arg(1),
        T_OTHER(','), I_arg(2), phys_close($size, $close)),
      $arg1, $arg2); });

DefMacro('\commutator',     '\lx@physics@fencedII{\commutator}{commutator}{}{[}{]}');
DefMacro('\anticommutator', '\lx@physics@fencedII{\anticommutator}{anticommutator}{}{\{}{\}}');
DefMacro('\poissonbracket', '\lx@physics@fencedII{\poissonbracket}{poisson-bracket}{}{\{}{\}}');
Let('\comm',  '\commutator');
Let('\acomm', '\anticommutator');
Let('\pb',    '\poissonbracket');

#======================================================================
# Vector Notation

# TODO: would like to add at least the type (but this isn't quite semantics)
DefConstructor('\lx@physics@mathbfit{}', '#1', bounded => 1, requireMath => 1,
  font      => { shape => 'italic', family => 'serif', series => 'bold', forcebold => 1 },
  reversion => '{\bf\it#1}');
DefMacro('\vectorbold OptionalMatch:* {}',
  '\lx@wrap[role=ID]{\ifx.#1.\mathbf{#2}\else\lx@physics@mathbfit{#2}\fi}');
DefMacro('\vectorarrow OptionalMatch:* {}',
  '\lx@wrap[role=ID]{\math@overrightarrow{\ifx.#1.\mathbf{#2}\else\lx@physics@mathbfit{#2}\fi}}');
DefMacro('\vectorunit OptionalMatch:* {}',
  '\lx@wrap[role=ID]{\hat{\ifx.#1.\mathbf{#2}\else\lx@physics@mathbfit{#2}\fi}}');
Let('\vb', '\vectorbold');
Let('\va', '\vectorarrow');
Let('\vu', '\vectorunit');

DefMathI('\dotproduct',   undef, "\x{22C5}", role => 'MULOP', meaning => 'dot-product');
DefMathI('\crossproduct', undef, UTF(0xD7),  role => 'MULOP', meaning => 'cross-product');
Let('\vdot',  '\dotproduct');
Let('\cross', '\crossproduct');
Let('\cp',    '\crossproduct');

# An operator (required semantics & function);
# Optional TeX or Delimited argument; default no fences
DefMacro('\lx@physics@operator{}{}{}', sub {
    my ($gullet, $cs, $semantic, $function) = @_;
    my $cfunc = I_symbol({ meaning => $semantic });
    my $size  = phys_readSize($gullet);
    my ($arg, $open, $close) = phys_readArg($gullet, 0, %physics_delimiters);
    return I_dual(
      { ($arg ? () : (role => 'OPERATOR')),
        reversion => Tokens($cs, phys_revSize($size),
          ($arg ? phys_revArg(I_arg(1), $open, $close) : ())) },
      ($arg ? I_apply({}, $cfunc, I_arg(1)) : $cfunc),
      Tokens($function, phys_open($size, $open), ($arg ? I_arg(1) : ()), phys_close($size, $close)),
      ($arg ? $arg : ())); });

DefMacro('\gradient',   '\lx@physics@operator{\gradient}{gradient}{\nabla}');
DefMacro('\divergence', '\lx@physics@operator{\divergence}{divergence}{\nabla\cdot}');
DefMacro('\curl',       '\lx@physics@operator{\curl}{curl}{\nabla\cross}');
DefMacro('\laplacian',  '\lx@physics@operator{\laplacian}{laplacian}{\nabla^2}');
Let('\grad',           '\gradient');
Let('\divisionsymbol', '\div');          # Save from amsmath.
Let('\div',            '\divergence');

#======================================================================
# Operators

# An operator with possible power (required semantic & function);
# with optional power (normal LaTeX []); optional TeX or PAREN-delimited arg.
# [I guess, since LaTeX optional arg, the argument delimiters are limited]
DefMacro('\lx@physics@operatorP{}{}{}', sub {
    my ($gullet, $cs, $semantic, $function) = @_;
##    my $cfunc = I_symbol({ meaning => $semantic, role=>'OPFUNCTION' });
    my $cfunc = I_symbol({ meaning => $semantic });
    my $pfunc = $function;
    my $size  = phys_readSize($gullet);
    my $power = $gullet->readOptional;
    my ($arg, $open, $close) = phys_readArg($gullet, 0, '(' => T_OTHER(')'));
    if (!$arg) {    # With no arg, we've got to put back the power!
      $gullet->unread(($power ? (T_OTHER('['), $power, T_OTHER(']')) : ()));
      return I_dual({ reversion => $cs }, $cfunc, $pfunc); }
    else {
      if ($power) {
        $cfunc = I_apply({}, I_symbol({ meaning => 'power' }), $cfunc, I_arg(2));
        $pfunc = I_superscript({ role => 'OPFUNCTION' }, $pfunc, I_arg(2)); }
      return I_dual(
        { reversion => Tokens($cs, phys_revSize($size),
            ($power ? (T_OTHER('['), I_arg(2), T_OTHER(']')) : ()),
            phys_revArg(I_arg(1), $open, $close)) },
        I_apply({}, $cfunc, I_arg(1)),
        Tokens($pfunc,
          phys_open($size, $open || T_OTHER('(')), I_arg(1), phys_close($size, $close || T_OTHER(')'))),
        $arg, ($power ? $power : ())); } });

my @operators = (
  ['sin', 'sine'], ['cos', 'cosine'], ['tan', 'tangent'], ['csc', 'cosecant'], ['sec', 'secant'], ['cot', 'cotangent'], ['hsin', 'hypsine', 'hyperbolic-sine'],
  ['cosh', 'hypcosine', 'hyperbolic-cosine'], ['tanh', 'hyptangent', 'hyperbolic-tangent'], ['csch', 'hypcosecant', 'hyperbolic-cosecant'],
  ['sech', 'hypsecant', 'hyperbolic-secant'], ['coth', 'hypcotangent', 'hyperbolic-cotangent'], ['arcsin', 'arcsine'], ['arccos', 'arccosine'],
  ['arctan', 'arctangent'], ['arccsc', 'arccosecant'], ['arcsec', 'arcsecant'], ['arccot', 'arccotangent'], ['asin', 'asine', 'arcsine'],
  ['acos', 'acosine', 'arccosine'], ['atan', 'atangent', 'arctangent'], ['acsc', 'acosecant', 'arccosecant'], ['asec', 'asecant', 'arcsecant'],
  ['acot', 'acotangent', 'arccotangent'], ['exp', 'exponential'], ['log', 'logarithm'], ['ln', 'naturallogarithm', 'natural-logarithm'],
  ['det', 'determinant'], ['Pr', 'Probability', 'probability']);

while (@operators) {
  my ($shortname, $longname, $meaning) = @{ shift(@operators) };
  $meaning ||= $longname;
  Let('\\' . $longname, '\\' . $shortname);
  DefMacroI('\\' . $shortname, undef,
    "\\lx\@physics\@operatorP{\\$shortname}{$meaning}{\\operatorname{$shortname}}"); }

# New operators
DefMacro('\trace',          '\lx@physics@operatorP{\tr}{trace}{\operatorname{tr}}');
DefMacro('\Trace',          '\lx@physics@operatorP{\Tr}{trace}{\operatorname{Tr}}');
DefMacro('\rank',           '\lx@physics@operatorP{\rank}{rank}{\operatorname{rank}}');
DefMacro('\erf',            '\lx@physics@operatorP{\erf}{error-function}{\operatorname{erf}}');
DefMacro('\Res',            '\lx@physics@operatorP{\Res}{residue}{\operatorname{Res}}');
DefMacro('\principalvalue', '\lx@physics@operatorP{\principalvalue}{principal-value}{\mathcal{P}}');
DefMacro('\PV',             '\lx@physics@operatorP{\PV}{principal-value}{\operatorname{P.V.}}');
Let('\tr', '\trace');
Let('\Tr', '\Trace');
Let('\pv', '\principalvalue');

Let('\real',      '\Re');
Let('\imaginary', '\Im');

# Given semantic & function; Optional TeX arg
DefMacro('\lx@physics@ReIm{}{}{}{}', sub {
    my ($gullet, $cs, $semantic, $raw, $function) = @_;
    my $size  = phys_readSize($gullet);
    my $cfunc = I_symbol({ meaning => $semantic });
    my $arg   = phys_readArg($gullet);
    return I_dual(
      { ($arg ? () : (role => 'OPERATOR')),
        reversion =>
          ($arg
          ? Tokens($cs, phys_revSize($size), phys_revArg(I_arg(1)))
          : $raw) },
      ($arg ? I_apply({}, $cfunc, I_arg(1)) : $cfunc),
      Tokens($function,
        ($arg ? (phys_open($size, T_CS('\lbrace')), I_arg(1), phys_close($size, T_CS('\rbrace'))) : ())),
      ($arg ? $arg : ())); });

DefMacro('\Re', '\lx@physics@ReIm{\Re}{real-part}{\real}{\operatorname{Re}}');
DefMacro('\Im', '\lx@physics@ReIm{\Im}{imaginary-part}{\imaginary}{\operatorname{Im}}');

#======================================================================
# Quick quad text
# TODO: Not sure if these are quite right; but the test file doesn't really use them!
# and uses a weird \Vtextvisiblespace macro!?!
DefMacro('\qqtext OptionalMatch:* {}', '\mbox{\ifx.#1.\quad\fi#2\quad}');
DefMacro('\qcomma',                    ',\quad');
DefMacro('\qcc OptionalMatch:*',       '\mbox{\ifx.#1.\quad\fi c.c.\quad}');
foreach my $word (qw ( if  then else otherwise unless given using assume since
  let for all even odd integer and or as in )) {
  DefMacroI('\q' . $word, 'OptionalMatch:*', '\mbox{\ifx.#1.\quad\fi ' . $word . '\quad}'); }
Let('\qq', '\qqtext');
Let('\qc', '\qcomma');

#======================================================================
# Derivatives
Let('\flatfrac', '\ifrac');

#  => (differential var [degree])
DefMacro('\lx@physics@diff{}{}{}', sub {
    my ($gullet, $cs, $semantic, $diff) = @_;
    my $cfunc  = I_symbol({ meaning => $semantic });
    my $pfunc  = I_wrap({ role => 'DIFFOP' }, $diff);
    my $degree = $gullet->readOptional;
    my ($arg, $open, $close) = phys_readArg($gullet, 0, '(' => T_OTHER(')'));
    return I_dual(
      { ($arg ? () : (role => 'DIFFOP')),
        reversion => Tokens($cs,
          ($degree ? (T_OTHER('['), I_arg(2), T_OTHER(']')) : ()),
          ($arg    ? phys_revArg(I_arg(1), $open, $close)   : ())) },
      ($arg
        ? I_apply({}, $cfunc, I_arg(1), ($degree ? I_arg(2) : ()))
        : ($degree
          ? I_apply({}, I_symbol({ meaning => 'functional-power' }), $cfunc, I_arg(2))
          : $cfunc)),
      Tokens(($degree ? I_superscript({ role => 'DIFFOP' }, $pfunc, I_arg(2)) : $pfunc),
        phys_open(1, $open), ($arg ? I_arg(1) : ()), phys_close(1, $close)),
      $arg, ($degree ? $degree : ())); });

# Trys to be (too) many things (but the syntax is insufficient)
# \derivative{var}
# \derivative[n]{var}
# \derivative{expr}{var}
# \derivative[n]{expr}{var}
# \pderivative{expr}{var1}{var2}
# \derivative{var}(expr)
# \derivative[n]{var}(expr)
# BUT
# \pderivative[n]{expr}{var1}{var2}  n is read but IGNORED
# \pderivative{expr}{var1}{var2}(expr2)  expr2 is read but IGNORED
DefMacro('\lx@physics@deriv{}{}{}', sub {
    my ($gullet, $cs, $semantic, $diff) = @_;
    #    my ($opensize, $closesize) = (T_CS('\left'), T_CS('\right'));
    my $semantic_string = ToString($semantic);

    my $inline = $gullet->readMatch(T_OTHER('*'));
    my $degree = $gullet->readOptional;              # Optional arg is degree
    my $tmp1   = $gullet->readArg();                 # 1st arg required: var1 or expr
    my ($tmp2, $open, $close) = phys_readArg($gullet, 0, '(' => T_OTHER(')'));
    my $tmp3;
    my ($expr, $var, $var2);

    if (($semantic_string =~ /^partial/) && $tmp2 && !$open) {    # if a {} arg, try for 2nd var
      ($tmp3, $open, $close) = phys_readArg($gullet, 0, '(' => T_OTHER(')'));
      $tmp3 = $open = $close = undef if $open; }                  # !!!!!
    if ($tmp3) {
      ($expr, $var, $var2) = ($tmp1, $tmp2, $tmp3); }
    elsif ($tmp2) {
      ($expr, $var) = ($open ? ($tmp2, $tmp1) : ($tmp1, $tmp2)); }
    else {
      $var = $tmp1; }
    # Check if $expr is EMPTY ??? still an operator!
    $expr = undef if IsEmpty($expr);
    my $cfunc = I_symbol({ meaning => $semantic });
    my $pfunc = I_wrap({ role => 'DIFFOP' }, $diff);
    if ($tmp3) {    # double derivative!
                    # power is IGNORED!
##      my $op = I_apply({}, I_apply({}, $cfunc, I_arg(2)), I_apply({}, $cfunc, I_arg(3)));
      my $op = I_apply({}, $cfunc, I_arg(2), T_OTHER('1'), I_arg(3), T_OTHER('1'));
      return I_dual(
        { ($expr ? () : (role => 'DIFFOP')),
          reversion => Tokens($cs, ($inline ? T_OTHER('*') : ()),
            phys_revArg(I_arg(1)), phys_revArg(I_arg(2)), phys_revArg(I_arg(3))) },
        ($expr ? I_apply({}, $op, I_arg(1)) : $op),
        Invocation(($inline ? T_CS('\ifrac') : T_CS('\frac')),
          Tokens(I_superscript({ role => 'DIFFOP' }, $pfunc, T_OTHER(2)), ($expr ? I_arg(1) : ())),
          Tokens(I_apply({}, $pfunc, I_arg(2)),                           I_apply({}, $pfunc, I_arg(3)))),
        $expr, $var, $var2); }
    else {
      my $op = I_apply({}, $cfunc, I_arg(2), ($degree ? I_arg(3) : ()));
      return I_dual(
        { ($expr ? () : (role => 'DIFFOP')),
          reversion => Tokens($cs, ($inline ? T_OTHER('*') : ()),
            ($degree ? (T_OTHER('['), I_arg(3), T_OTHER(']')) : ()),
            ($open
              ? (phys_revArg(I_arg(2)), phys_revArg(I_arg(1), $open, $close))
              : ($expr
                ? (phys_revArg(I_arg(1)), phys_revArg(I_arg(2), $open, $close))
                : (phys_revArg(I_arg(2)))))) },
        ($expr ? I_apply({}, $op, I_arg(1)) : $op),
        Tokens(
          Invocation(($inline ? T_CS('\ifrac') : T_CS('\frac')),
            Tokens(($degree ? I_superscript({ role => 'DIFFOP' }, $pfunc, I_arg(3)) : $pfunc),
              (!$expr || $open ? () : I_arg(1))),
            ($degree ? I_superscript({}, I_apply({}, $pfunc, I_arg(2)), I_arg(3))
              : I_apply({}, $pfunc, I_arg(2)))),
          ($expr && $open
            ? (T_CS('\lx@ApplyFunction'), phys_open(1, $open), I_arg(1), phys_close(1, $close))
            : ())),
        $expr, $var, $degree); } });

DefMacro('\differential', '\lx@physics@diff{\differential}{differential}{\mathrm{d}}');
DefMacro('\variation',    '\lx@physics@diff{\variation}{variation}{\delta}');
DefMacro('\derivative',
  '\lx@physics@deriv{\derivative}{derivative}{\mathrm{d}}');
DefMacro('\partialderivative',
  '\lx@physics@deriv{\partialderivative}{partial-derivative}{\partial}');
DefMacro('\functionalderivative',
  '\lx@physics@deriv{\functionalderivative}{functional-derivative}{\delta}');

Let('\dd',          '\differential');
Let('\var',         '\variation');
Let('\dv',          '\derivative');
Let('\pdv',         '\partialderivative');
Let('\pderivative', '\partialderivative');
Let('\fdv',         '\functionalderivative');

#======================================================================
# Dirac bra-ket notation

DefMacro('\ket', sub {
    my ($gullet) = @_;
    my $size     = phys_readSize($gullet);
    my $arg      = $gullet->readArg();
    return I_dual(
      { reversion => Tokens(T_CS('\ket'), phys_revSize($size), phys_revArg(I_arg(1))) },
      I_apply({}, I_symbol({ meaning => 'ket' }), I_arg(1)),
      Tokens(phys_open($size, T_CS('\vert')), I_arg(1), phys_close($size, T_CS('\rangle'))),
      $arg); });

DefMacro('\bra', sub {
    my ($gullet) = @_;
    my $size     = ($gullet->readMatch(T_OTHER('*')) ? 0 : 1);
    my $arg      = $gullet->readArg();
    if ($gullet->readMatch(T_CS('\ket'))) {    # join to make braket!
      $size = 0 if $gullet->readMatch(T_OTHER('*'));    # star here, too
      my $arg2 = $gullet->readArg();
      return I_dual(
        { reversion => Tokens(T_CS('\bra'), phys_revSize($size), phys_revArg(I_arg(1)),
            T_CS('\ket'), phys_revSize($size), phys_revArg(I_arg(2))) },
        I_apply({}, I_symbol({ meaning => 'inner-product' }), I_arg(1), I_arg(2)),
        Tokens(phys_open($size, T_CS('\langle')), I_arg(1),
          phys_mid($size, T_CS('\vert')), I_arg(2), phys_close($size, T_CS('\rangle'))),
        $arg, $arg2); }
    else {
      return I_dual(
        { reversion => Tokens(T_CS('\bra'), phys_revSize($size), phys_revArg(I_arg(1))) },
        I_apply({}, I_symbol({ meaning => 'bra' }), I_arg(1)),
        Tokens(phys_open($size, T_CS('\langle')), I_arg(1),
          phys_close($size, T_CS('\vert'))),
        $arg); } });

DefMacro('\lx@physics@qm@product{}{}{}{}{}', sub {
    my ($gullet, $cs, $semantic, $open, $middle, $close) = @_;
    my $cfunc = I_symbol({ meaning => $semantic });
    my $size  = ($gullet->readMatch(T_OTHER('*')) ? 0 : 1);
    my $arg0  = $gullet->readArg();
    my $argx  = phys_readArg($gullet);
    my $arg1  = $argx || $arg0;
    return I_dual(
      { reversion => Tokens($cs, phys_revSize($size),
          phys_revArg(I_arg(1)), phys_revArg(I_arg(2))) },
      I_apply({}, I_symbol({ meaning => $semantic }), I_arg(1), I_arg(2)),
      Tokens(phys_open($size, $open), I_arg(1),
        phys_mid($size, $middle), I_arg(2), phys_close($size, $close)),
      $arg0, $arg1); });

DefMacro('\innerproduct',
  '\lx@physics@qm@product{\innerproduct}{inner-product}{\langle}{\vert}{\rangle}');
DefMacro('\outerproduct',
  '\lx@physics@qm@product{\outerproduct}{outer-product}{\vert}{\rangle\langle}{\vert}');

DefMacro('\expectationvalue', sub {
    my ($gullet) = @_;
    my $cfunc    = I_symbol({ meaning => 'expectation-value' });
    my $size     = ($gullet->readMatch(T_OTHER('*'))
      ? ($gullet->readMatch(T_OTHER('*')) ? 1 : 0)
      : 1);
    my ($open, $middle, $close) =
      (phys_open($size, T_CS('\langle')), phys_mid($size, T_CS('\vert')), phys_close($size, T_CS('\rangle')));
    my $arg0 = $gullet->readArg();
    my $arg1 = phys_readArg($gullet);
    if ($arg1) {
      return I_dual(
        { reversion => Tokens(T_CS('\expectationvalue'), ($size ? () : T_OTHER('*')),
            phys_revArg(I_arg(1)), phys_revArg(I_arg(2))) },
        I_apply({}, $cfunc, I_arg(1), I_arg(2), I_arg(3)),
        Tokens($open, I_arg(2), $middle, I_arg(1), $middle, I_arg(3), $close),
        $arg0, $arg1, $arg1); }
    else {
      return I_dual(
        { reversion => Tokens(T_CS('\expectationvalue'), ($size ? () : T_OTHER('*')),
            phys_revArg(I_arg(1))) },
        I_apply({}, $cfunc, I_arg(1)),
        Tokens($open, I_arg(1), $close),
        $arg0); } });

DefMacro('\matrixelement', sub {
    my ($gullet) = @_;
    my $cfunc    = I_symbol({ meaning => 'expectation-value' });
    my $size     = ($gullet->readMatch(T_OTHER('*'))
      ? ($gullet->readMatch(T_OTHER('*')) ? 1 : 0)
      : 1);
    my ($open, $middle, $close) =
      (phys_open($size, T_CS('\langle')), phys_mid($size, T_CS('\vert')), phys_close($size, T_CS('\rangle')));
    my $arg0 = $gullet->readArg();
    my $arg1 = $gullet->readArg();
    my $arg2 = $gullet->readArg();
    return I_dual(
      { reversion => Tokens(T_CS('\matrixelement'), ($size ? () : T_OTHER('*')),
          phys_revArg(I_arg(1)), phys_revArg(I_arg(2)), phys_revArg(I_arg(3))) },
      I_apply({}, $cfunc, I_arg(2), I_arg(1), I_arg(3)),
      Tokens($open, I_arg('1'), $middle, I_arg(2), $middle, I_arg(3), $close),
      $arg0, $arg1, $arg2); });

Let('\braket',   '\innerproduct');
Let('\ip',       '\innerproduct');
Let('\dyad',     '\outerproduct');
Let('\ketbra',   '\outerproduct');
Let('\op',       '\outerproduct');
Let('\expval',   '\expectationvalue');
Let('\ev',       '\expectationvalue');
Let('\matrixel', '\matrixelement');
Let('\mel',      '\matrixelement');

#======================================================================
# Matrix macros

# The following create the contents of a matrix, but not the matrix environment.
DefMacro('\identitymatrix{}', sub {
    my ($gullet, $n) = @_;
    $n = ToString($n);
    my @tokens = ();
    for (my $i = 0 ; $i < $n ; $i++) {
      push(@tokens, T_CS("\\\\")) if $i > 0;
      for (my $j = 0 ; $j < $n ; $j++) {
        push(@tokens, ($j > 0 ? T_ALIGN : ()), ($i == $j ? T_OTHER(1) : T_OTHER(0))); } }
    return Tokens(@tokens); });

DefMacro('\xmatrix OptionalMatch:* {}{}{}', sub {
    my ($gullet, $star, $item, $n, $m) = @_;
    $n = ToString($n);
    $m = ToString($m);
    my @tokens = ();
    for (my $i = 0 ; $i < $n ; $i++) {
      push(@tokens, T_CS("\\\\")) if $i > 0;
      for (my $j = 0 ; $j < $m ; $j++) {
        push(@tokens, ($j > 0 ? T_ALIGN : ()),
          $item,
          ($star
            ? Invocation(T_CS('\@@POSTSUBSCRIPT'),
              ($n == 1 ? T_OTHER($j + 1)
                : ($m == 1 ? T_OTHER($i + 1)
                  : Tokens(T_OTHER($i + 1), T_CS('\lx@InvisibleComma'), T_OTHER($j + 1)))))    # one based
            : ())
        ); } }
    return Tokens(@tokens); });

DefMacro('\zeromatrix{}{}', '\xmatrix{0}{#1}{#2}');

# This really should be a stock (internal) def!
DefMath('\lx@physics@iunit', '\mathit{i}', meaning => 'imaginary-unit', alias => 'i');
DefMacro('\paulimatrix{}', sub {
    my ($gullet, $n) = @_;
    $n = ToString($n);
    if ($n == 0) { Tokens(T_OTHER(1), T_ALIGN, T_OTHER(0), T_CS("\\\\"),
        T_OTHER(0), T_ALIGN, T_OTHER(1)); }
    elsif ($n == 1) { Tokens(T_OTHER(0), T_ALIGN, T_OTHER(1), T_CS("\\\\"),
        T_OTHER(1), T_ALIGN, T_OTHER(0)); }
    elsif ($n == 2) { Tokens(T_OTHER(0), T_ALIGN, T_OTHER('-'), T_CS('\lx@physics@iunit'), T_CS("\\\\"),
        T_CS('\lx@physics@iunit'), T_ALIGN, T_OTHER(0)); }
    elsif ($n == 3) { Tokens(T_OTHER(1), T_ALIGN, T_OTHER(0), T_CS("\\\\"),
        T_OTHER(0), T_ALIGN, T_OTHER('-'), T_OTHER(1)); }
    else { (); } });

DefMacro('\diagonalmatrix[]{}', sub {
    my ($gullet, $z, $diag) = @_;
    my @diag   = SplitTokens($diag, T_OTHER(','));
    my @tokens = ();
    $z = T_SPACE unless $z;
    my $n = scalar(@diag);
    for (my $i = 0 ; $i < $n ; $i++) {
      push(@tokens, T_CS("\\\\")) if $i > 0;
      for (my $j = 0 ; $j < $n ; $j++) {
        push(@tokens, T_ALIGN) if $j > 0;
        if ($i == $j) {
          my @t = @{ shift(@diag) };    # submatrix? (crazy)
          if (grep { $_->equals(T_ALIGN) } @t) {
            push(@tokens, T_CS('\matrix'), @t, T_CS('\endmatrix')); }
          else {
            push(@tokens, @t); } }
        else { push(@tokens, $z); } } }
    return Tokens(@tokens); });

DefMacro('\antidiagonalmatrix[]{}', sub {
    my ($gullet, $z, $diag) = @_;
    my @diag   = SplitTokens($diag, T_OTHER(','));
    my @tokens = ();
    $z = T_SPACE unless $z;
    my $n = scalar(@diag);
    for (my $i = 0 ; $i < $n ; $i++) {
      push(@tokens, T_CS("\\\\")) if $i > 0;
      for (my $j = 0 ; $j < $n ; $j++) {
        push(@tokens, T_ALIGN) if $j > 0;
        if ($j == $n - $i - 1) {
          my @t = @{ shift(@diag) };    # submatrix? (crazy)
          if (grep { $_->equals(T_ALIGN) } @t) {
            push(@tokens, T_CS('\matrix'), @t, T_CS('\endmatrix')); }
          else {
            push(@tokens, Tokens(@t)); } }
        else { push(@tokens, $z); } } }
    return Tokens(@tokens); });

# This wraps matrix content in an matrix environment, possibly with delimiters
# No obvious semantics, other than type
# The reversions SHOULD be omitting the \begin matrix \end matrix stuff!!!! How?
DefMacro('\lx@physics@mat{}{}{}{}{}', sub {
    my ($gullet, $cs, $semantic, $env, $defopen, $defclose) = @_;
    my $alt = $gullet->readMatch(T_OTHER('*'));
    $semantic = undef if IsEmpty($semantic);
    my $cfunc = $semantic && I_symbol({ meaning => $semantic });
    $env = ToString($env);
    my ($body, $open, $close) = phys_readArg($gullet, 1, %physics_delimiters);
    my $matrix = Tokens(T_CS('\\' . $env), $body, T_CS('\end' . $env));
    return I_dual(
      { reversion => Tokens($cs, ($alt ? T_OTHER('*') : ()), phys_revArg(I_arg(1), $open, $close)) },
      ($cfunc
        ? I_apply({}, $cfunc, I_arg(1))
        : I_arg(1)),
      Tokens(phys_open(1, $open || $defopen), I_arg(1), phys_close(1, $close || $defclose)),
      $matrix);
});

# Like matrix & smallmatrix except NO NAME.
DefMacro('\lx@physics@matrix',         '\lx@ams@matrix{datameaning=matrix}');
DefMacro('\endlx@physics@matrix',      '\lx@end@ams@matrix');
DefMacro('\lx@physics@smallmatrix',    '\lx@ams@matrix{datameaning=matrix,style=\scriptsize}');
DefMacro('\endlx@physics@smallmatrix', '\lx@end@ams@matrix');

DefMacro('\matrixquantity',      '\lx@physics@mat{\matrixquantity}{}{lx@physics@matrix}{}{}');
DefMacro('\pmqty{}',             '\lx@physics@mat{\pmqty}{}{lx@physics@matrix}{(}{)}');
DefMacro('\Pmqty{}',             '\lx@physics@mat{\Pmqty}{}{lx@physics@matrix}{(}{)}');
DefMacro('\bmqty{}',             '\lx@physics@mat{\bmqty}{}{lx@physics@matrix}{[}{]}');
DefMacro('\vmqty{}',             '\lx@physics@mat{\vmqty}{}{lx@physics@matrix}{\vert}{\vert}');
DefMacro('\smallmatrixquantity', '\lx@physics@mat{\smallmatrixquantity}{}{lx@physics@smallmatrix}{}{}');
DefMacro('\spmqty{}', '\lx@physics@mat{\spmqty}{}{lx@physics@smallmatrix}{(}{)}');
DefMacro('\sPmqty{}', '\lx@physics@mat{\sPmqty}{}{lx@physics@smallmatrix(}{)}');
DefMacro('\sbmqty{}', '\lx@physics@mat{\sbmqty}{}{lx@physics@smallmatrix}{[}{]}');
DefMacro('\svmqty{}', '\lx@physics@mat{\svmqty}{}{lx@physics@smallmatrix}{\vert}{\vert}');

DefMacro('\matrixdeterminant',
  '\lx@physics@mat{\matrixdeterminant}{determinant}{matrix}{\vert}{\vert}');
DefMacro('\smallmatrixdeterminant',
  '\lx@physics@mat{\smallmatrixdeterminant}{determinant}{smallmatrix}{\vert}{\vert}');

Let('\imat',  '\identitymatrix');
Let('\xmat',  '\xmatrix');
Let('\zmat',  '\zeromatrix');
Let('\pmat',  '\paulimatrix');
Let('\dmat',  '\diagonalmatrix');
Let('\admat', '\antidiagonalmatrix');
Let('\mqty',  '\matrixquantity');
Let('\smqty', '\smallmatrixquantity');
Let('\smqty', '\smallmatrixquantity');
Let('\mdet',  '\matrixdeterminant');
Let('\smdet', '\smallmatrixdeterminant');

#======================================================================
1;