Rename.hs

Plain text version of Rename.hs

----------------------------------------------------------------------
-- |
-- Module      : Rename
-- Maintainer  : AR
-- Stability   : (stable)
-- Portability : (portable)
--
-- > CVS $Date: 2005/05/30 18:39:44 $
-- > CVS $Author: aarne $
-- > CVS $Revision: 1.19 $
--
-- AR 14\/5\/2003
-- The top-level function 'renameGrammar' does several things:
--
--   - extends each module symbol table by indirections to extended module
--
--   - changes unqualified and as-qualified imports to absolutely qualified
--
--   - goes through the definitions and resolves names
--
-- Dependency analysis between modules has been performed before this pass.
-- Hence we can proceed by @fold@ing "from left to right".
-----------------------------------------------------------------------------

module GF.Compile.Rename (
     renameSourceTerm,
     renameModule
    ) where

import GF.Infra.Ident
import GF.Infra.CheckM
import GF.Grammar.Grammar
import GF.Grammar.Values
import GF.Grammar.Predef
import GF.Grammar.Lookup
import GF.Grammar.Macros
import GF.Grammar.Printer
import GF.Data.Operations

import Control.Monad
import Data.List (nub,(\\))
import qualified Data.List as L
import qualified Data.Map as Map
import Data.Maybe(mapMaybe)
import GF.Text.Pretty

-- | this gives top-level access to renaming term input in the cc command
renameSourceTerm :: Grammar -> ModuleName -> Term -> Check Term
renameSourceTerm g m t = do
  mi     <- lookupModule g m
  status <- buildStatus "" g (m,mi)
  renameTerm status [] t

renameModule :: FilePath -> Grammar -> Module -> Check Module
renameModule cwd gr mo@(m,mi) = do
  status <- buildStatus cwd gr mo
  js     <- checkMapRecover (renameInfo cwd status mo) (jments mi)
  return (m, mi{jments = js})

type Status = (StatusMap, [(OpenSpec, StatusMap)])

type StatusMap = Map.Map Ident StatusInfo

type StatusInfo = Ident -> Term

-- Delays errors, allowing many errors to be detected and reported
renameIdentTerm env = accumulateError (renameIdentTerm' env)

-- Fails immediately on error, makes it possible to try other possibilities
renameIdentTerm' :: Status -> Term -> Check Term
renameIdentTerm' env@(act,imps) t0 =
  case t0 of
    Vr c -> ident predefAbs c
    Cn c -> ident (\_ s -> checkError s) c
    Q (m',c) | m' == cPredef {- && isInPredefined c -} -> return t0
    Q (m',c) -> do
      m <- lookupErr m' qualifs
      f <- lookupIdent c m
      return $ f c
    QC (m',c) | m' == cPredef {- && isInPredefined c -} -> return t0
    QC (m',c) -> do
      m <- lookupErr m' qualifs
      f <- lookupIdent c m
      return $ f c
    _ -> return t0
  where
    opens   = [st  | (OSimple _,st) <- imps]
    qualifs = [(m, st) | (OQualif m _, st) <- imps] ++
              [(m, st) | (OQualif _ m, st) <- imps] ++
              [(m, st) | (OSimple m, st) <- imps] -- qualif is always possible

    -- this facility is mainly for BWC with GF1: you need not import PredefAbs
    predefAbs c s
      | isPredefCat c = return (Q (cPredefAbs,c))
      | otherwise     = checkError s

    ident alt c =
      case Map.lookup c act of
        Just f -> return (f c)
        _      -> case mapMaybe (Map.lookup c) opens of
                    [f]  -> return (f c)
                    []   -> alt c ("constant not found:" <+> c $$
                                   "given" <+> fsep (punctuate ',' (map fst qualifs)))
                    fs   -> case nub [f c | f <- fs]  of
                              [tr]     -> return tr
                              ts@(t:_) -> do checkWarn ("atomic term" <+> ppTerm Qualified 0 t0 $$
                                                        "conflict" <+> hsep (punctuate ',' (map (ppTerm Qualified 0) ts)) $$
                                                        "given" <+> fsep (punctuate ',' (map fst qualifs)))
                                             return (bestTerm ts) -- Heuristic for resource grammar. Returns t for all others.
       where
        -- Hotfix for https://github.com/GrammaticalFramework/gf-core/issues/56
        -- Real bug is probably somewhere deeper in recognising excluded functions. /IL 2020-06-06
        notFromCommonModule :: Term -> Bool
        notFromCommonModule term =
          let t = render $ ppTerm Qualified 0 term :: String
           in not $ any (\moduleName -> moduleName `L.isPrefixOf` t)
                        ["CommonX", "ConstructX", "ExtendFunctor"
                        ,"MarkHTMLX", "ParamX", "TenseX", "TextX"]

        -- If one of the terms comes from the common modules,
        -- we choose the other one, because that's defined in the grammar.
        bestTerm :: [Term] -> Term
        bestTerm [] = error "constant not found" -- not reached: bestTerm is only called for case ts@(t:_)
        bestTerm ts@(t:_) =
          let notCommon = [t | t <- ts, notFromCommonModule t]
           in case notCommon of
                []    -> t -- All terms are from common modules, return first of original list
                (u:_) -> u --