# -*- mode: Perl -*-
# /=====================================================================\ #
# |  calc.sty                                                           | #
# | 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;

## TODO: Refactor goals.
## There is a choice made in this binding, where the core macros of calc.sty are left undefined
## (e.g. \ratio, \minOf, etc)
## and are instead consumed incrementally. However, for that to succeed with the correct implementation
## of readXToken, we would need to define them explicitly, or we get "undefined cs was expanded" errors.
DefPrimitive('\minof',    '');
DefPrimitive('\maxof',    '');
DefPrimitive('\widthof',  '');
DefPrimitive('\heightof', '');
DefPrimitive('\ratio',    '');
DefPrimitive('\real',     '');

# \setcounter{<ctr>}{<integer expression>}
DefPrimitive('\setcounter{}{}', sub {
    my ($stomach, $ctr, $arg) = @_;
    SetCounter($ctr, readExpression($stomach->getGullet, 'Number', $arg));
    return; });

# \addtocounter{<ctr>}{<integer expression>}
DefPrimitive('\addtocounter{}{}', sub {
    my ($stomach, $ctr, $arg) = @_;
    AddToCounter($ctr, readExpression($stomach->getGullet, 'Number', $arg));
    return; });

DefPrimitive('\setlength{Variable}{}', sub {
    my ($stomach, $var, $arg) = @_;
    my ($defn, @args) = @$var;
    return unless $defn && ($defn ne 'missing');
    $defn->setValue(readExpression($stomach->getGullet, 'Glue', $arg), @args); });

DefPrimitive('\addtolength{Variable}{}', sub {
    my ($stomach, $var, $arg) = @_;
    my ($defn, @args) = @$var;
    return unless $defn && ($defn ne 'missing');
    $defn->setValue($defn->valueOf(@args)->add(readExpression($stomach->getGullet, 'Glue', $arg)),
      @args); });

DefPrimitive('\settowidth{Variable}{}', sub {
    my ($stomach, $var, $arg) = @_;
    my ($defn, @args) = @$var;
    my $box = Digest($arg);
    return unless $defn && ($defn ne 'missing');
    $defn->setValue($box->getWidth, @args); });

DefPrimitive('\settoheight{Variable}{}', sub {
    my ($stomach, $var, $arg) = @_;
    my ($defn, @args) = @$var;
    return unless $defn && ($defn ne 'missing');
    my $box = Digest($arg);
    $defn->setValue($box->getHeight, @args); });

DefPrimitive('\settodepth{Variable}{}', sub {
    my ($stomach, $var, $arg) = @_;
    my ($defn, @args) = @$var;
    return unless $defn && ($defn ne 'missing');
    my $box = Digest($arg);
    $defn->setValue($box->getDepth, @args); });

DefPrimitive('\settototalheight{Variable}{}', sub {
    my ($stomach, $var, $arg) = @_;
    my ($defn, @args) = @$var;
    return unless $defn && ($defn ne 'missing');
    my $box = Digest($arg);
    $defn->setValue($box->getHeight + $box->getDepth, @args); });

#======================================================================
# Grammar as specified in calc.sty
# type = integer | dimen | glue
# <type expression>    -> <type term>
#                       | <type expression><plus or minus><type term>
# <type term>          -> <type term><type scan stop>
#                       | <type factor>
#                       | <type term><multiply or divide><integer>
#                       | <type term><multiply or divide><real number>
#                       | <type term><multiply or divide><max or min integer>
# <type scan stop>     -> <empty>
#                       | <optional space>
#                       | \relax
# <type factor>         -> <type>
#                       | <text dimen factor>
#                       | <max or min type>
#                       | ( <type expression> )
# <integer>            -> <number>
# <max or min type>    -> <max or min command>{<type expression>}{<type expression>}
# <max or min command> -> \maxof | \minof
# <text dimen factor>  -> <text dimen command>{<text>}
# <text dimen command> -> \widthof | \heightof | \depthof | \totalheightof
# <multiply or divide> -> * | /
# <real number>        -> \ratio{<dimen expression>}{<dimen expression>}
#                       | \real{<optional signs><decimal constant>}
# <plus or minus>      -> + | -
# <decimal constant>   -> . | , | <digit><decimal constant> |<decimal constant><digit>
# <digit>              -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
# <optional signs>     -> <optional spaces>
#                       | <optional signs><plus or minus><optional spaces>
#======================================================================
# LaTeXML implementation, not exactly the same grammar as above
# (but I'm not convinced that grammar is what the latex version does)
# Here, type = Number, Glue

sub readExpression {
  my ($ogullet, $type, $tokens) = @_;
  return $ogullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {
      my ($gullet) = @_;
      $gullet->unread($tokens);    # Really so weird?
      my $term = readTerm($gullet, $type);
      while (my $op = $gullet->readKeyword('+', '-')) {
        my $term2 = readTerm($gullet, $type);
        $term = ($op eq '+' ? $term->add($term2) : $term->subtract($term2)); }
      return $term; }); }

sub readTerm {
  my ($gullet, $type) = @_;
  $gullet->skipSpaces;
  # my $factor = readTextDimenFactor($gullet) # but only if type is dimen!
  #   || readReal($gullet)                  # ? only sometimes
  #   || readMinMax($gullet,$type)
  #     || readParenthesized($gullet,$type)
  #       || $gullet->readValue($type);
  my $factor = readValue($gullet, $type);
  while (my $op = $gullet->readKeyword('*', '/')) {
    # my $factor2 = readReal($gullet)
    #   || readMinMax($gullet,'Number')
    #   || readParenthesized($gullet,'Number')
    #   || $gullet->readNumber();
    my $factor2 = readValue($gullet, 'Number');
    $factor = ($op eq '*'
      ? $factor->multiply($factor2)
      : $factor->divide($factor2)); }
  return $factor; }

