packages feed

haste-compiler-0.6.0.0: src/Haste/AST/Syntax.hs

{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, FlexibleInstances, CPP,
             OverloadedStrings #-}
-- | Abstract syntax of Haste's intermediate format.
module Haste.AST.Syntax where
import qualified Data.Set as S
import qualified Data.Map.Strict as M
import Haste.AST.Op
import qualified Data.ByteString.Char8 as BS

type Arity = Int
type Comment = BS.ByteString
type Reorderable = Bool

-- | A Name consists of a variable name and optional (package, module)
--   information.
data Name = Name {
    nameIdent :: !BS.ByteString,
    nameQualifier :: !(Maybe (BS.ByteString, BS.ByteString))
  } deriving (Eq, Ord, Show)

class HasModule a where
  moduleOf :: a -> Maybe BS.ByteString
  pkgOf    :: a -> Maybe BS.ByteString

instance HasModule Name where
  moduleOf (Name _ mmod) = fmap snd mmod
  pkgOf (Name _ mmod)    = fmap fst mmod

instance HasModule Var where
  moduleOf (Foreign _)      = Nothing
  moduleOf (Internal n _ _) = moduleOf n
  pkgOf (Foreign _)         = Nothing
  pkgOf (Internal n _ _)    = pkgOf n

type KnownLoc = Bool

-- | Representation of variables.
data Var where
  Foreign  :: !BS.ByteString -> Var
  -- | Being a "known location" means that we can never substitute this
  --   variable for another one, as it is used to hold "return values" from
  --   case statements, tail loopification and similar.
  --   If a variable is *not* a known location, then we may always perform the
  --   substitution @a=b ; exp => exp [a/b]@.
  Internal :: !Name -> !Comment -> !KnownLoc -> Var
  deriving (Show)

isKnownLoc :: Var -> Bool
isKnownLoc (Internal _ _ knownloc) = knownloc
isKnownLoc _                       = False

instance Eq Var where
  {-# INLINE (==) #-}
  (Foreign f1)  == (Foreign f2)          = f1 == f2
  (Internal i1 _ _) == (Internal i2 _ _) = i1 == i2
  _ == _                                 = False

instance Ord Var where
  {-# INLINE compare #-}
  compare (Foreign f1) (Foreign f2)           = compare f1 f2
  compare (Internal i1 _ _) (Internal i2 _ _) = compare i1 i2
  compare (Foreign _) (Internal _ _ _)        = Prelude.LT
  compare (Internal _ _ _) (Foreign _)        = Prelude.GT

-- | Left hand side of an assignment. Normally we only assign internal vars,
--   but for some primops we need to assign array elements as well.
data LHS where
  -- | Introduce a new variable. May be reorderable.
  --   Invariant: a NewVar must be the first occurrence of a 'Var' in its
  --   scope.
  NewVar :: !Reorderable -> !Var -> LHS
  -- | Assign a value to an arbitrary LHS expression. May be reorderable.
  LhsExp :: !Reorderable -> !Exp -> LHS
  deriving (Eq, Show)

-- | Distinguish between normal, optimized and method calls.
--   Normal and optimized calls take a boolean indicating whether the called
--   function should trampoline or not. This defaults to True, and should
--   only be set to False when there is absolutely no possibility whatsoever
--   that the called function will tailcall.
data Call where
  Normal   :: !Bool          -> Call
  Fast     :: !Bool          -> Call
  Method   :: !BS.ByteString -> Call
  deriving (Eq, Show)

-- | Literals; nothing fancy to see here.
data Lit where
  LNum  :: !Double        -> Lit
  LStr  :: !BS.ByteString -> Lit
  LBool :: !Bool          -> Lit
  LInt  :: !Integer       -> Lit
  LNull :: Lit
  deriving (Eq, Show)

-- | Expressions. Completely predictable.
data Exp where
  Var       :: !Var -> Exp
  Lit       :: !Lit -> Exp
  -- | A literal JS snippet.
  --   Invariant: JSLits must not perform side effects or significant
  --   computation.
  JSLit     :: !BS.ByteString -> Exp
  Not       :: !Exp -> Exp
  BinOp     :: !BinOp -> Exp -> !Exp -> Exp
  Fun       :: ![Var] -> !Stm -> Exp
  Call      :: !Arity -> !Call -> !Exp -> ![Exp] -> Exp
  Index     :: !Exp -> !Exp -> Exp
  Member    :: !Exp -> !BS.ByteString -> Exp
  Arr       :: ![Exp] -> Exp
  Obj       :: ![(BS.ByteString, Exp)] -> Exp
  AssignEx  :: !Exp -> !Exp -> Exp
  IfEx      :: !Exp -> !Exp -> !Exp -> Exp
  Eval      :: !Exp -> Exp
  Thunk     :: !Bool -> !Stm -> Exp -- Thunk may be updatable or not
  deriving (Eq, Show)

-- | Is the given expression guaranteed to not be a thunk?
--   @definitelyNotThunk e <=> safe to skip evaluation of e@
definitelyNotThunk :: Exp -> Bool
definitelyNotThunk (Lit {})   = True
definitelyNotThunk (JSLit {}) = True
definitelyNotThunk (Not {})   = True
definitelyNotThunk (BinOp {}) = True
definitelyNotThunk (Fun {})   = True
definitelyNotThunk (Arr {})   = True
definitelyNotThunk (Eval {})  = True
definitelyNotThunk _          = False

-- | Statements. The only mildly interesting thing here are the Case and Jump
--   constructors, which allow explicit sharing of continuations.
data Stm where
  Case     :: !Exp -> !Stm -> ![Alt] -> !Stm -> Stm
  Forever  :: !Stm -> Stm
  Assign   :: !LHS -> !Exp -> !Stm -> Stm
  Return   :: !Exp -> Stm
  Cont     :: Stm
  Stop     :: Stm -- Do nothing at all past this point
  Tailcall :: !Exp -> Stm
  ThunkRet :: !Exp -> Stm -- Return from a Thunk
  deriving (Eq, Show)

-- | Case alternatives - an expression to match and a branch.
type Alt = (Exp, Stm)

-- | Represents a module. A module has a name, an owning
--   package, a dependency map of all its definitions, and a bunch of
--   definitions.
data Module = Module {
    modPackageId   :: !BS.ByteString,
    modName        :: !BS.ByteString,
    modDeps        :: M.Map Name (S.Set Name),
    modDefs        :: M.Map Name Exp,
    modSPT         :: [Name]
  }

-- | Merge two modules. The module and package IDs of the second argument are
--   used, and the second argument will take precedence for symbols which exist
--   in both.
merge :: Module -> Module -> Module
merge m1 m2 = Module {
    modPackageId = modPackageId m2,
    modName = modName m2,
    modDeps = M.union (modDeps m1) (modDeps m2),
    modDefs = M.union (modDefs m1) (modDefs m2),
    modSPT  = modSPT m1 ++ modSPT m2
  }

-- | Imaginary module for foreign code that may need one.
foreignModule :: Module
foreignModule = Module {
    modPackageId   = "",
    modName        = "",
    modDeps        = M.empty,
    modDefs        = M.empty,
    modSPT         = []
  }

-- | An LHS that's guaranteed to not ever be read, enabling the pretty
--   printer to ignore assignments to it.
blackHole :: LHS
blackHole = LhsExp False $ Var blackHoleVar

-- | Name of the data constructor tag field.
dataConTagField :: BS.ByteString
dataConTagField = "_"

-- | Infinite list of data constructor names.
dataConFieldNames :: [BS.ByteString]
dataConFieldNames = names
  where
    prefix  = BS.singleton '_'
    names   = concat [small, capital, map (BS.append prefix) names]
    small   = map BS.singleton ['a'..'z']
    capital = map BS.singleton ['A'..'Z']

-- | The global "zero object", which represents @{_:0}@.
zeroObject :: Exp
zeroObject = Var $ Foreign "__Z"

-- | The variable of the blackHole LHS.
blackHoleVar :: Var
blackHoleVar = Internal (Name "" (Just ("$blackhole", "$blackhole"))) "" False

-- | Returns the precedence of the top level operator of the given expression.
--   Everything that's not an operator has equal precedence, higher than any
--   binary operator.
expPrec :: Exp -> Int
expPrec (BinOp Sub (Lit (LNum 0)) _) = 500 -- 0-n is always printed as -n
expPrec (BinOp op _ _)               = opPrec op
expPrec (AssignEx _ _)               = 0
expPrec (Not _)                      = 500
expPrec _                            = 1000