-- | Label-set analysis which annotates all the statements in the script
-- with their label sets according to ECMAScript specification,
-- section 12.12. The result of this analysis are useful for building
-- control-flow graphs.

module Language.ECMAScript3.Analysis.LabelSets
       {-# DEPRECATED "Use 'Language.ECMAScript3.Analysis.LabelSet'\
                      \ from package 'language-ecmascript-analysis'" #-}
       (annotateLabelSets
       ,Label(..)) where

import Language.ECMAScript3.Syntax
import Language.ECMAScript3.Syntax.Annotations
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Generics.Uniplate.Data
import Data.Data (Data)
import Control.Applicative
import Data.Typeable (Typeable)

-- | Labels are either strings (identifiers) or /empty/ (see 12.12 of
-- the spec)
data Label = Label String
           | EmptyLabel
             deriving (Ord, Eq, Show, Data, Typeable)

-- | Annotates statements with their label sets; example use:
--
-- >>> let jsa = reannotate (\a -> (a, Set.empty))
-- >>> in  annotateLabelSets jsa snd (\labs (a, ls) -> (a, labs `Set.union` ls))
annotateLabelSets :: Data a =>
                     (a -> Set Label) -- ^ annotation read function
                  -> (Set Label -> a -> a) -- ^ annotation write function
                  -> JavaScript a  -- ^ the script to annotate
                  -> JavaScript a
annotateLabelSets r w = transformBi (annotateFuncStmtBodies r w)
                      . transformBi (annotateFuncExprBodies r w)
                      . descendBi   (annotateStatement r w)

annotateFuncStmtBodies :: Data a =>
                          (a -> Set Label)
                       -> (Set Label -> a -> a)
                       -> Statement a
                       -> Statement a
annotateFuncStmtBodies r w s = case s of
  FunctionStmt a name params body ->
    let newbody = map (descend (annotateStatement r w)) body
    in  FunctionStmt a name params newbody
  _ -> s

annotateFuncExprBodies :: Data a =>
                          (a -> Set Label)
                       -> (Set Label -> a -> a)
                       -> Expression a
                       -> Expression a
annotateFuncExprBodies r w e = case e of
  FuncExpr a mname params body ->
    let newbody = map (descend (annotateStatement r w)) body
    in  FuncExpr a mname params newbody
  _ -> e

-- | 12.12 ECMA262: the production /Identifier/ : /Statement/ is
-- evaluated by adding /Identifier/ to the label ser of /Statement/
-- and then evluating /Statement/. If the /LabelledStatement/ itsef
-- has a non-empty label set, these labels are also added to the label
-- set of /Statement/ before evaluating it. ... Prior to evaluation of
-- a /LabelledStatement/, the contained /Statement/ is regarded as
-- possessing an empty label set, unless it is an /IterationStatement/
-- or a /SwitchStatement/, in which case it is regarded as possessing
-- a label set consisting of the single element, @empty@.
annotateStatement :: Data a =>
                     (a -> Set Label)
                  -> (Set Label -> a -> a)
                  -> Statement a
                  -> Statement a
annotateStatement r w s = case s of
  LabelledStmt ann lab stmt ->
    let labelset = Set.insert (id2Label lab) (r ann)
        newstmt  = annotateStatement r w $ w labelset <$> stmt
    in  LabelledStmt ann lab newstmt
  SwitchStmt {} ->
    let labelset = Set.insert EmptyLabel (r $ getAnnotation s)
    in  descend (annotateStatement r w) (w labelset <$> s)
  _ | isIterationStmt s ->
    let labelset = Set.insert EmptyLabel (r $ getAnnotation s)
    in  descend (annotateStatement r w) (w labelset <$> s)
  _                     -> descend (annotateStatement r w) s

id2Label :: Id a -> Label
id2Label = Label . unId