sub readValue {
  my ($gullet, $type) = @_;
  $gullet->skipSpaces;
  my $peek = $gullet->readXToken();
  # Evaluate <Text Dimen Factors>
  if (Equals($peek, T_CS('\widthof'))) {
    my $box = Digest($gullet->readArg);
    Error('unexpected', $peek, $gullet, "\\widthof not expected here (reading $type)")
      if $type eq 'Number';
    return $box->getWidth; }
  elsif (Equals($peek, T_CS('\heightof'))) {
    my $box = Digest($gullet->readArg);
    Error('unexpected', $peek, $gullet, "\\heightof not expected here (reading $type)")
      if $type eq 'Number';
    return $box->getHeight; }
  elsif (Equals($peek, T_CS('\depthof'))) {
    my $box = Digest($gullet->readArg);
    Error('unexpected', $peek, $gullet, "\\depthof not expected here (reading $type)")
      if $type eq 'Number';
    return $box->getDepth; }
  elsif (Equals($peek, T_CS('\totalheightof'))) {
    my $box = Digest($gullet->readArg);
    Error('unexpected', $peek, $gullet, "\\totalheightof not expected here (reading $type)")
      if $type eq 'Number';
    return $box->getHeight->add($box->getDepth); }
  # Evaluate <Real> values
  elsif (Equals($peek, T_CS('\real'))) {
    my $arg = $gullet->readArg;
    return $gullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {
        my ($igullet) = @_;
        $igullet->unread($arg);    # Really so weird?
        return $igullet->readFloat(); }); }
  elsif (Equals($peek, T_CS('\ratio'))) {
    my $x = readExpression($gullet, 'Glue', $gullet->readArg);
    my $y = readExpression($gullet, 'Glue', $gullet->readArg);
    my $y_pt = $y->ptValue;
    if ($y_pt == 0) {
      Warn("unexpected", "<number>", "calc: divisor should never be zero!");
      $y_pt = 1; }
    return Float($x->ptValue / $y_pt); }
  # Evaluate MinMax values
  elsif (Equals($peek, T_CS('\minof'))) {
    my $x = readExpression($gullet, $type, $gullet->readArg);
    my $y = readExpression($gullet, $type, $gullet->readArg);
    return $x->smaller($y); }
  elsif (Equals($peek, T_CS('\maxof'))) {
    my $x = readExpression($gullet, $type, $gullet->readArg);
    my $y = readExpression($gullet, $type, $gullet->readArg);
    return $x->larger($y); }
  # Read & Evaluate parenthesized subexpressions
  elsif (Equals($peek, T_OTHER('('))) {
    return readExpression($gullet, $type, $gullet->readUntil(T_OTHER(')'))); }
  # Else read literal values.
  else {
    $gullet->unread($peek);
    if ($type eq 'Number') {
      return $gullet->readNumber(); }
    else {    # Really should read glue, but downgrade to dimension????
      return $gullet->readGlue(); } } }

sub readMinMax {
  my ($gullet, $type) = @_;
  $gullet->skipSpaces;
  my $peek = $gullet->readXToken();
  if (Equals($peek, T_CS('\minof'))) {
    my $x = readExpression($gullet, $type, $gullet->readArg);
    my $y = readExpression($gullet, $type, $gullet->readArg);
    return $x->smaller($y); }
  elsif (Equals($peek, T_CS('\maxof'))) {
    my $x = readExpression($gullet, $type, $gullet->readArg);
    my $y = readExpression($gullet, $type, $gullet->readArg);
    return $x->larger($y); }
  else {
    $gullet->unread($peek);
    return; } }

sub readTextDimenFactor {
  my ($gullet) = @_;
  $gullet->skipSpaces;
  my $peek = $gullet->readXToken();
  if (Equals($peek, T_CS('\widthof'))) {
    my $box = Digest($gullet->readArg);
    return Dimension(0); }
  elsif (Equals($peek, T_CS('\heightof'))) {
    my $box = Digest($gullet->readArg);
    return Dimension(0); }
  elsif (Equals($peek, T_CS('\depthof'))) {
    my $box = Digest($gullet->readArg);
    return Dimension(0); }
  elsif (Equals($peek, T_CS('\totalheightof'))) {
    my $box = Digest($gullet->readArg);
    return Dimension(0); }
  else {
    $gullet->unread($peek);
    return; } }

sub readParenthesized {
  my ($gullet, $type) = @_;
  $gullet->skipSpaces;
  if (my $op = $gullet->readKeyword('(')) {
    return readExpression($gullet, $type, $gullet->readUntil(T_OTHER(')'))); }
  else {
    return; } }

sub readReal {
  my ($gullet) = @_;
  $gullet->skipSpaces;
  my $peek = $gullet->readXToken();
  if (Equals($peek, T_CS('\real'))) {
    my $arg = $gullet->readArg;
    return $gullet->readingFromMouth(LaTeXML::Core::Mouth->new(), sub {
        my ($igullet) = @_;
        $igullet->unread($arg);    # Really so weird?
        return $igullet->readFloat(); }); }
  elsif (Equals($peek, T_CS('\ratio'))) {
    my $x = readExpression($gullet, 'Glue', $gullet->readArg);
    my $y = readExpression($gullet, 'Glue', $gullet->readArg);
    my $y_pt = $y->ptValue;
    if ($y_pt == 0) {
      Warn("unexpected", "<number>", "calc: divisor should never be zero!");
      $y_pt = 1; }
    return Float($x->ptValue / $y_pt); }
  else {
    $gullet->unread($peek);
    return; } }

#**********************************************************************
1;
