{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.ECMAScript3.Syntax (JavaScript(..)
,unJavaScript
,Statement(..)
,isIterationStmt
,CaseClause(..)
,CatchClause(..)
,ForInit(..)
,ForInInit(..)
,VarDecl(..)
,Expression(..)
,InfixOp(..)
,AssignOp(..)
,Id(..)
,unId
,PrefixOp(..)
,Prop(..)
,UnaryAssignOp(..)
,LValue (..)
,SourcePos
,isValid
,isValidIdentifier
,isValidIdentifierName
,isReservedWord
,isValidIdStart
,isValidIdPart
,EnclosingStatement(..)
,pushLabel
,pushEnclosing
,HasLabelSet (..)
,isIter
,isIterSwitch
) where
import Text.Parsec.Pos(initialPos,SourcePos)
import Data.Data (Data)
import Data.Typeable (Typeable)
import Data.Foldable (Foldable)
import Data.Traversable (Traversable)
import Data.Default.Class
import Data.Generics.Uniplate.Data
import Data.Char
import Control.Monad.State
import Control.Arrow
data JavaScript a
= Script a [Statement a]
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
instance Default a => Default (JavaScript a) where
def = Script def []
unJavaScript :: JavaScript a -> [Statement a]
unJavaScript (Script _ stmts) = stmts
instance Default SourcePos where
def = initialPos ""
data Id a = Id a String
deriving (Show,Eq,Ord,Data,Typeable,Functor,Foldable,Traversable)
unId :: Id a -> String
unId (Id _ s) = s
data InfixOp = OpLT
| OpLEq
| OpGT
| OpGEq
| OpIn
| OpInstanceof
| OpEq
| OpNEq
| OpStrictEq
| OpStrictNEq
| OpLAnd
| OpLOr
| OpMul
| OpDiv
| OpMod
| OpSub
| OpLShift
| OpSpRShift
| OpZfRShift
| OpBAnd
| OpBXor
| OpBOr
| OpAdd
deriving (Show,Data,Typeable,Eq,Ord,Enum)
data AssignOp = OpAssign
| OpAssignAdd
| OpAssignSub
| OpAssignMul
| OpAssignDiv
| OpAssignMod
| OpAssignLShift
| OpAssignSpRShift
| OpAssignZfRShift
| OpAssignBAnd
| OpAssignBXor
| OpAssignBOr
deriving (Show,Data,Typeable,Eq,Ord)
data UnaryAssignOp = PrefixInc
| PrefixDec
| PostfixInc
| PostfixDec
deriving (Show, Data, Typeable, Eq, Ord)
data PrefixOp = PrefixLNot
| PrefixBNot
| PrefixPlus
| PrefixMinus
| PrefixTypeof
| PrefixVoid
| PrefixDelete
deriving (Show,Data,Typeable,Eq,Ord)
data Prop a = PropId a (Id a)
| PropString a String
| PropNum a Integer
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data LValue a
= LVar a String
| LDot a (Expression a) String
| LBracket a (Expression a) (Expression a)
deriving (Show, Eq, Ord, Data, Typeable, Functor,Foldable,Traversable)
data Expression a
= StringLit a String
| RegexpLit a String Bool Bool
| NumLit a Double
| IntLit a Int
| BoolLit a Bool
| NullLit a
| ArrayLit a [Expression a]
| ObjectLit a [(Prop a, Expression a)]
| ThisRef a
| VarRef a (Id a)
| DotRef a (Expression a) (Id a)
| BracketRef a (Expression a) (Expression a)
| NewExpr a (Expression a) [Expression a]
| PrefixExpr a PrefixOp (Expression a)
| UnaryAssignExpr a UnaryAssignOp (LValue a)
| InfixExpr a InfixOp (Expression a) (Expression a)
| CondExpr a (Expression a) (Expression a) (Expression a)
| AssignExpr a AssignOp (LValue a) (Expression a)
| ListExpr a [Expression a]
| CallExpr a (Expression a) [Expression a]
| FuncExpr a (Maybe (Id a)) [Id a] [Statement a]
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data CaseClause a = CaseClause a (Expression a) [Statement a]
| CaseDefault a [Statement a]
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data CatchClause a = CatchClause a (Id a) (Statement a)
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data VarDecl a = VarDecl a (Id a) (Maybe (Expression a))
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data ForInit a = NoInit
| VarInit [VarDecl a]
| ExprInit (Expression a)
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data ForInInit a = ForInVar (Id a)
| ForInLVal (LValue a)
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
data Statement a
= BlockStmt a [Statement a]
| EmptyStmt a
| ExprStmt a (Expression a)
| IfStmt a (Expression a) (Statement a) (Statement a)
| IfSingleStmt a (Expression a) (Statement a)
| SwitchStmt a (Expression a) [CaseClause a]
| WhileStmt a (Expression a) (Statement a)
| DoWhileStmt a (Statement a) (Expression a)
| BreakStmt a (Maybe (Id a))
| ContinueStmt a (Maybe (Id a))
| LabelledStmt a (Id a) (Statement a)
| ForInStmt a (ForInInit a) (Expression a) (Statement a)
| ForStmt a (ForInit a)
(Maybe (Expression a))
(Maybe (Expression a))
(Statement a)
| TryStmt a (Statement a) (Maybe (CatchClause a))
(Maybe (Statement a))
| ThrowStmt a (Expression a)
| ReturnStmt a (Maybe (Expression a))
| WithStmt a (Expression a) (Statement a)
| VarDeclStmt a [VarDecl a]
| FunctionStmt a (Id a) [Id a] [Statement a]
deriving (Show,Data,Typeable,Eq,Ord,Functor,Foldable,Traversable)
isIterationStmt :: Statement a -> Bool
isIterationStmt s = case s of
WhileStmt {} -> True
DoWhileStmt {} -> True
ForStmt {} -> True
ForInStmt {} -> True
_ -> False
isValid :: forall a. (Data a, Typeable a) => JavaScript a -> Bool
isValid js = checkIdentifiers js && checkBreakContinueLabels js
where checkIdentifiers :: (Data a, Typeable a) => JavaScript a -> Bool
checkIdentifiers js =
and $ map isValidIdentifierName $
[n | (Id _ n) :: Id a <- universeBi js] ++
[n | (LVar _ n) :: LValue a <- universeBi js] ++
[n | (LDot _ _ n) :: LValue a <- universeBi js]
checkBreakContinueLabels js@(Script _ body) = and $ map checkStmt $
body ++ concat ([body | FuncExpr _ _ _ body <- universeBi js] ++
[body | FunctionStmt _ _ _ body <- universeBi js])
checkStmt :: Statement a -> Bool
checkStmt s = evalState (checkStmtM s) ([], [])
checkStmtM :: Statement a -> State ([Label], [EnclosingStatement]) Bool
checkStmtM stmt = case stmt of
ContinueStmt a mlab -> do
encls <- gets snd
let enIts = filter isIter encls
return $ case mlab of
Nothing -> not $ null enIts
Just lab -> any (elem (unId lab) . getLabelSet) enIts
BreakStmt a mlab -> do
encls <- gets snd
return $ case mlab of
Nothing -> any isIterSwitch encls
Just lab -> any (elem (unId lab) . getLabelSet) encls
LabelledStmt _ lab s -> do
labs <- gets fst
if (unId lab) `elem` labs then return False
else pushLabel lab $ checkStmtM s
WhileStmt _ _ s -> iterCommon s
DoWhileStmt _ s _ -> iterCommon s
ForStmt _ _ _ _ s -> iterCommon s
ForInStmt _ _ _ s -> iterCommon s
SwitchStmt _ _ cs -> pushEnclosing EnclosingSwitch $ liftM and $ mapM checkCaseM cs
BlockStmt _ ss -> pushEnclosing EnclosingOther $ liftM and $ mapM checkStmtM ss
IfStmt _ _ t e -> liftM2 (&&) (checkStmtM t) (checkStmtM e)
IfSingleStmt _ _ t -> checkStmtM t
TryStmt _ body mcatch mfinally -> liftM2 (&&) (checkStmtM body) $
liftM2 (&&) (maybe (return True) checkCatchM mcatch)
(maybe (return True) checkStmtM mfinally)
WithStmt _ _ body -> checkStmtM body
_ -> return True
iterCommon s = pushEnclosing EnclosingIter $ checkStmtM s
pushEnclosing :: Monad m => ([Label] -> EnclosingStatement)
-> StateT ([Label], [EnclosingStatement]) m a
-> StateT ([Label], [EnclosingStatement]) m a
pushEnclosing ctor = bracketState (\(labs, encls) -> ([], ctor labs:encls))
pushLabel :: Monad m => Id b -> StateT ([Label], [EnclosingStatement]) m a
-> StateT ([Label], [EnclosingStatement]) m a
pushLabel l = bracketState (first (unId l:))
checkCaseM c = let ss = case c of
CaseClause _ _ body -> body
CaseDefault _ body -> body
in liftM and $ mapM checkStmtM ss
checkCatchM (CatchClause _ _ body) = checkStmtM body
bracketState :: Monad m => (s -> s) -> StateT s m a -> StateT s m a
bracketState f m = do original <- get
modify f
rv <- m
put original
return rv
isValidIdentifier :: Id a -> Bool
isValidIdentifier (Id _ name) = isValidIdentifierName name
isValidIdentifierName :: String -> Bool
isValidIdentifierName name = case name of
"" -> False
(c:cs) -> isValidIdStart c && and (map isValidIdPart cs) && (not $ isReservedWord name)
isReservedWord :: String -> Bool
isReservedWord = (`elem` reservedWords)
where reservedWords = keyword ++ futureReservedWord ++ nullKw ++ boolLit
keyword = ["break", "case", "catch", "continue", "default", "delete"
,"do", "else", "finally", "for", "function", "if", "in"
,"instanceof", "new", "return", "switch", "this", "throw"
,"try", "typeof", "var", "void", "while", "with"]
futureReservedWord = ["abstract", "boolean", "byte", "char", "class"
,"const", "debugger", "enum", "export", "extends"
,"final", "float", "goto", "implements", "int"
,"interface", "long", "native", "package", "private"
,"protected", "short", "static", "super"
,"synchronized", "throws", "transient", "volatile"]
nullKw = ["null"]
boolLit = ["true", "false"]
isValidIdStart :: Char -> Bool
isValidIdStart c = unicodeLetter c || c == '$' || c == '_'
where unicodeLetter c = case generalCategory c of
UppercaseLetter -> True
LowercaseLetter -> True
TitlecaseLetter -> True
ModifierLetter -> True
OtherLetter -> True
LetterNumber -> True
_ -> False
isValidIdPart :: Char -> Bool
isValidIdPart c = isValidIdStart c || isValidIdPartUnicode c
where isValidIdPartUnicode c = case generalCategory c of
NonSpacingMark -> True
SpacingCombiningMark -> True
DecimalNumber -> True
ConnectorPunctuation -> True
_ -> False
data EnclosingStatement = EnclosingIter [Label]
| EnclosingSwitch [Label]
| EnclosingOther [Label]
instance Show EnclosingStatement where
show (EnclosingIter ls) = "iteration" ++ show ls
show (EnclosingSwitch ls) = "switch" ++ show ls
show (EnclosingOther ls) = "statement" ++ show ls
isIter :: EnclosingStatement -> Bool
isIter (EnclosingIter _) = True
isIter _ = False
isIterSwitch :: EnclosingStatement -> Bool
isIterSwitch (EnclosingIter _) = True
isIterSwitch (EnclosingSwitch _) = True
isIterSwitch _ = False
class HasLabelSet a where
getLabelSet :: a -> [Label]
setLabelSet :: [Label] -> a -> a
modifyLabelSet :: HasLabelSet a => ([Label] -> [Label]) -> a -> a
modifyLabelSet f a = setLabelSet (f $ getLabelSet a) a
instance HasLabelSet EnclosingStatement where
getLabelSet e = case e of
EnclosingIter ls -> ls
EnclosingSwitch ls -> ls
EnclosingOther ls -> ls
setLabelSet ls e = case e of
EnclosingIter _ -> EnclosingIter ls
EnclosingSwitch _ -> EnclosingSwitch ls
EnclosingOther _ -> EnclosingOther ls
type Label = String