-- Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd.
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
--     - Redistributions of source code must retain the above copyright
--       notice, this list of conditions and the following disclaimer.
--
--     - Redistributions in binary form must reproduce the above copyright
--       notice, this list of conditions and the following disclaimer in
--       the documentation and/or other materials provided with the
--       distribution.
--
--     - Neither the name of The Numerical ALgorithms Group Ltd. nor the
--       names of its contributors may be used to endorse or promote products
--       derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-- PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
-- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import '"i-coerfn"
)package "BOOT"

$constructorExposureList := '(Boolean Integer String)
$domPvar := nil



sayFunctionSelection(op,args,target,dc,func) ==
  $abbreviateTypes : local := true
  startTimingProcess 'debug
  fsig := formatSignatureArgs args
  if not LISTP fsig then fsig := LIST fsig
  if func then func := bright ['"by ",func]
  sayMSG concat ['%l,:bright '"Function Selection for",op,:func,'%l,
    '"      Arguments:",:bright fsig]
  if target then sayMSG concat ['"      Target type:",
    :bright prefix2String target]
  if dc  then sayMSG concat ['"      From:     ",
    :bright prefix2String dc]
  stopTimingProcess 'debug

sayFunctionSelectionResult(op,args,mmS) ==
  $abbreviateTypes : local := true
  startTimingProcess 'debug
  if mmS then printMms mmS
  else sayMSG concat ['"   -> no function",:bright op,
    '"found for arguments",:bright formatSignatureArgs args]
  stopTimingProcess 'debug

selectMms(op,args,$declaredMode) ==
  -- selects applicable modemaps for node op and arguments args
  -- if there is no local modemap, and it is not a package call, then
  --   the cached function selectMms1 is called
  startTimingProcess 'modemaps
  n:= getUnname op
  val := getValue op
  opMode := objMode val

  -- see if we have a functional parameter
  ((isSharpVarWithNum(n) and opMode) or (val and opMode)) and
      opMode is ['Mapping,:ta] =>
        imp :=
          val => wrapped2Quote objVal val
          n
        [[['local,:ta], imp , NIL]]

  ((isSharpVarWithNum(n) and opMode) or (val and opMode)) and
      opMode is ['Variable,f] =>
         emptyAtree op
         op.0 := f
         selectMms(op,args,$declaredMode)

  isSharpVarWithNum(n) and opMode is ['FunctionCalled,f] =>
         op.0 := f
         selectMms(op,args,$declaredMode)

  types1 := getOpArgTypes(n,args)
  numArgs := #args
  member('(SubDomain (Domain)),types1) => NIL
  member($EmptyMode,types1) => NIL

  tar := getTarget op
  dc  := getAtree(op,'dollar)

  null dc and val and objMode(val) = $AnonymousFunction =>
      tree := mkAtree objValUnwrap getValue op
      putTarget(tree,['Mapping,tar,:types1])
      bottomUp tree
      val := getValue tree
      [[['local,:rest objMode val], wrapped2Quote objVal val, NIL]]

  if (n = 'map) and (first types1 = $AnonymousFunction)
    then
      tree := mkAtree objValUnwrap getValue first args
      ut :=
        tar => underDomainOf tar
        NIL
      ua := [underDomainOf x for x in rest types1]
      member(NIL,ua) => NIL
      putTarget(tree,['Mapping,ut,:ua])
      bottomUp tree
      val := getValue tree
      types1 := [objMode val,:rest types1]
      RPLACA(args,tree)

  if numArgs = 1 and (n = "numer" or n = "denom") and
    isEqualOrSubDomain(first types1,$Integer) and null dc then
      dc := ['Fraction, $Integer]
      putAtree(op, 'dollar, dc)


  if $reportBottomUpFlag then sayFunctionSelection(n,types1,tar,dc,NIL)

  identType := 'Variable
  for x in types1 while not $declaredMode repeat
      not EQCAR(x,identType) => $declaredMode:= x
  types2 := [altTypeOf(x,y,$declaredMode) for x in types1 for y in args]

  mmS:=
    dc => selectDollarMms(dc,n,types1,types2)

    if n = "/" and tar = $Integer then
      tar := $RationalNumber
      putTarget(op,tar)

    -- now to speed up some standard selections
    if not tar then
      tar := defaultTarget(op,n,#types1,types1)
      if tar and $reportBottomUpFlag then
        sayMSG concat ['"      Default target type:",
          :bright prefix2String tar]

    selectLocalMms(op,n,types1,tar) or
      (VECTORP op and selectMms1(n,tar,types1,types2,'T))
  if $reportBottomUpFlag then sayFunctionSelectionResult(n,types1,mmS)
  stopTimingProcess 'modemaps
  mmS

-- selectMms1 is in clammed.boot

selectMms2(op,tar,args1,args2,$Coerce) ==
  -- decides whether to find functions from a domain or package
  --   or by general modemap evaluation
  or/[STRINGP arg for arg in args1] => NIL
  if tar = $EmptyMode then tar := NIL
  nargs := #args1
  mmS := NIL
  mmS :=
    -- special case map for the time being
    $Coerce and (op = 'map) and (2 = nargs) and
      (first(args1) is ['Variable,fun]) =>
        null (ud := underDomainOf CADR args1) => NIL
        if tar then ut := underDomainOf(tar)
        else ut := nil
        null (mapMms := selectMms1(fun,ut,[ud],[NIL],true)) => NIL
        mapMm := CDAAR mapMms
        selectMms1(op,tar,[['Mapping,:mapMm],CADR args1],
          [NIL,CADR args2],$Coerce)

    $Coerce and (op = 'map) and (2 = nargs) and
      (first(args1) is ['FunctionCalled,fun]) =>
        null (ud := underDomainOf CADR args1) => NIL
        if tar then ut := underDomainOf(tar)
        else ut := nil
        funNode := mkAtreeNode fun
        transferPropsToNode(fun,funNode)
        null (mapMms := selectLocalMms(funNode,fun,[ud],NIL)) => NIL
        mapMm := CDAAR mapMms
        selectMms1(op,tar,[['Mapping,:mapMm],CADR args1],
          [NIL,CADR args2],$Coerce)

    -- get the argument domains and the target
    a := nil
    for x in args1 repeat if x then a := cons(x,a)
    for x in args2 repeat if x then a := cons(x,a)
    if tar and not isPartialMode tar then a := cons(tar,a)

    -- for typically homogeneous functions, throw in resolve too
    if op in '(_= _+ _* _- ) then
      r := resolveTypeList a
      if r ^= nil then a := cons(r,a)

    if tar and not isPartialMode tar then
      if xx := underDomainOf(tar) then a := cons(xx,a)
    for x in args1 repeat
      PAIRP(x) and CAR(x) in '(List Vector Stream FiniteSet Array) =>
        xx := underDomainOf(x) => a := cons(xx,a)

    -- now extend this list with those from the arguments to
    -- any Unions, Mapping or Records

    a' := nil
    a := nreverse REMDUP a
    for x in a repeat
      null x => 'iterate
      x = '(RationalRadicals) => a' := cons($RationalNumber,a')
      x is ['Union,:l] =>
        -- check if we have a tagged union
        l and first l is [":",:.] =>
          for [.,.,t] in l repeat
            a' := cons(t,a')
        a' := append(reverse l,a')
      x is ['Mapping,:l] => a' := append(reverse l,a')
      x is ['Record,:l] =>
        a' := append(reverse [CADDR s for s in l],a')
      x is ['FunctionCalled,name] =>
        (xm := get(name,'mode,$e)) and not isPartialMode xm =>
          a' := cons(xm,a')
    a := append(a,REMDUP a')
    a := [x for x in a | PAIRP(x)]

    -- step 1. see if we have one without coercing
    a' := a
    while a repeat
      x:= CAR a
      a:= CDR a
      ATOM x => 'iterate
      mmS := append(mmS, findFunctionInDomain(op,x,tar,args1,args2,NIL,NIL))

    -- step 2. if we didn't get one, trying coercing (if we are
    --         suppose to)

    if null(mmS) and $Coerce then
      a := a'
      while a repeat
        x:= CAR a
        a:= CDR a
        ATOM x => 'iterate
        mmS := append(mmS,
          findFunctionInDomain(op,x,tar,args1,args2,$Coerce,NIL))

    mmS or selectMmsGen(op,tar,args1,args2)
  mmS and orderMms(op, mmS,args1,args2,tar)

isAVariableType t ==
    t is ['Variable,.] or t = $Symbol or t is ['OrderedVariableList,.]

defaultTarget(opNode,op,nargs,args) ==
  -- this is for efficiency. Chooses standard targets for operations
  -- when no target exists.

  target := nil

  nargs = 0 =>
    op = 'nil =>
      putTarget(opNode, target := '(List (None)))
      target
    op = 'true  or op = 'false =>
      putTarget(opNode, target := $Boolean)
      target
    op = 'pi =>
      putTarget(opNode, target := ['Pi])
      target
    op = 'infinity =>
      putTarget(opNode, target := ['OnePointCompletion, $Integer])
      target
    member(op, '(plusInfinity minusInfinity)) =>
      putTarget(opNode, target := ['OrderedCompletion, $Integer])
      target
    target

  a1 := CAR args
  ATOM a1 => target
  a1f := QCAR a1

  nargs = 1 =>
    op = 'kernel =>
      putTarget(opNode, target := ['Kernel, ['Expression, $Integer]])
      target
    op = 'list =>
      putTarget(opNode, target := ['List, a1])
      target
    target

  a2 := CADR args

  nargs >= 2 and op = "draw" and a1 is ['FunctionCalled,sym] and a2 is ['Segment,.] =>

    -- this clears up some confusion over 2D and 3D graphics

    symNode := mkAtreeNode sym
    transferPropsToNode(sym,symNode)

    nargs >= 3 and CADDR args is ['Segment,.] =>
      selectLocalMms(symNode,sym,[$DoubleFloat, $DoubleFloat],NIL)
      putTarget(opNode, target := '(ThreeDimensionalViewport))
      target

    (mms := selectLocalMms(symNode,sym,[$DoubleFloat],NIL)) =>
      [.,targ,:.] := CAAR mms
      targ = $DoubleFloat =>
          putTarget(opNode, target := '(TwoDimensionalViewport))
          target
      targ = ['Point, $DoubleFloat] =>
          putTarget(opNode, target := '(ThreeDimensionalViewport))
          target
      target

    target

  nargs >= 2 and op = "makeObject" and a1 is ['FunctionCalled,sym] and a2 is ['Segment,.] =>
    -- we won't actually bother to put a target on makeObject
    -- this is just to figure out what the first arg is
    symNode := mkAtreeNode sym
    transferPropsToNode(sym,symNode)

    nargs >= 3 and CADDR args is ['Segment,.] =>
      selectLocalMms(symNode,sym,[$DoubleFloat, $DoubleFloat],NIL)
      target

    selectLocalMms(symNode,sym,[$DoubleFloat],NIL)
    target

  nargs = 2 =>
    op = "elt" =>
        a1 = '(BasicOperator) and a2 is ['List, ['OrderedVariableList, .]] =>
           ['Expression, $Integer]
        target

    op = "eval" =>
        a1 is ['Expression,b1] and a2 is ['Equation, ['Polynomial,b2]] =>
            target :=
              canCoerce(b2, a1) => a1
              t := resolveTT(b1, b2)
              (not t) or (t = $Any) => nil
              resolveTT(a1, t)
            if target then putTarget(opNode, target)
            target
        a1 is ['Equation, .] and a2 is ['Equation, .] =>
            target := resolveTT(a1, a2)
            if target and not (target = $Any) then putTarget(opNode,target)
            else target := nil
            target
        a1 is ['Equation, .] and a2 is ['List, a2e] and a2e is ['Equation, .] =>
            target := resolveTT(a1, a2e)
            if target and not (target = $Any) then putTarget(opNode,target)
            else target := nil
            target
        a2 is ['Equation, a2e] or a2 is ['List, ['Equation, a2e]] =>
            target := resolveTT(a1, a2e)
            if target and not (target = $Any) then putTarget(opNode,target)
            else target := nil
            target

    op = "**" or op = "^" =>
      a2 = $Integer =>
        if (target := resolveTCat(a1,'(Field))) then
          putTarget(opNode,target)
        target
      a1 = '(AlgebraicNumber) and (a2 = $Float or a2 = $DoubleFloat) =>
          target := ['Expression, a2]
          putTarget(opNode,target)
          target
      a1 = '(AlgebraicNumber) and a2 is ['Complex, a3] and (a3 = $Float or a3 = $DoubleFloat) =>
          target := ['Expression, a3]
          putTarget(opNode,target)
          target
      ((a2 = $RationalNumber) and
        (typeIsASmallInteger(a1) or isEqualOrSubDomain(a1,$Integer))) =>
            putTarget(opNode, target := '(AlgebraicNumber))
            target
      ((a2 = $RationalNumber) and (isAVariableType(a1)
          or a1 is ['Polynomial,.] or a1 is ['RationalFunction,.])) =>
            putTarget(opNode, target := defaultTargetFE a1)
            target
      isAVariableType(a1) and (a2 = $PositiveInteger or a2 = $NonNegativeInteger) =>
          putTarget(opNode, target := '(Polynomial (Integer)))
          target
      isAVariableType(a2) =>
        putTarget(opNode, target := defaultTargetFE a1)
        target
      a2 is ['Polynomial, D] =>
        (a1 = a2) or isAVariableType(a1)
         or ((a1 is ['RationalFunction, D1]) and (D1 = D)) or (a1 = D)
          or ((a1 is [=$QuotientField, D1]) and (D1 = a1)) =>
            putTarget(opNode, target := defaultTargetFE a2)
            target
        target
      a2 is ['RationalFunction, D] =>
        (a1 = a2) or isAVariableType(a1)
         or ((a1 is ['RationalFunction, D1]) and (D1 = D)) or (a1 = D)
          or ((a1 is [=$QuotientField, D1]) and (D1 = a1)) =>
            putTarget(opNode, target := defaultTargetFE a2)
            target
        target
      target

    op = "/" =>
      isEqualOrSubDomain(a1, $Integer) and isEqualOrSubDomain(a2, $Integer) =>
        putTarget(opNode, target := $RationalNumber)
        target
      a1 = a2 =>
        if (target := resolveTCat(CAR args,'(Field))) then
          putTarget(opNode,target)
        target
      a1 is ['Variable,.] and a2 is ['Variable,.] =>
        putTarget(opNode,target := mkRationalFunction  '(Integer))
        target
      isEqualOrSubDomain(a1,$Integer) and a2 is ['Variable,.] =>
        putTarget(opNode,target := mkRationalFunction '(Integer))
        target
      a1 is ['Variable,.] and
        a2 is ['Polynomial,D] =>
          putTarget(opNode,target := mkRationalFunction D)
          target
        target
      a2 is ['Variable,.] and
        a1 is ['Polynomial,D] =>
          putTarget(opNode,target := mkRationalFunction D)
          target
        target
      a2 is ['Polynomial,D] and (a1 = D) =>
        putTarget(opNode,target := mkRationalFunction D)
        target
      target

  a3 := CADDR args
  nargs = 3 =>
    op = "eval" =>
        a3 is ['List, a3e] =>
            target := resolveTT(a1, a3e)
            if not (target = $Any) then putTarget(opNode,target)
            else target := nil
            target

        target := resolveTT(a1, a3)
        if not (target = $Any) then putTarget(opNode,target)
        else target := nil
        target
  target

mkRationalFunction D ==  ['Fraction, ['Polynomial, D]]

defaultTargetFE(a,:options) ==
  a is ['Variable,.] or a = $RationalNumber or MEMQ(QCAR a,
    [QCAR $Symbol, 'RationalRadicals,
     'Pi]) or typeIsASmallInteger(a) or isEqualOrSubDomain(a, $Integer) or
       a = '(AlgebraicNumber) =>
          IFCAR options => [$FunctionalExpression, ['Complex, $Integer]]
          [$FunctionalExpression, $Integer]
  a is ['Complex,uD] => defaultTargetFE(uD, true)
  a is [D,uD] and MEMQ(D, '(Polynomial RationalFunction Fraction)) =>
     defaultTargetFE(uD, IFCAR options)
  a is [=$FunctionalExpression,.] => a
  IFCAR options => [$FunctionalExpression, ['Complex, a]]
  [$FunctionalExpression, a]

altTypeOf(type,val,$declaredMode) ==
  (EQCAR(type,'Symbol) or EQCAR(type,'Variable)) and
    (a := getMinimalVarMode(objValUnwrap getValue(val),$declaredMode)) =>
      a
  type is ['OrderedVariableList,vl] and
    INTEGERP(val1 := objValUnwrap getValue(val)) and
      (a := getMinimalVarMode(vl.(val1 - 1),$declaredMode)) =>
        a
  type = $PositiveInteger    => $Integer
  type = $NonNegativeInteger => $Integer
  type = '(List (PositiveInteger)) => '(List (Integer))
  NIL

getOpArgTypes(opname, args) ==
  l := getOpArgTypes1(opname, args)
  [f(a,opname) for a in l] where
    f(x,op) ==
      x is ['FunctionCalled,g] and op ^= 'name =>
        m := get(g,'mode,$e) =>
          m is ['Mapping,:.] => m
          x
        x
      x

getOpArgTypes1(opname, args) ==
  null args => NIL
  -- special cases first
  opname = 'coef and args is [b,n] =>
    [CAR getModeSet b, CAR getModeSetUseSubdomain n]
  opname = 'monom and args is [d,c] =>
    [CAR getModeSetUseSubdomain d,CAR getModeSet c]
  opname = 'monom and args is [v,d,c] =>
    [CAR getModeSet v,CAR getModeSetUseSubdomain d,CAR getModeSet c]
  (opname = 'cons) and (2 = #args) and (CADR(args) = 'nil) =>
    ms := [CAR getModeSet x for x in args]
    if CADR(ms) = '(List (None)) then
      ms := [first ms,['List,first ms]]
    ms
  nargs := #args
  v := argCouldBelongToSubdomain(opname,nargs)
  mss := NIL
  for i in 0..(nargs-1) for x in args repeat
    ms :=
      v.i = 0 => CAR getModeSet x
      CAR getModeSetUseSubdomain x
    mss := [ms,:mss]
  nreverse mss

argCouldBelongToSubdomain(op, nargs) ==
  -- this returns a vector containing 0 or ^0 for each argument.
  -- if ^0, this indicates that there exists a modemap for the
  -- op that needs a subdomain in that position
  nargs = 0 => NIL
  v := GETZEROVEC nargs
  isMap(op) => v
  mms := getModemapsFromDatabase(op,nargs)
  null mms => v
  nargs:=nargs-1
  -- each signature has form
  -- [domain of implementation, target, arg1, arg2, ...]
  for [sig,cond,:.] in mms repeat
    for t in CDDR sig for i in 0..(nargs) repeat
      CONTAINEDisDomain(t,cond) =>
          v.i := 1 + v.i
  v

CONTAINEDisDomain(symbol,cond) ==
-- looks for [isSubDomain,symbol,[domain]] in cond: returning T or NIL
-- with domain being one of PositiveInteger and NonNegativeInteger
   ATOM cond => false
   MEMQ(QCAR cond,'(AND OR and or)) =>
       or/[CONTAINEDisDomain(symbol, u) for u in QCDR cond]
   EQ(QCAR cond,'isDomain) =>
       EQ(symbol,CADR cond) and PAIRP(dom:=CADDR cond) and
         MEMQ(dom,'(PositiveInteger NonNegativeInteger))
   false

selectDollarMms(dc,name,types1,types2) ==
  -- finds functions for name in domain dc
  isPartialMode dc => throwKeyedMsg("S2IF0001",NIL)
  mmS := findFunctionInDomain(name,dc,NIL,types1,types2,'T,'T) =>
    orderMms(name, mmS,types1,types2,NIL)
  if $reportBottomUpFlag then sayMSG
    ["%b",'"          function not found in ",prefix2String dc,"%d","%l"]
  NIL

selectLocalMms(op,name,types,tar) ==
  -- partial rewrite, looks now for exact local modemap
  mmS:= getLocalMms(name,types,tar) => mmS
  obj := getValue op
  obj and (objVal obj is ['MAP,:mapDef]) and
    analyzeMap(op,types,mapDef,tar) and getLocalMms(name,types,tar)

-- next defn may be better, test when more time. RSS 3/11/94
-- selectLocalMms(op,name,types,tar) ==
--  mmS := getLocalMms(name,types,tar)
--  -- if no target, just return what we got
--  mmS and null tar => mmS
--  matchingMms := nil
--  for mm in mmS repeat
--    [., targ, :.] := mm
--    if tar = targ then matchingMms := cons(mm, matchingMms)
--  -- if we got some exact matchs on the target, return them
--  matchingMms => nreverse matchingMms
--
--  obj := getValue op
--  obj and (objVal obj is ['MAP,:mapDef]) and
--    analyzeMap(op,types,mapDef,tar) and getLocalMms(name,types,tar)

getLocalMms(name,types,tar) ==
  -- looks for exact or subsumed local modemap in $e
  mmS := NIL
  for  (mm:=[dcSig,:.]) in get(name,'localModemap,$e) repeat
    -- check format and destructure
    dcSig isnt [dc,result,:args] => NIL
    -- make number of args is correct
    #types ^= #args => NIL
    -- check for equal or subsumed arguments
    subsume := (not $useIntegerSubdomain) or (tar = result) or
      get(name,'recursive,$e)
    acceptableArgs :=
      and/[f(b,a,subsume) for a in args for b in types] where
        f(x,y,subsume) ==
          if subsume
            then isEqualOrSubDomain(x,y)
            else x = y
    not acceptableArgs =>
      -- interpreted maps are ok
      dc = 'interpOnly and not($Coerce)=> mmS := [mm,:mmS]
      NIL
    mmS := [mm,:mmS]
  nreverse mmS

mmCost(name, sig,cond,tar,args1,args2) ==
  cost := mmCost0(name, sig,cond,tar,args1,args2)
  res := CADR sig
  res = $PositiveInteger => cost - 2
  res = $NonNegativeInteger => cost - 1
  res = $DoubleFloat => cost + 1
  cost

mmCost0(name, sig,cond,tar,args1,args2) ==
  sigArgs := CDDR sig
  n:=
    null cond => 1
    not (or/cond) => 1
    0

  -- try to favor homogeneous multiplication

--if name = "*" and 2 = #sigArgs and first sigArgs ^= first rest sigArgs then n := n + 1

  -- because of obscure problem in evalMm, sometimes we will have extra
  -- modemaps with the wrong number of arguments if we want to the one
  -- with no arguments and the name is overloaded. Thus check for this.

  if args1 then
    for x1 in args1 for x2 in args2 for x3 in sigArgs repeat
      n := n +
        isEqualOrSubDomain(x1,x3) => 0
        topcon := first deconstructT x1
        topcon2 := first deconstructT x3
        topcon = topcon2 => 3
        CAR topcon2 = 'Mapping => 2
        4
  else if sigArgs then n := n + 100000000000

  res := CADR sig
  res=tar => 10000*n
  10000*n + 1000*domainDepth(res) + hitListOfTarget(res)

orderMms(name, mmS,args1,args2,tar) ==
  -- it counts the number of necessary coercions of the argument types
  -- if this isn't enough, it compares the target types
  mmS and null rest mmS => mmS
  mS:= NIL
  N:= NIL
  for mm in MSORT mmS repeat
    [sig,.,cond]:= mm
    b:= 'T
    p:= CONS(m := mmCost(name, sig,cond,tar,args1,args2),mm)
    mS:=
      null mS => list p
      m < CAAR mS => CONS(p,mS)
      S:= mS
      until b repeat
        b:= null CDR S or m < CAADR S =>
          RPLACD(S,CONS(p,CDR S))
        S:= CDR S
      mS
  mmS and [CDR p for p in mS]

domainDepth(d) ==
  -- computes the depth of lisp structure d
  atom d => 0
  MAX(domainDepth(CAR d)+1,domainDepth(CDR d))

hitListOfTarget(t) ==
  -- assigns a number between 1 and 998 to a type t

  -- want to make it hard to go to Polynomial Pi

  t = '(Polynomial (Pi)) => 90000

  EQ(CAR t, 'Polynomial) => 300
  EQ(CAR t, 'List) => 400
  EQ(CAR t,'Matrix) => 910
  EQ(CAR t,'UniversalSegment) => 501
  EQ(CAR t,'RationalFunction) => 900
  EQ(CAR t,'Union) => 999
  EQ(CAR t,'Expression) => 1600
  500

getFunctionFromDomain(op,dc,args) ==
  -- finds the function op with argument types args in dc
  -- complains, if no function or ambiguous
  $reportBottomUpFlag:local:= NIL
  member(CAR dc,$nonLisplibDomains) =>
    throwKeyedMsg("S2IF0002",[CAR dc])
  not constructor? CAR dc =>
    throwKeyedMsg("S2IF0003",[CAR dc])
  p:= findFunctionInDomain(op,dc,NIL,args,args,NIL,NIL) =>
--+
    --sig := [NIL,:args]
    domain := evalDomain dc
    for mm in nreverse p until b repeat
      [[.,:osig],nsig,:.] := mm
      b := compiledLookup(op,nsig,domain)
    b or  throwKeyedMsg("S2IS0023",[op,dc])
  throwKeyedMsg("S2IF0004",[op,dc])

isOpInDomain(opName,dom,nargs) ==
  -- returns true only if there is an op in the given domain with
  -- the given number of arguments
  mmList := ASSQ(opName,getOperationAlistFromLisplib CAR dom)
  mmList := subCopy(mmList,constructSubst dom)
  null mmList => NIL
  gotOne := NIL
  nargs := nargs + 1
  for mm in CDR mmList while not gotOne repeat
    nargs = #CAR mm => gotOne := [mm, :gotOne]
  gotOne

findCommonSigInDomain(opName,dom,nargs) ==
  -- this looks at all signatures in dom with given opName and nargs
  -- number of arguments. If no matches, returns NIL. Otherwise returns
  -- a "signature" where a type position is non-NIL only if all
  -- signatures shares that type .
  CAR(dom) in '(Union Record Mapping) => NIL
  mmList := ASSQ(opName,getOperationAlistFromLisplib CAR dom)
  mmList := subCopy(mmList,constructSubst dom)
  null mmList => NIL
  gotOne := NIL
  nargs := nargs + 1
  vec := NIL
  for mm in CDR mmList repeat
    nargs = #CAR mm =>
      null vec  => vec := LIST2VEC CAR mm
      for i in 0.. for x in CAR mm repeat
        if vec.i and vec.i ^= x then vec.i := NIL
  VEC2LIST vec

findUniqueOpInDomain(op,opName,dom) ==
  -- return function named op in domain dom if unique, choose one if not
  mmList := ASSQ(opName,getOperationAlistFromLisplib CAR dom)
  mmList := subCopy(mmList,constructSubst dom)
  null mmList =>
    throwKeyedMsg("S2IS0021",[opName,dom])
  if #CDR mmList > 1 then
    mm := selectMostGeneralMm CDR mmList
    sayKeyedMsg("S2IS0022",[opName,dom,['Mapping,:CAR mm]])
  else mm := CADR mmList
  [sig,slot,:.] := mm
  fun :=
--+
      $genValue =>
         compiledLookupCheck(opName,sig,evalDomain dom)
      NRTcompileEvalForm(opName, sig, evalDomain dom)
  NULL(fun) or NULL(PAIRP(fun)) => NIL
  CAR fun = function(Undef) => throwKeyedMsg("S2IS0023",[opName,dom])
  binVal :=
    $genValue => wrap fun
    fun
  putValue(op,objNew(binVal,m:=['Mapping,:sig]))
  putModeSet(op,[m])

selectMostGeneralMm mmList ==
  -- selects the modemap in mmList with arguments all the other
  -- argument types can be coerced to
  -- also selects function with #args closest to 2
  min := 100
  mml := mmList
  while mml repeat
    [mm,:mml] := mml
    sz := #CAR mm
    if (met := ABS(sz - 3)) < min then
      min := met
      fsz := sz
  mmList := [mm for mm in mmList | (#CAR mm) = fsz]
  mml := CDR mmList
  genMm := CAR mmList
  while mml repeat
    [mm,:mml] := mml
    and/[canCoerceFrom(genMmArg,mmArg) for mmArg in CDAR mm
      for genMmArg in CDAR genMm] => genMm := mm
  genMm

findFunctionInDomain(op,dc,tar,args1,args2,$Coerce,$SubDom) ==
  -- looks for a modemap for op with signature  args1 -> tar
  --   in the domain of computation dc
  -- tar may be NIL (= unknown)
  null isLegitimateMode(tar, nil, nil) => nil
  dcName:= CAR dc
  member(dcName,'(Union Record Mapping Enumeration)) =>
    -- First cut code that ignores args2, $Coerce and $SubDom
    -- When domains no longer have to have Set, the hard coded 6 and 7
    -- should go.
    op = '_= =>
        #args1 ^= 2 or args1.0 ^= dc or args1.1 ^= dc => NIL
        tar and tar ^= '(Boolean) => NIL
        [[[dc, '(Boolean), dc, dc], ['(Boolean),'$,'$], [NIL, NIL]]]
    op = 'coerce =>
        #args1 ^= 1 
        dcName='Enumeration and (args1.0=$Symbol or tar=dc)=>
           [[[dc, dc, $Symbol], ['$,$Symbol], [NIL, NIL]]]
        args1.0 ^= dc => NIL
        tar and tar ^= $Expression => NIL
        [[[dc, $Expression, dc], [$Expression,'$], [NIL, NIL]]]
    member(dcName,'(Record Union)) =>
      findFunctionInCategory(op,dc,tar,args1,args2,$Coerce,$SubDom)
    NIL
  fun:= NIL
  ( p := ASSQ(op,getOperationAlistFromLisplib dcName) ) and
    SL := constructSubst dc
    -- if the arglist is homogeneous, first look for homogeneous
    -- functions. If we don't find any, look at remaining ones
    if isHomogeneousList args1 then
      q := NIL
      r := NIL
      for mm in CDR p repeat
        -- CDAR of mm is the signature argument list
        if isHomogeneousList CDAR mm then q := [mm,:q]
        else r := [mm,:r]
      q := allOrMatchingMms(q,args1,tar,dc)
      for mm in q repeat
        fun:= nconc(fun,findFunctionInDomain1(mm,op,tar,args1,args2,SL))
      r := reverse r
    else r := CDR p
    r := allOrMatchingMms(r,args1,tar,dc)
    if not fun then    -- consider remaining modemaps
      for mm in r repeat
        fun:= nconc(fun,findFunctionInDomain1(mm,op,tar,args1,args2,SL))
  if not fun and $reportBottomUpFlag then
    sayMSG concat
      ['"   -> no appropriate",:bright op,'"found in",
        :bright prefix2String dc]
  fun

allOrMatchingMms(mms,args1,tar,dc) ==
  -- if there are exact matches on the arg types, return them
  -- otherwise return the original list
  null mms or null rest mms => mms
  x := NIL
  for mm in mms repeat
    [sig,:.] := mm
    [res,:args] := MSUBSTQ(dc,"$",sig)
    args ^= args1 => nil
    x := CONS(mm,x)
  if x then x
  else mms

isHomogeneousList y ==
  y is [x] => true
  y and rest y =>
    z := CAR y
    "and"/[x = z for x in CDR y]
  NIL

findFunctionInDomain1(omm,op,tar,args1,args2,SL) ==
  dc:= CDR (dollarPair := ASSQ('$,SL))
  -- need to drop '$ from SL
  mm:= subCopy(omm, SL)
  -- tests whether modemap mm is appropriate for the function
  -- defined by op, target type tar and argument types args
  $RTC:local:= NIL
  -- $RTC is a list of run-time checks to be performed

  [sig,slot,cond,y] := mm
  [osig,:.]  := omm
  osig := subCopy(osig, SUBSTQ(CONS('$,'$), dollarPair, SL))
  if CONTAINED('_#, sig) or CONTAINED('construct,sig) then
    sig := [replaceSharpCalls t for t in sig]
  matchMmCond cond and matchMmSig(mm,tar,args1,args2) and
    EQ(y,'Subsumed) and
      -- hmmmm: do Union check in following because (as in DP)
      -- Unions are subsumed by total modemaps which are in the
      -- mm list in findFunctionInDomain.
      y := 'ELT      -- if subsumed fails try it again
      not $SubDom and CAR sig isnt ['Union,:.] and slot is [tar,:args] and
        (f := findFunctionInDomain(op,dc,tar,args,args,NIL,NIL)) => f
    EQ(y,'ELT) => [[CONS(dc,sig),osig,nreverse $RTC]]
    EQ(y,'CONST) => [[CONS(dc,sig),osig,nreverse $RTC]]
    EQ(y,'ASCONST) => [[CONS(dc,sig),osig,nreverse $RTC]]
    y is ['XLAM,:.] => [[CONS(dc,sig),y,nreverse $RTC]]
    sayKeyedMsg("S2IF0006",[y])
    NIL

findFunctionInCategory(op,dc,tar,args1,args2,$Coerce,$SubDom) ==
  -- looks for a modemap for op with signature  args1 -> tar
  --   in the domain of computation dc
  -- tar may be NIL (= unknown)
  dcName:= CAR dc
  not MEMQ(dcName,'(Record Union Enumeration)) => NIL
  fun:= NIL
 --  cat := constructorCategory dc
  makeFunc := GETL(dcName,"makeFunctionList") or
      systemErrorHere '"findFunctionInCategory"
  [funlist,.] := FUNCALL(makeFunc,"$",dc,$CategoryFrame)
  -- get list of implementations and remove sharps
  maxargs := -1
  impls := nil
  for [a,b,d] in funlist repeat
    not EQ(a,op) => nil
    d is ['XLAM,xargs,:.] =>
      if PAIRP(xargs) then maxargs := MAX(maxargs,#xargs)
      else maxargs := MAX(maxargs,1)
      impls := cons([b,nil,true,d],impls)
    impls := cons([b,d,true,d],impls)
  impls := NREVERSE impls
  if maxargs ^= -1 then
    SL:= NIL
    for i in 1..maxargs repeat
      impls := SUBSTQ(GENSYM(),INTERNL('"#",STRINGIMAGE i),impls)
  impls and
    SL:= constructSubst dc
    for mm in impls repeat
      fun:= nconc(fun,findFunctionInDomain1(mm,op,tar,args1,args2,SL))
  if not fun and $reportBottomUpFlag then
    sayMSG concat
      ['"   -> no appropriate",:bright op,'"found in",
        :bright prefix2String dc]
  fun

matchMmCond(cond) ==
  -- tests the condition, which comes with a modemap
  -- cond is 'T or a list, but I hate to test for 'T (ALBI)
  $domPvar: local := nil
  atom cond or
    cond is ['AND,:conds] or cond is ['and,:conds] =>
      and/[matchMmCond c for c in conds]
    cond is ['OR,:conds] or cond is ['or,:conds] =>
      or/[matchMmCond c for c in conds]
    cond is ['has,dom,x] =>
      hasCaty(dom,x,NIL) ^= 'failed
    cond is ['not,cond1] => not matchMmCond cond1
    keyedSystemError("S2GE0016",
      ['"matchMmCond",'"unknown form of condition"])

matchMmSig(mm,tar,args1,args2) ==
  -- matches the modemap signature against  args1 -> tar
  -- if necessary, runtime checks are created for subdomains
  -- then the modemap condition is evaluated
  [sig,:.]:= mm
  if CONTAINED('_#, sig) then
    sig := [replaceSharpCalls COPY t for t in sig]
  null args1 => matchMmSigTar(tar,CAR sig)
  a:= CDR sig
  arg:= NIL
  for i in 1.. while args1 and args2 and a until not b repeat
    x1:= CAR args1
    args1:= CDR args1
    x2:= CAR args2
    args2:= CDR args2
    x:= CAR a
    a:= CDR a
    rtc:= NIL
    if x is ['SubDomain,y,:.] then x:= y
    b := isEqualOrSubDomain(x1,x) or
      (STRINGP(x) and (x1 is ['Variable,v]) and (x = PNAME v)) or
        $SubDom and isSubDomain(x,x1) => rtc:= 'T
        $Coerce => x2=x or canCoerceFrom(x1,x)
        x1 is ['Variable,:.] and x = '(Symbol)
    $RTC:= CONS(rtc,$RTC)
  null args1 and null a and b and matchMmSigTar(tar,CAR sig)

matchMmSigTar(t1,t2) ==
  -- t1 is a target type specified by :: or by a declared variable
  -- t2 is the target of a modemap signature
  null t1 or
    isEqualOrSubDomain(t2,t1) => true
    if t2 is ['Union,a,b] then
      if a='"failed" then return matchMmSigTar(t1, b)
      if b='"failed" then return matchMmSigTar(t1, a)
    $Coerce and
      isPartialMode t1 => resolveTM(t2,t1)
-- I think this should be true  -SCM
--    true
      canCoerceFrom(t2,t1)

constructSubst(d) ==
  -- constructs a substitution which substitutes d for $
  -- and the arguments of d for #1, #2 ..
  SL:= list CONS('$,d)
  for x in CDR d for i in 1.. repeat
    SL:= CONS(CONS(INTERNL('"#",STRINGIMAGE i),x),SL)
  SL

filterModemapsFromPackages(mms, names, op) ==
  -- mms is a list of modemaps
  -- names is a list of domain constructors
  -- this returns a 2-list containing those modemaps that have one
  -- of the names in the package source of the modemap and all the
  -- rest of the modemaps in the second element.
  good := NIL
  bad  := NIL
  -- hack to speed up factorization choices for mpolys and to overcome
  -- some poor naming of packages
  mpolys := '("Polynomial" "MultivariatePolynomial"
   "DistributedMultivariatePolynomial"
      "HomogeneousDistributedMultivariatePolynomial")
  mpacks := '("MFactorize" "MRationalFactorize")
  for mm in mms repeat
    isFreeFunctionFromMm(mm) => bad := cons(mm, bad)
    type := getDomainFromMm mm
    null type => bad := cons(mm,bad)
    if PAIRP type then type := first type
    GETDATABASE(type,'CONSTRUCTORKIND) = 'category => bad := cons(mm,bad)
    name := object2String type
    found := nil
    for n in names while not found repeat
      STRPOS(n,name,0,NIL) => found := true
      -- hack, hack
      (op = 'factor) and member(n,mpolys) and member(name,mpacks) =>
        found := true
    if found
      then good := cons(mm, good)
      else bad := cons(mm,bad)
  [good,bad]


isTowerWithSubdomain(towerType,elem) ==
  not PAIRP towerType => NIL
  dt := deconstructT towerType
  2 ^= #dt => NIL
  s := underDomainOf(towerType)
  isEqualOrSubDomain(s,elem) and constructM(first dt,[elem])

selectMmsGen(op,tar,args1,args2) ==
  -- general modemap evaluation of op with argument types args1
  -- evaluates the condition and looks for the slot number
  -- returns all functions which are applicable
  -- args2 is a list of polynomial types for symbols
  $Subst: local := NIL
  $SymbolType: local := NIL

  null (S := getModemapsFromDatabase(op,QLENGTH args1)) => NIL

  if (op = 'map) and (2 = #args1) and
    (CAR(args1) is ['Mapping,., elem]) and
      (a := isTowerWithSubdomain(CADR args1,elem))
        then args1 := [CAR args1,a]

  -- we first split the modemaps into two groups:
  --   haves:    these are from packages that have one of the top level
  --             constructor names in the package name
  --   havenots: everything else

  -- get top level constructor names for constructors with parameters
  conNames := nil
  if op = 'reshape then args := APPEND(rest args1, rest args2)
  else args := APPEND(args1,args2)
  if tar then args := [tar,:args]
  -- for common aggregates, use under domain also
  for a in REMDUP args repeat
    a =>
      atom a => nil
      fa := QCAR a
      fa in '(Record Union) => NIL
      conNames := insert(STRINGIMAGE fa, conNames)

  if conNames
    then [haves,havenots] := filterModemapsFromPackages(S,conNames,op)
    else
      haves := NIL
      havenots := S

  mmS := NIL

  if $reportBottomUpFlag then
    sayMSG ['%l,:bright '"Modemaps from Associated Packages"]

  if haves then
    [havesExact,havesInexact] := exact?(haves,tar,args1)
    if $reportBottomUpFlag then
      for mm in APPEND(havesExact,havesInexact) for i in 1.. repeat
        sayModemapWithNumber(mm,i)
    if havesExact then
      mmS := matchMms(havesExact,op,tar,args1,args2)
      if mmS then
        if $reportBottomUpFlag then
          sayMSG '"   found an exact match!"
        return mmS
    mmS := matchMms(havesInexact,op,tar,args1,args2)
  else if $reportBottomUpFlag then sayMSG '"   no modemaps"
  mmS => mmS

  if $reportBottomUpFlag then
    sayMSG ['%l,:bright '"Remaining General Modemaps"]
  --  for mm in havenots for i in 1.. repeat sayModemapWithNumber(mm,i)

  if havenots then
    [havesNExact,havesNInexact] := exact?(havenots,tar,args1)
    if $reportBottomUpFlag then
      for mm in APPEND(havesNExact,havesNInexact) for i in 1.. repeat
        sayModemapWithNumber(mm,i)
    if havesNExact then
      mmS := matchMms(havesNExact,op,tar,args1,args2)
      if mmS then
        if $reportBottomUpFlag then
          sayMSG '"   found an exact match!"
        return mmS
    mmS := matchMms(havesNInexact,op,tar,args1,args2)
  else if $reportBottomUpFlag then sayMSG '"   no modemaps"
  mmS
 where
  exact?(mmS,tar,args) ==
    ex := inex := NIL
    for (mm := [sig,[mmC,:.],:.]) in mmS repeat
      [c,t,:a] := sig
      ok := true
      for pat in a for arg in args while ok repeat
        not CONTAINED(['isDomain,pat,arg],mmC) => ok := NIL
      ok => ex := CONS(mm,ex)
      inex := CONS(mm,inex)
    [ex,inex]
  matchMms(mmaps,op,tar,args1,args2) ==
    mmS := NIL
    for [sig,mmC] in mmaps repeat
      -- sig is [dc,result,:args]
      $Subst :=
        tar and not isPartialMode tar =>
          -- throw in the target if it is not the same as one
          -- of the arguments
          res := CADR sig
          member(res,CDDR sig) => NIL
          [[res,:tar]]
        NIL
      [c,t,:a] := sig
      if a then matchTypes(a,args1,args2)
      not EQ($Subst,'failed) =>
        mmS := nconc(evalMm(op,tar,sig,mmC),mmS)
    mmS

matchTypes(pm,args1,args2) ==
  -- pm is a list of pattern variables, args1 a list of argument types,
  --   args2 a list of polynomial types for symbols
  -- the result is a match from pm to args, if one exists
  for v in pm for t1 in args1 for t2 in args2 until $Subst='failed repeat
    p:= ASSQ(v,$Subst) =>
      t:= CDR p
      t=t1 => $Coerce and EQCAR(t1,'Symbol) and
        (q := ASSQ(v,$SymbolType)) and t2 and
          (t3 := resolveTT(CDR q, t2)) and
            RPLACD(q, t3)
      $Coerce =>
        if EQCAR(t,'Symbol) and (q := ASSQ(v,$SymbolType)) then
          t := CDR q
        if EQCAR(t1,'Symbol) and t2 then t1:= t2
        t0 := resolveTT(t,t1) => RPLACD(p,t0)
        $Subst:= 'failed
      $Subst:= 'failed
    $Subst:= CONS(CONS(v,t1),$Subst)
    if EQCAR(t1,'Symbol) and t2 then $SymbolType:= CONS(CONS(v,t2),$SymbolType)

evalMm(op,tar,sig,mmC) ==
  -- evaluates a modemap with signature sig and condition mmC
  -- the result is a list of lists [sig,slot,cond] or NIL
  --if $Coerce is NIL, tar has to be the same as the computed target type
--if CONTAINED('LinearlyExplicitRingOver,mmC) then hohoho()
  mS:= NIL
  for st in evalMmStack mmC repeat
    SL:= evalMmCond(op,sig,st)
    not EQ(SL,'failed) =>
      SL := fixUpTypeArgs SL
      sig:= [subCopy(deepSubCopy(x,SL),$Subst) for x in sig]
      not containsVars sig =>
        isFreeFunctionFromMmCond mmC and (m := evalMmFreeFunction(op,tar,sig,mmC)) =>
           mS:= nconc(m,mS)
        "or"/[^isValidType(arg) for arg in sig] => nil
        [dc,t,:args]:= sig
        $Coerce or null tar or tar=t =>
          mS:= nconc(findFunctionInDomain(op,dc,t,args,args,NIL,'T),mS)
  mS

evalMmFreeFunction(op,tar,sig,mmC) ==
  [dc,t,:args]:= sig
  $Coerce or null tar or tar=t =>
     nilArgs := nil
     for a in args repeat nilArgs := [NIL,:nilArgs]
     [[[["__FreeFunction__",:dc],t,:args], [t, :args], nilArgs]]
  nil

evalMmStack(mmC) ==
  -- translates the modemap condition mmC into a list of stacks
  mmC is ['AND,:a] =>
    ["NCONC"/[evalMmStackInner cond for cond in a]]
  mmC is ['OR,:args] => [:evalMmStack a for a in args]
  mmC is ['partial,:mmD] => evalMmStack mmD
  mmC is ['ofCategory,pvar,cat] and cat is ['Join,:args] =>
    evalMmStack CONS('AND,[['ofCategory,pvar,c] for c in args])
  mmC is ['ofType,:.] => [NIL]
  mmC is ['has,pat,x] =>
    MEMQ(x,'(ATTRIBUTE SIGNATURE)) =>
      [[['ofCategory,pat,['CATEGORY,'unknown,x]]]]
    [['ofCategory,pat,x]]
  [[mmC]]

evalMmStackInner(mmC) ==
  mmC is ['OR,:args] =>
    keyedSystemError("S2GE0016",
      ['"evalMmStackInner",'"OR condition nested inside an AND"])
  mmC is ['partial,:mmD] => evalMmStackInner mmD
  mmC is ['ofCategory,pvar,cat] and cat is ['Join,:args] =>
    [['ofCategory, pvar, c] for c in args]
  mmC is ['ofType,:.] => NIL
  mmC is ['isAsConstant] => NIL
  mmC is ['has,pat,x] =>
    MEMQ(x,'(ATTRIBUTE SIGNATURE)) =>
      [['ofCategory,pat,['CATEGORY,'unknown,x]]]
    [['ofCategory,pat,x]]
  [mmC]

evalMmCond(op,sig,st) ==
  $insideEvalMmCondIfTrue : local := true
  evalMmCond0(op,sig,st)

evalMmCond0(op,sig,st) ==
  -- evaluates the nonempty list of modemap conditions st
  -- the result is either 'failed or a substitution list
  SL:= evalMmDom st
  SL='failed => 'failed
  for p in SL until p1 and not b repeat b:=
    p1:= ASSQ(CAR p,$Subst)
    p1 and
      t1:= CDR p1
      t:= CDR p
      t=t1 or
        containsVars t =>
          if $Coerce and EQCAR(t1,'Symbol) then t1:= getSymbolType CAR p
          resolveTM1(t1,t)
        $Coerce and
          -- if we are looking at the result of a function, the coerce
          -- goes the opposite direction
          (t1 = $AnonymousFunction and t is ['Mapping, :.]) => t
          CAR p = CADR sig and not member(CAR p, CDDR sig) =>
            canCoerceFrom(t,t1) => 'T
            NIL
          canCoerceFrom(t1,t) => 'T
          isSubDomain(t,t1) => RPLACD(p,t1)
          EQCAR(t1,'Symbol) and canCoerceFrom(getSymbolType CAR p,t)
  ( SL and p1 and not b and 'failed ) or evalMmCat(op,sig,st,SL)

fixUpTypeArgs SL ==
  for (p := [v, :t2]) in SL repeat
    t1 := LASSOC(v, $Subst)
    null t1 => RPLACD(p,replaceSharpCalls t2)
    RPLACD(p, coerceTypeArgs(t1, t2, SL))
  SL

replaceSharpCalls t ==
  noSharpCallsHere t => t
  doReplaceSharpCalls t

doReplaceSharpCalls t ==
  ATOM t => t
  t is ['_#, l] => #l
  t is ['construct,: l] => EVAL ['LIST,:l]
  [CAR t,:[ doReplaceSharpCalls u for u in CDR t]]

noSharpCallsHere t ==
  t isnt [con, :args] => true
  MEMQ(con,'(construct _#)) => NIL
  and/[noSharpCallsHere u for u in args]

coerceTypeArgs(t1, t2, SL) ==
  -- if the type t has type-valued arguments, coerce them to the new types,
  -- if needed.
  t1 isnt [con1, :args1] or t2 isnt [con2, :args2] => t2
  con1 ^= con2 => t2
  coSig := CDR GETDATABASE(CAR t1, 'COSIG)
  and/coSig => t2
  csub1 := constructSubst t1
  csub2 := constructSubst t2
  cs1 := CDR getConstructorSignature con1
  cs2 := CDR getConstructorSignature con2
  [con1, :
    [makeConstrArg(arg1, arg2, constrArg(c1,csub1,SL),
      constrArg(c2,csub2,SL), cs)
       for arg1 in args1 for arg2 in args2 for c1 in cs1 for c2 in cs2
         for cs in coSig]]

constrArg(v,sl,SL) ==
  x := LASSOC(v,sl) =>
    y := LASSOC(x,SL) => y
    y := LASSOC(x, $Subst) => y
    x
  y := LASSOC(x, $Subst) => y
  v

makeConstrArg(arg1, arg2, t1, t2, cs) ==
  if arg1 is ['_#, l] then arg1 := # l
  if arg2 is ['_#, l] then arg2 := # l
  cs => arg2
  t1 = t2 => arg2
  obj1 := objNewWrap(arg1, t1)
  obj2 := coerceInt(obj1, t2)
  null obj2 => throwKeyedMsgCannotCoerceWithValue(wrap arg1,t1,t2)
  objValUnwrap obj2

evalMmDom(st) ==
  -- evals all isDomain(v,d) of st
  SL:= NIL
  for mmC in st until SL='failed repeat
    mmC is ['isDomain,v,d] =>
      STRINGP d => SL:= 'failed
      p:= ASSQ(v,SL) and not (d=CDR p) => SL:= 'failed
      d1:= subCopy(d,SL)
      CONSP(d1) and MEMQ(v,d1) => SL:= 'failed
      SL:= augmentSub(v,d1,SL)
    mmC is ['isFreeFunction,v,fun] =>
      SL:= augmentSub(v,subCopy(fun,SL),SL)
  SL

orderMmCatStack st ==
  -- tries to reorder stack so that free pattern variables appear
  -- as parameters first
  null(st) or null rest(st) => st
  vars := DELETE_-DUPLICATES [CADR(s) for s in st | isPatternVar(CADR(s))]
  null vars => st
  havevars := nil
  haventvars := nil
  for s in st repeat
    cat := CADDR s
    mem := nil
    for v in vars while not mem repeat
      if MEMQ(v,cat) then
        mem := true
        havevars := cons(s,havevars)
    if not mem then haventvars := cons(s,haventvars)
  null havevars => st
  st := nreverse nconc(haventvars,havevars)
  SORT(st, function mmCatComp)

mmCatComp(c1, c2) ==
  b1 := ASSQ(CADR c1, $Subst)
  b2 := ASSQ(CADR c2, $Subst)
  b1 and null(b2) => true
  false

evalMmCat(op,sig,stack,SL) ==
  -- evaluates all ofCategory's of stack as soon as possible
  $hope:local:= NIL
  numConds:= #stack
  stack:= orderMmCatStack [mmC for mmC in stack | EQCAR(mmC,'ofCategory)]
  while stack until not makingProgress repeat
    st := stack
    stack := NIL
    makingProgress := NIL
    for mmC in st repeat
      S:= evalMmCat1(mmC,op, SL)
      S='failed and $hope =>
        stack:= CONS(mmC,stack)
      S = 'failed => return S
      not atom S =>
        makingProgress:= 'T
        SL:= mergeSubs(S,SL)
  if stack or S='failed then 'failed else SL

evalMmCat1(mmC is ['ofCategory,d,c],op, SL) ==
  -- evaluates mmC using information from the lisplib
  -- d may contain variables, and the substitution list $Subst is used
  -- the result is a substitution or failed
  $domPvar: local := NIL
  $hope:= NIL
  NSL:= hasCate(d,c,SL)
  NSL='failed and isPatternVar d and $Coerce and ( p:= ASSQ(d,$Subst) )
    and (EQCAR(CDR p,'Variable) or EQCAR(CDR p,'Symbol)) =>
      RPLACD(p,getSymbolType d)
      hasCate(d,c,SL)
  NSL='failed and isPatternVar d =>
    -- following is hack to take care of the case where we have a
    -- free substitution variable with a category condition on it.
    -- This would arise, for example, where a package has an argument
    -- that is not in a needed modemap.  After making the following
    -- dummy substitutions, the package can be instantiated and the
    -- modemap used.       RSS 12-22-85
    -- If c is not Set, Ring or Field then the more general mechanism
    dom := defaultTypeForCategory(c, SL)
    null dom =>
      op ^= 'coerce => 'failed -- evalMmCatLastChance(d,c,SL)
    null (p := ASSQ(d,$Subst)) =>
      dom =>
        NSL := [CONS(d,dom)]
      op ^= 'coerce => 'failed -- evalMmCatLastChance(d,c,SL)
    if containsVars dom then dom := resolveTM(CDR p, dom)
    $Coerce and canCoerce(CDR p, dom) =>
      NSL := [CONS(d,dom)]
    op ^= 'coerce => 'failed -- evalMmCatLastChance(d,c,SL)
  NSL

hasCate(dom,cat,SL) ==
  -- asks whether dom has cat under SL
  -- augments substitution SL or returns 'failed
  dom = $EmptyMode => NIL
  isPatternVar dom =>
    (p:= ASSQ(dom,SL)) and ((NSL := hasCate(CDR p,cat,SL)) ^= 'failed) =>
       NSL
    (p:= ASSQ(dom,$Subst)) or (p := ASSQ(dom, SL)) =>
--      S:= hasCate(CDR p,cat,augmentSub(CAR p,CDR p,copy SL))
      S:= hasCate1(CDR p,cat,SL, dom)
      not (S='failed) => S
      hasCateSpecial(dom,CDR p,cat,SL)
    if SL ^= 'failed then $hope:= 'T
    'failed
  SL1 := [[v,:d] for [v,:d] in SL | not containsVariables d]
  if SL1 then cat := subCopy(cat, SL1)
  hasCaty(dom,cat,SL)

hasCate1(dom, cat, SL, domPvar) ==
  $domPvar:local := domPvar
  hasCate(dom, cat, SL)

hasCateSpecial(v,dom,cat,SL) ==
  -- v is a pattern variable, dom it's binding under $Subst
  -- tries to change dom, so that it has category cat under SL
  -- the result is a substitution list or 'failed
  dom is ['FactoredForm,arg] =>
    if isSubDomain(arg,$Integer) then arg := $Integer
    d := ['FactoredRing,arg]
    SL:= hasCate(arg,'(Ring),augmentSub(v,d,SL))
    SL = 'failed => 'failed
    hasCaty(d,cat,SL)
  EQCAR(cat,'Field) or EQCAR(cat, 'DivisionRing) =>
    if isSubDomain(dom,$Integer) then dom := $Integer
    d:= eqType [$QuotientField, dom]
    hasCaty(dom,'(IntegralDomain),augmentSub(v,d,SL))
  cat is ['PolynomialCategory, d, :.] =>
    dom' := ['Polynomial, d]
    (containsVars d or canCoerceFrom(dom, dom'))
       and hasCaty(dom', cat, augmentSub(v,dom',SL))
  isSubDomain(dom,$Integer) =>
    NSL:= hasCate($Integer,cat,augmentSub(v,$Integer,SL))
    NSL = 'failed =>
      hasCateSpecialNew(v, dom, cat, SL)
    hasCaty($Integer,cat,NSL)
  hasCateSpecialNew(v, dom, cat, SL)

-- to be used in $newSystem only
hasCateSpecialNew(v,dom,cat,SL) ==
  fe := member(QCAR cat, '(ElementaryFunctionCategory
       TrigonometricFunctionCategory ArcTrigonometricFunctionCategory
        HyperbolicFunctionCategory ArcHyperbolicFunctionCategory
         PrimitiveFunctionCategory SpecialFunctionCategory Evalable
          CombinatorialOpsCategory TranscendentalFunctionCategory
           AlgebraicallyClosedFunctionSpace ExpressionSpace
             LiouvillianFunctionCategory FunctionSpace))
  alg := member(QCAR cat, '(RadicalCategory AlgebraicallyClosedField))
  fefull := fe or alg or EQCAR(cat, 'CombinatorialFunctionCategory)
  partialResult :=
    EQCAR(dom, 'Variable) or EQCAR(dom, 'Symbol) =>
      CAR(cat) in
       '(SemiGroup AbelianSemiGroup Monoid AbelianGroup AbelianMonoid
         PartialDifferentialRing Ring InputForm) =>
                d := ['Polynomial, $Integer]
                augmentSub(v, d, SL)
      EQCAR(cat, 'Group) =>
        d := ['Fraction, ['Polynomial, $Integer]]
        augmentSub(v, d, SL)
      fefull =>
        d := defaultTargetFE dom
        augmentSub(v, d, SL)
      'failed
    isEqualOrSubDomain(dom, $Integer) =>
      fe =>
        d := defaultTargetFE $Integer
        augmentSub(v, d, SL)
      alg =>
        d := '(AlgebraicNumber)
        --d := defaultTargetFE $Integer
        augmentSub(v, d, SL)
      'failed
    underDomainOf dom = $ComplexInteger =>
      d := defaultTargetFE $ComplexInteger
      hasCaty(d,cat,augmentSub(v, d, SL))
    (dom = $RationalNumber) and alg =>
      d := '(AlgebraicNumber)
      --d := defaultTargetFE $Integer
      augmentSub(v, d, SL)
    fefull =>
      d := defaultTargetFE dom
      augmentSub(v, d, SL)
    'failed
  partialResult = 'failed => 'failed
  hasCaty(d, cat, partialResult)

hasCaty(d,cat,SL) ==
  -- calls hasCat, which looks up a hashtable and returns:
  -- 1. T, NIL or a (has x1 x2) condition, if cat is not parameterized
  -- 2. a list of pairs (argument to cat,condition) otherwise
  -- then the substitution SL is augmented, or the result is 'failed
  cat is ['CATEGORY,.,:y] => hasAttSig(d,subCopy(y,constructSubst d),SL)
  cat is ['SIGNATURE,foo,sig] =>
    hasSig(d,foo,subCopy(sig,constructSubst d),SL)
  cat is ['ATTRIBUTE,a] => hasAtt(d,subCopy(a,constructSubst d),SL)
  x:= hasCat(opOf d,opOf cat) =>
    y:= KDR cat =>
      S  := constructSubst d
      for [z,:cond] in x until not (S1='failed) repeat
        S' := [[p, :mkDomPvar(p, d, z, y)] for [p,:d] in S]
        if $domPvar then
          dom := [CAR d, :[domArg(arg, i, z, y) for i in 0..
                           for arg in CDR d]]
          SL := augmentSub($domPvar, dom, copy SL)
        z' := [domArg2(a, S, S') for a in z]
        S1:= unifyStruct(y,z',copy SL)
        if not (S1='failed) then S1:=
          atom cond => S1
          ncond := subCopy(cond, S)
          ncond is ['has, =d, =cat] => 'failed
          hasCaty1(ncond,S1)
      S1
    atom x => SL
    ncond := subCopy(x, constructSubst d)
    ncond is ['has, =d, =cat] => 'failed
    hasCaty1(ncond, SL)
  'failed

mkDomPvar(p, d, subs, y) ==
  l := MEMQ(p, $FormalMapVariableList) =>
    domArg(d, #$FormalMapVariableList - #l, subs, y)
  d

domArg(type, i, subs, y) ==
  p := MEMQ($FormalMapVariableList.i, subs) =>
    y.(#subs - #p)
  type

domArg2(arg, SL1, SL2) ==
  isSharpVar arg => subCopy(arg, SL1)
  arg = '_$ and $domPvar => $domPvar
  subCopy(arg, SL2)

hasCaty1(cond,SL) ==
  -- cond is either a (has a b) or an OR clause of such conditions
  -- SL is augmented, if cond is true, otherwise the result is 'failed
  $domPvar: local := NIL
  cond is ['has,a,b] => hasCate(a,b,SL)
  cond is ['AND,:args] =>
    for x in args while not (S='failed) repeat S:=
      x is ['has,a,b] => hasCate(a,b, SL)
      -- next line is for an obscure bug in the table
      x is [['has,a,b]] => hasCate(a,b, SL)
      --'failed
      hasCaty1(x, SL)
    S
  cond is ['OR,:args] =>
    for x in args until not (S='failed) repeat S:=
      x is ['has,a,b] => hasCate(a,b,copy SL)
      -- next line is for an obscure bug in the table
      x is [['has,a,b]] => hasCate(a,b,copy SL)
      --'failed
      hasCaty1(x, copy SL)
    S
  keyedSystemError("S2GE0016",
    ['"hasCaty1",'"unexpected condition from category table"])

hasAttSig(d,x,SL) ==
  -- d is domain, x a list of attributes and signatures
  -- the result is an augmented SL, if d has x, 'failed otherwise
  for y in x until SL='failed repeat SL:=
    y is ['ATTRIBUTE,a] => hasAtt(d,a,SL)
    y is ['SIGNATURE,foo,s] => hasSig(d,foo,s,SL)
    keyedSystemError("S2GE0016",
      ['"hasAttSig",'"unexpected form of unnamed category"])
  SL

hasSigAnd(andCls, S0, SL) ==
  dead := NIL
  SA := 'failed
  for cls in andCls while not dead repeat
    SA :=
      atom cls => copy SL
      cls is ['has,a,b] =>
        hasCate(subCopy(a,S0),subCopy(b,S0),copy SL)
      keyedSystemError("S2GE0016",
        ['"hasSigAnd",'"unexpected condition for signature"])
    if SA = 'failed then dead := true
  SA

hasSigOr(orCls, S0, SL) ==
  found := NIL
  SA := 'failed
  for cls in orCls until found repeat
    SA :=
      atom cls => copy SL
      cls is ['has,a,b] =>
        hasCate(subCopy(a,S0),subCopy(b,S0),copy SL)
      cls is ['AND,:andCls] or cls is ['and,:andCls] =>
        hasSigAnd(andCls, S0, SL)
      keyedSystemError("S2GE0016",
        ['"hasSigOr",'"unexpected condition for signature"])
    if SA ^= 'failed then found := true
  SA

hasSig(dom,foo,sig,SL) ==
  -- tests whether domain dom has function foo with signature sig
  -- under substitution SL
  $domPvar: local := nil
  fun:= constructor? CAR dom =>
    S0:= constructSubst dom
    p := ASSQ(foo,getOperationAlistFromLisplib CAR dom) =>
      for [x,.,cond,.] in CDR p until not (S='failed) repeat
        S:=
          atom cond => copy SL
          cond is ['has,a,b] =>
            hasCate(subCopy(a,S0),subCopy(b,S0),copy SL)
          cond is ['AND,:andCls] or cond is ['and,:andCls] =>
            hasSigAnd(andCls, S0, SL)
          cond is ['OR,:orCls] or cond is ['or,:orCls] =>
            hasSigOr(orCls, S0, SL)
          keyedSystemError("S2GE0016",
             ['"hasSig",'"unexpected condition for signature"])
        not (S='failed) => S:= unifyStruct(subCopy(x,S0),sig,S)
      S
    'failed
  'failed

hasAtt(dom,att,SL) ==
  -- tests whether dom has attribute att under SL
  -- needs S0 similar to hasSig above ??
  $domPvar: local := nil
  fun:= CAR dom =>
    atts:= subCopy(GETDATABASE(fun,'ATTRIBUTES),constructSubst dom) =>
      PAIRP (u := getInfovec CAR dom) =>
        --UGH! New world has attributes stored as pairs not as lists!!
        for [x,:cond] in atts until not (S='failed) repeat
          S:= unifyStruct(x,att,copy SL)
          not atom cond and not (S='failed) => S := hasCatExpression(cond,S)
        S
      for [x,cond] in atts until not (S='failed) repeat
        S:= unifyStruct(x,att,copy SL)
        not atom cond and not (S='failed) => S := hasCatExpression(cond,S)
      S
    'failed
  'failed

hasCatExpression(cond,SL) ==
  cond is ["OR",:l] =>
    or/[(y:=hasCatExpression(x,SL)) ^= 'failed for x in l] => y
  cond is ["AND",:l] =>
    and/[(SL:= hasCatExpression(x,SL)) ^= 'failed for x in l] => SL
  cond is ["has",a,b] => hasCate(a,b,SL)
  keyedSystemError("S2GE0016",
    ['"hasSig",'"unexpected condition for attribute"])

unifyStruct(s1,s2,SL) ==
  -- tests for equality of s1 and s2 under substitutions SL and $Subst
  -- the result is a substitution list or 'failed
  s1=s2 => SL
  if s1 is [":",x,.] then s1:= x
  if s2 is [":",x,.] then s2:= x
  if ^atom s1 and CAR s1 = '_# then s1:= LENGTH CADR s1
  if ^atom s2 and CAR s2 = '_# then s2:= LENGTH CADR s2
  s1=s2 => SL
  isPatternVar s1 => unifyStructVar(s1,s2,SL)
  isPatternVar s2 => unifyStructVar(s2,s1,SL)
  atom s1 or atom s2 => 'failed
  until null s1 or null s2 or SL='failed repeat
    SL:= unifyStruct(CAR s1,CAR s2,SL)
    s1:= CDR s1
    s2:= CDR s2
  s1 or s2 => 'failed
  SL

unifyStructVar(v,s,SL) ==
  -- the first argument is a pattern variable, which is not substituted
  -- by SL
  CONTAINED(v,s) => 'failed
  ps := LASSOC(s, SL)
  s1 := (ps => ps; s)
  (s0 := LASSOC(v, SL)) or (s0 := LASSOC(v,$Subst)) =>
    S:= unifyStruct(s0,s1,copy SL)
    S='failed =>
      $Coerce and not atom s0 and constructor? CAR s0 =>
        containsVars s0 or containsVars s1 =>
          ns0 := subCopy(s0, SL)
          ns1 := subCopy(s1, SL)
          containsVars ns0 or containsVars ns1 =>
            $hope:= 'T
            'failed
          if canCoerce(ns0, ns1) then s3 := s1
          else if canCoerce(ns1, ns0) then s3 := s0
          else s3 := nil
          s3 =>
            if (s3 ^= s0) then SL := augmentSub(v,s3,SL)
            if (s3 ^= s1) and isPatternVar(s) then SL := augmentSub(s,s3,SL)
            SL
          'failed
        $domPvar =>
          s3 := resolveTT(s0,s1)
          s3 =>
            if (s3 ^= s0) then SL := augmentSub(v,s3,SL)
            if (s3 ^= s1) and isPatternVar(s) then SL := augmentSub(s,s3,SL)
            SL
          'failed
--        isSubDomain(s,s0) => augmentSub(v,s0,SL)
        'failed
      'failed
    augmentSub(v,s,S)
  augmentSub(v,s,SL)

ofCategory(dom,cat) ==
  -- entry point to category evaluation from other points than type
  --   analysis
  -- the result is true or NIL
  $Subst:local:= NIL
  $hope:local := NIL
  IDENTP dom => NIL
  cat is ['Join,:cats] => and/[ofCategory(dom,c) for c in cats]
  (hasCaty(dom,cat,NIL) ^= 'failed)

printMms(mmS) ==
  -- mmS a list of modemap signatures
  sayMSG '" "
  for [sig,imp,.] in mmS for i in 1.. repeat
    istr := STRCONC('"[",STRINGIMAGE i,'"]")
    if QCSIZE(istr) = 3 then istr := STRCONC(istr,'" ")
    sayMSG [:bright istr,'"signature:   ",:formatSignature CDR sig]
    CAR sig='local =>
      sayMSG ['"      implemented: local function ",imp]
    imp is ['XLAM,:.] =>
      sayMSG concat('"      implemented: XLAM from ",
        prefix2String CAR sig)
    sayMSG concat('"      implemented: slot ",imp,
      '" from ",prefix2String CAR sig)
  sayMSG '" "

containsVars(t) ==
  -- tests whether term t contains a * variable
  atom t => isPatternVar t
  containsVars1(t)

containsVars1(t) ==
  -- recursive version, which works on a list
  [t1,:t2]:= t
  atom t1 =>
    isPatternVar t1 or
      atom t2 => isPatternVar t2
      containsVars1(t2)
  containsVars1(t1) or
    atom t2 => isPatternVar t2
    containsVars1(t2)

isPartialMode m ==
  CONTAINED($EmptyMode,m)


getSymbolType var ==
-- var is a pattern variable
  p:= ASSQ(var,$SymbolType) => CDR p
  t:= '(Polynomial (Integer))
  $SymbolType:= CONS(CONS(var,t),$SymbolType)
  t

isEqualOrSubDomain(d1,d2) ==
  -- last 2 parts are for tagged unions (hack for now, RSS)
  (d1=d2) or isSubDomain(d1,d2) or
    (atom(d1) and ((d2 is ['Variable,=d1]) or (d2 is [=d1])))
     or (atom(d2) and ((d1 is ['Variable,=d2]) or (d1 is [=d2])))

defaultTypeForCategory(cat, SL) ==
  -- this function returns a domain belonging to cat
  -- note that it is important to note that in some contexts one
  -- might not want to use this result. For example, evalMmCat1
  -- calls this and should possibly fail in some cases.
  cat := subCopy(cat, SL)
  c := CAR cat
  d := GETDATABASE(c, 'DEFAULTDOMAIN)
  d => [d, :CDR cat]
  cat is [c] =>
    c = 'Field => $RationalNumber
    c in '(Ring IntegralDomain EuclideanDomain GcdDomain
      OrderedRing DifferentialRing) => '(Integer)
    c = 'OrderedSet => $Symbol
    c = 'FloatingPointSystem => '(Float)
    NIL
  cat is [c,p1] =>
    c = 'FiniteLinearAggregate => ['Vector, p1]
    c = 'VectorCategory => ['Vector, p1]
    c = 'SetAggregate => ['Set, p1]
    c = 'SegmentCategory => ['Segment, p1]
    NIL
  cat is [c,p1,p2] =>
    NIL
  cat is [c,p1,p2,p3] =>
    cat is ['MatrixCategory, d, ['Vector, =d], ['Vector, =d]] =>
      ['Matrix, d]
    NIL
  NIL