-- | Parser for ECMAScript 3. {-# LANGUAGE FlexibleContexts #-} module Language.ECMAScript3.Parser (parse , Parser , expression , statement , program , parseFromString , parseFromFile -- old and deprecated , parseScriptFromString , parseJavaScriptFromFile , parseScript , parseExpression , parseString , ParsedStatement , ParsedExpression , parseSimpleExpr' , parseBlockStmt , parseStatement , StatementParser , ExpressionParser , assignExpr , parseObjectLit ) where import Language.ECMAScript3.Lexer hiding (identifier) import qualified Language.ECMAScript3.Lexer as Lexer import Language.ECMAScript3.Parser.State import Language.ECMAScript3.Parser.Type import Language.ECMAScript3.Syntax hiding (pushLabel) import Language.ECMAScript3.Syntax.Annotations import Data.Default.Class import Text.Parsec hiding (parse) import Text.Parsec.Expr import Control.Monad(liftM,liftM2) import Control.Monad.Trans (MonadIO,liftIO) import Numeric(readDec,readOct,readHex, readFloat) import Data.Char import Control.Monad.Identity import Data.Maybe (isJust, isNothing, fromMaybe) import Control.Monad.Error.Class import Control.Applicative ((<$>), (<*>)) {-# DEPRECATED ParsedStatement, ParsedExpression, StatementParser, ExpressionParser "These type aliases will be hidden in the next version" #-} {-# DEPRECATED parseSimpleExpr', parseBlockStmt, parseObjectLit "These parsers will be hidden in the next version" #-} {-# DEPRECATED assignExpr, parseExpression "Use 'expression' instead" #-} {-# DEPRECATED parseStatement "Use 'statement' instead" #-} {-# DEPRECATED parseScript "Use 'program' instead" #-} {-# DEPRECATED parseScriptFromString, parseString "Use 'parseFromString' instead" #-} {-# DEPRECATED parseJavaScriptFromFile "Use 'parseFromFile' instead" #-} -- We parameterize the parse tree over source-locations. type ParsedStatement = Statement SourcePos type ParsedExpression = Expression SourcePos -- These parsers can store some arbitrary state type StatementParser s = Parser s ParsedStatement type ExpressionParser s = Parser s ParsedExpression initialParserState :: ParserState initialParserState = [] -- | checks if the label is not yet on the stack, if it is -- throws -- an error; otherwise it pushes it onto the stack pushLabel :: String -> Parser s () pushLabel lab = do labs <- getState pos <- getPosition if lab `elem` labs then fail $ "Duplicate label at " ++ show pos else putState (lab:labs) popLabel :: Parser s () popLabel = modifyState safeTail where safeTail [] = [] safeTail (_:xs) = xs clearLabels :: ParserState -> ParserState clearLabels _ = [] withFreshLabelStack :: Parser s a -> Parser s a withFreshLabelStack p = do oldState <- getState putState $ clearLabels oldState a <- p putState oldState return a identifier :: Stream s Identity Char => Parser s (Id SourcePos) identifier = liftM2 Id getPosition Lexer.identifier --{{{ Statements -- Keep in mind that Token.reserved parsers (exported from the lexer) do not -- consume any input on failure. Note that all statements (expect for labelled -- and expression statements) begin with a reserved-word. If we fail to parse -- this reserved-word, no input is consumed. Hence, we can have the massive or -- block that is parseExpression. Note that if the reserved-word is parsed, it -- must be whatever statement the reserved-word indicates. If we fail after the -- reserved-word, we truly have a syntax error. Since input has been consumed, -- <|> will not try its alternate in parseExpression, and we will fail. parseIfStmt:: Stream s Identity Char => StatementParser s parseIfStmt = do pos <- getPosition reserved "if" test <- parseParenExpr "parenthesized test-expression in if statement" consequent <- parseStatement "true-branch of if statement" optional semi -- TODO: in spec? ((do reserved "else" alternate <- parseStatement return $ IfStmt pos test consequent alternate) <|> return (IfSingleStmt pos test consequent)) parseSwitchStmt :: Stream s Identity Char => StatementParser s parseSwitchStmt = let parseDefault = do pos <- getPosition reserved "default" colon statements <- many parseStatement return (CaseDefault pos statements) parseCase = do pos <- getPosition reserved "case" condition <- parseListExpr colon actions <- many parseStatement return (CaseClause pos condition actions) isCaseDefault (CaseDefault _ _) = True isCaseDefault _ = False checkClauses cs = case filter isCaseDefault cs of (_:c:_) -> fail $ "duplicate default clause in switch statement at " ++ show (getAnnotation c) _ -> return () in do pos <- getPosition reserved "switch" test <- parseParenExpr clauses <- braces $ many $ parseDefault <|> parseCase checkClauses clauses return (SwitchStmt pos test clauses) parseWhileStmt:: Stream s Identity Char => StatementParser s parseWhileStmt = do pos <- getPosition reserved "while" test <- parseParenExpr "parenthesized test-expression in while loop" body <- parseStatement return (WhileStmt pos test body) parseDoWhileStmt:: Stream s Identity Char => StatementParser s parseDoWhileStmt = do pos <- getPosition reserved "do" body <- parseStatement reserved "while" "while at the end of a do block" test <- parseParenExpr "parenthesized test-expression in do loop" optional semi return (DoWhileStmt pos body test) parseContinueStmt:: Stream s Identity Char => StatementParser s parseContinueStmt = do pos <- getPosition reserved "continue" pos' <- getPosition -- Ensure that the identifier is on the same line as 'continue.' id <- if sourceLine pos == sourceLine pos' then liftM Just identifier <|> return Nothing else return Nothing optional semi return $ ContinueStmt pos id parseBreakStmt:: Stream s Identity Char => StatementParser s parseBreakStmt = do pos <- getPosition reserved "break" pos' <- getPosition -- Ensure that the identifier is on the same line as 'break.' id <- if sourceLine pos == sourceLine pos' then liftM Just identifier <|> return Nothing else return Nothing optional semi return $ BreakStmt pos id parseBlockStmt:: Stream s Identity Char => StatementParser s parseBlockStmt = do pos <- getPosition statements <- braces (many parseStatement) return (BlockStmt pos statements) parseEmptyStmt:: Stream s Identity Char => StatementParser s parseEmptyStmt = do pos <- getPosition semi return (EmptyStmt pos) parseLabelledStmt:: Stream s Identity Char => StatementParser s parseLabelledStmt = do pos <- getPosition -- Lookahead for the colon. If we don't see it, we are parsing an identifier -- for an expression statement. label <- try (do label <- identifier colon return label) pushLabel $ unId label statement <- parseStatement popLabel return (LabelledStmt pos label statement) parseExpressionStmt:: Stream s Identity Char => StatementParser s parseExpressionStmt = do pos <- getPosition expr <- parseExpression -- TODO: spec 12.4? optional semi return $ ExprStmt pos expr parseForInStmt:: Stream s Identity Char => StatementParser s parseForInStmt = let parseInit = (reserved "var" >> liftM ForInVar identifier) <|> liftM ForInLVal lvalue in do pos <- getPosition -- Lookahead, so that we don't clash with parseForStmt (init,expr) <- try $ do reserved "for" parens $ do init <- parseInit reserved "in" expr <- parseExpression return (init,expr) body <- parseStatement return $ ForInStmt pos init expr body parseForStmt:: Stream s Identity Char => StatementParser s parseForStmt = let parseInit = (reserved "var" >> liftM VarInit (parseVarDecl `sepBy` comma)) <|> liftM ExprInit parseListExpr <|> return NoInit in do pos <- getPosition reserved "for" reservedOp "(" init <- parseInit semi test <- optionMaybe parseExpression semi iter <- optionMaybe parseExpression reservedOp ")" "closing paren" stmt <- parseStatement return $ ForStmt pos init test iter stmt parseTryStmt:: Stream s Identity Char => StatementParser s parseTryStmt = let parseCatchClause = do pos <- getPosition reserved "catch" id <- parens identifier stmt <- parseStatement return $ CatchClause pos id stmt in do reserved "try" pos <- getPosition guarded <- parseStatement mCatch <- optionMaybe parseCatchClause mFinally <- optionMaybe $ reserved "finally" >> parseStatement -- the spec requires at least a catch or a finally block to -- be present if isJust mCatch || isJust mFinally then return $ TryStmt pos guarded mCatch mFinally else fail $ "A try statement should have at least a catch\ \ or a finally block, at " ++ show pos parseThrowStmt:: Stream s Identity Char => StatementParser s parseThrowStmt = do pos <- getPosition reserved "throw" expr <- parseExpression optional semi return (ThrowStmt pos expr) parseReturnStmt:: Stream s Identity Char => StatementParser s parseReturnStmt = do pos <- getPosition reserved "return" expr <- optionMaybe parseListExpr optional semi return (ReturnStmt pos expr) parseWithStmt:: Stream s Identity Char => StatementParser s parseWithStmt = do pos <- getPosition reserved "with" context <- parseParenExpr stmt <- parseStatement return (WithStmt pos context stmt) parseVarDecl :: Stream s Identity Char => Parser s (VarDecl SourcePos) parseVarDecl = do pos <- getPosition id <- identifier init <- (reservedOp "=" >> liftM Just assignExpr) <|> return Nothing return (VarDecl pos id init) parseVarDeclStmt:: Stream s Identity Char => StatementParser s parseVarDeclStmt = do pos <- getPosition reserved "var" decls <- parseVarDecl `sepBy` comma optional semi return (VarDeclStmt pos decls) parseFunctionStmt:: Stream s Identity Char => StatementParser s parseFunctionStmt = do pos <- getPosition name <- try (reserved "function" >> identifier) -- ambiguity with FuncExpr args <- parens (identifier `sepBy` comma) -- label sets don't cross function boundaries BlockStmt _ body <- withFreshLabelStack parseBlockStmt "function body in { ... }" return (FunctionStmt pos name args body) parseStatement :: Stream s Identity Char => StatementParser s parseStatement = parseIfStmt <|> parseSwitchStmt <|> parseWhileStmt <|> parseDoWhileStmt <|> parseContinueStmt <|> parseBreakStmt <|> parseBlockStmt <|> parseEmptyStmt <|> parseForInStmt <|> parseForStmt <|> parseTryStmt <|> parseThrowStmt <|> parseReturnStmt <|> parseWithStmt <|> parseVarDeclStmt <|> parseFunctionStmt -- labelled, expression and the error message always go last, in this order <|> parseLabelledStmt <|> parseExpressionStmt "statement" -- | The parser that parses a single ECMAScript statement statement :: Stream s Identity Char => Parser s (Statement SourcePos) statement = parseStatement --}}} --{{{ Expressions -- References used to construct this stuff: -- + http://developer.mozilla.org/en/docs/ -- Core_JavaScript_1.5_Reference:Operators:Operator_Precedence -- + http://www.mozilla.org/js/language/grammar14.html -- -- Aren't expression tables nice? Well, we can't quite use them, because of -- JavaScript's ternary (?:) operator. We have to use two expression tables. -- We use one expression table for the assignment operators that bind looser -- than ?: (assignTable). The terms of assignTable are ternary expressions -- (parseTernaryExpr). parseTernaryExpr left-factors the left-recursive -- production for ?:, and is defined over the second expression table, -- exprTable, which consists of operators that bind tighter than ?:. The terms -- of exprTable are atomic expressions, parenthesized expressions, functions and -- array references. --{{{ Primary expressions parseThisRef:: Stream s Identity Char => ExpressionParser s parseThisRef = do pos <- getPosition reserved "this" return (ThisRef pos) parseNullLit:: Stream s Identity Char => ExpressionParser s parseNullLit = do pos <- getPosition reserved "null" return (NullLit pos) parseBoolLit:: Stream s Identity Char => ExpressionParser s parseBoolLit = do pos <- getPosition let parseTrueLit = reserved "true" >> return (BoolLit pos True) parseFalseLit = reserved "false" >> return (BoolLit pos False) parseTrueLit <|> parseFalseLit parseVarRef:: Stream s Identity Char => ExpressionParser s parseVarRef = liftM2 VarRef getPosition identifier parseArrayLit:: Stream s Identity Char => ExpressionParser s parseArrayLit = liftM2 ArrayLit getPosition (squares (assignExpr `sepEndBy` comma)) parseFuncExpr :: Stream s Identity Char => ExpressionParser s parseFuncExpr = do pos <- getPosition reserved "function" name <- optionMaybe identifier args <- parens (identifier `sepBy` comma) -- labels don't cross function boundaries BlockStmt _ body <- withFreshLabelStack parseBlockStmt return $ FuncExpr pos name args body --{{{ parsing strings escapeChars = [('\'','\''),('\"','\"'),('\\','\\'),('b','\b'),('f','\f'),('n','\n'), ('r','\r'),('t','\t'),('v','\v'),('/','/'),(' ',' '),('0','\0')] allEscapes:: String allEscapes = map fst escapeChars parseEscapeChar :: Stream s Identity Char => Parser s Char parseEscapeChar = do c <- oneOf allEscapes let (Just c') = lookup c escapeChars -- will succeed due to line above return c' parseAsciiHexChar :: Stream s Identity Char => Parser s Char parseAsciiHexChar = do char 'x' d1 <- hexDigit d2 <- hexDigit return ((chr.fst.head.readHex) (d1:d2:"")) parseUnicodeHexChar :: Stream s Identity Char => Parser s Char parseUnicodeHexChar = do char 'u' liftM (chr.fst.head.readHex) (sequence [hexDigit,hexDigit,hexDigit,hexDigit]) isWhitespace ch = ch `elem` " \t" -- The endWith argument is either single-quote or double-quote, depending on how -- we opened the string. parseStringLit' endWith = (char endWith >> return "") <|> (do try (string "\\'") cs <- parseStringLit' endWith return $ "'" ++ cs) <|> (do char '\\' c <- parseEscapeChar <|> parseAsciiHexChar <|> parseUnicodeHexChar <|> char '\r' <|> char '\n' cs <- parseStringLit' endWith if c == '\r' || c == '\n' then return (c:dropWhile isWhitespace cs) else return (c:cs)) <|> liftM2 (:) anyChar (parseStringLit' endWith) parseStringLit:: Stream s Identity Char => ExpressionParser s parseStringLit = do pos <- getPosition -- parseStringLit' takes as an argument the quote-character that opened the -- string. str <- lexeme $ (char '\'' >>= parseStringLit') <|> (char '\"' >>= parseStringLit') -- CRUCIAL: Parsec.Token parsers expect to find their token on the first -- character, and read whitespaces beyond their tokens. Without 'lexeme' -- above, expressions like: -- var s = "string" ; -- do not parse. return $ StringLit pos str --}}} parseRegexpLit:: Stream s Identity Char => ExpressionParser s parseRegexpLit = do let parseFlags = do flags <- many (oneOf "mgi") return $ \f -> f ('g' `elem` flags) ('i' `elem` flags) let parseEscape :: Stream s Identity Char => Parser s Char parseEscape = char '\\' >> anyChar let parseChar :: Stream s Identity Char => Parser s Char parseChar = noneOf "/" let parseRe = (char '/' >> return "") <|> (do char '\\' ch <- anyChar -- TODO: too lenient rest <- parseRe return ('\\':ch:rest)) <|> liftM2 (:) anyChar parseRe pos <- getPosition char '/' notFollowedBy $ char '/' pat <- parseRe --many1 parseChar flags <- parseFlags spaces -- crucial for Parsec.Token parsers return $ flags (RegexpLit pos pat) parseObjectLit:: Stream s Identity Char => ExpressionParser s parseObjectLit = let parseProp = do -- Parses a string, identifier or integer as the property name. I -- apologize for the abstruse style, but it really does make the code -- much shorter. name <- liftM (\(StringLit p s) -> PropString p s) parseStringLit <|> liftM2 PropId getPosition identifier <|> liftM2 PropNum getPosition (parseNumber >>= toInt) colon val <- assignExpr return (name,val) toInt eid = case eid of Left i -> return $ fromIntegral i -- Note, the spec actually allows floats in property names. -- This is left for legacy reasons and will be fixed in 1.0 Right d-> unexpected "Floating point number in property name" in do pos <- getPosition props <- braces (parseProp `sepEndBy` comma) "object literal" return $ ObjectLit pos props --{{{ Parsing numbers. From pg. 17-18 of ECMA-262. hex :: Stream s Identity Char => Parser s (Either Int Double) hex = do s <- hexIntLit Left <$> wrapReadS Numeric.readHex s decimal :: Stream s Identity Char => Parser s (Either Int Double) decimal = do (s, i) <- decLit if i then Left <$> wrapReadS readDec s else Right <$> wrapReadS readFloat s wrapReadS :: ReadS a -> String -> Parser s a wrapReadS r s = case r s of [(a, "")] -> return a _ -> fail "Bad parse: could not convert a string to a Haskell value" parseNumber:: Stream s Identity Char => Parser s (Either Int Double) parseNumber = hex <|> decimal parseNumLit:: Stream s Identity Char => ExpressionParser s parseNumLit = do pos <- getPosition eid <- lexeme $ parseNumber notFollowedBy identifierStart "whitespace" return $ case eid of Left i -> IntLit pos i Right d-> NumLit pos d ------------------------------------------------------------------------------ -- Position Helper ------------------------------------------------------------------------------ withPos cstr p = do { pos <- getPosition; e <- p; return $ cstr pos e } ------------------------------------------------------------------------------- -- Compound Expression Parsers ------------------------------------------------------------------------------- dotRef e = (reservedOp "." >> withPos cstr identifier) "property.ref" where cstr pos = DotRef pos e funcApp e = parens (withPos cstr (assignExpr `sepBy` comma)) "(function application)" where cstr pos = CallExpr pos e bracketRef e = brackets (withPos cstr parseExpression) "[property-ref]" where cstr pos = BracketRef pos e ------------------------------------------------------------------------------- -- Expression Parsers ------------------------------------------------------------------------------- parseParenExpr:: Stream s Identity Char => ExpressionParser s parseParenExpr = parens parseListExpr -- everything above expect functions parseExprForNew :: Stream s Identity Char => ExpressionParser s parseExprForNew = parseThisRef <|> parseNullLit <|> parseBoolLit <|> parseStringLit <|> parseArrayLit <|> parseParenExpr <|> parseNewExpr <|> parseNumLit <|> parseRegexpLit <|> parseObjectLit <|> parseVarRef -- all the expression parsers defined above parseSimpleExpr' :: Stream s Identity Char => ExpressionParser s parseSimpleExpr' = parseThisRef <|> parseNullLit <|> parseBoolLit <|> parseStringLit <|> parseArrayLit <|> parseParenExpr <|> parseFuncExpr <|> parseNumLit <|> parseRegexpLit <|> parseObjectLit <|> parseVarRef parseNewExpr :: Stream s Identity Char => ExpressionParser s parseNewExpr = (do pos <- getPosition reserved "new" constructor <- parseSimpleExprForNew Nothing -- right-associativity arguments <- try (parens (assignExpr `sepBy` comma)) <|> return [] return (NewExpr pos constructor arguments)) <|> parseSimpleExpr' parseSimpleExpr (Just e) = ((dotRef e <|> funcApp e <|> bracketRef e) >>= parseSimpleExpr . Just) <|> return e parseSimpleExpr Nothing = do e <- parseNewExpr "expression (3)" parseSimpleExpr (Just e) parseSimpleExprForNew :: Stream s Identity Char =>(Maybe ParsedExpression) -> ExpressionParser s parseSimpleExprForNew (Just e) = ((dotRef e <|> bracketRef e) >>= parseSimpleExprForNew . Just) <|> return e parseSimpleExprForNew Nothing = do e <- parseNewExpr "expression (3)" parseSimpleExprForNew (Just e) --}}} makeInfixExpr str constr = Infix parser AssocLeft where parser:: Stream s Identity Char => Parser s (Expression SourcePos -> Expression SourcePos -> Expression SourcePos) parser = do pos <- getPosition reservedOp str return (InfixExpr pos constr) -- points-free, returns a function -- apparently, expression tables can't handle immediately-nested prefixes parsePrefixedExpr :: Stream s Identity Char => ExpressionParser s parsePrefixedExpr = do pos <- getPosition op <- optionMaybe $ (reservedOp "!" >> return PrefixLNot) <|> (reservedOp "~" >> return PrefixBNot) <|> (try (lexeme $ char '-' >> notFollowedBy (char '-')) >> return PrefixMinus) <|> (try (lexeme $ char '+' >> notFollowedBy (char '+')) >> return PrefixPlus) <|> (reserved "typeof" >> return PrefixTypeof) <|> (reserved "void" >> return PrefixVoid) <|> (reserved "delete" >> return PrefixDelete) case op of Nothing -> unaryAssignExpr Just op -> do innerExpr <- parsePrefixedExpr return (PrefixExpr pos op innerExpr) exprTable:: Stream s Identity Char => [[Operator s ParserState Identity ParsedExpression]] exprTable = [ [ makeInfixExpr "*" OpMul , makeInfixExpr "/" OpDiv , makeInfixExpr "%" OpMod ] , [ makeInfixExpr "+" OpAdd , makeInfixExpr "-" OpSub ] , [ makeInfixExpr "<<" OpLShift , makeInfixExpr ">>" OpSpRShift , makeInfixExpr ">>>" OpZfRShift ] , [ makeInfixExpr "<" OpLT , makeInfixExpr "<=" OpLEq , makeInfixExpr ">" OpGT , makeInfixExpr ">=" OpGEq , makeInfixExpr "instanceof" OpInstanceof , makeInfixExpr "in" OpIn ] , [ makeInfixExpr "==" OpEq , makeInfixExpr "!=" OpNEq , makeInfixExpr "===" OpStrictEq , makeInfixExpr "!==" OpStrictNEq ] , [ makeInfixExpr "&" OpBAnd ] , [ makeInfixExpr "^" OpBXor ] , [ makeInfixExpr "|" OpBOr ] , [ makeInfixExpr "&&" OpLAnd ] , [ makeInfixExpr "||" OpLOr ] ] parseExpression' :: Stream s Identity Char => ExpressionParser s parseExpression' = buildExpressionParser exprTable parsePrefixedExpr "simple expression" asLValue :: Stream s Identity Char => SourcePos -> Expression SourcePos -> Parser s (LValue SourcePos) asLValue p' e = case e of VarRef p (Id _ x) -> return (LVar p x) DotRef p e (Id _ x) -> return (LDot p e x) BracketRef p e1 e2 -> return (LBracket p e1 e2) otherwise -> fail $ "expected a left-value at " ++ show p' lvalue :: Stream s Identity Char => Parser s (LValue SourcePos) lvalue = do p <- getPosition e <- parseSimpleExpr Nothing asLValue p e unaryAssignExpr :: Stream s Identity Char => ExpressionParser s unaryAssignExpr = do p <- getPosition let prefixInc = do reservedOp "++" liftM (UnaryAssignExpr p PrefixInc) lvalue let prefixDec = do reservedOp "--" liftM (UnaryAssignExpr p PrefixDec) lvalue let postfixInc e = do reservedOp "++" liftM (UnaryAssignExpr p PostfixInc) (asLValue p e) let postfixDec e = do reservedOp "--" liftM (UnaryAssignExpr p PostfixDec) (asLValue p e) let other = do e <- parseSimpleExpr Nothing postfixInc e <|> postfixDec e <|> return e prefixInc <|> prefixDec <|> other parseTernaryExpr':: Stream s Identity Char => Parser s (ParsedExpression,ParsedExpression) parseTernaryExpr' = do reservedOp "?" l <- assignExpr colon r <- assignExpr return (l,r) parseTernaryExpr:: Stream s Identity Char => ExpressionParser s parseTernaryExpr = do e <- parseExpression' e' <- optionMaybe parseTernaryExpr' case e' of Nothing -> return e Just (l,r) -> do p <- getPosition return $ CondExpr p e l r assignOp :: Stream s Identity Char => Parser s AssignOp assignOp = (reservedOp "=" >> return OpAssign) <|>(reservedOp "+=" >> return OpAssignAdd) <|>(reservedOp "-=" >> return OpAssignSub) <|>(reservedOp "*=" >> return OpAssignMul) <|>(reservedOp "/=" >> return OpAssignDiv) <|>(reservedOp "%=" >> return OpAssignMod) <|>(reservedOp "<<=" >> return OpAssignLShift) <|>(reservedOp ">>=" >> return OpAssignSpRShift) <|>(reservedOp ">>>=" >> return OpAssignZfRShift) <|>(reservedOp "&=" >> return OpAssignBAnd) <|>(reservedOp "^=" >> return OpAssignBXor) <|>(reservedOp "|=" >> return OpAssignBOr) assignExpr :: Stream s Identity Char => ExpressionParser s assignExpr = do p <- getPosition lhs <- parseTernaryExpr let assign = do op <- assignOp lhs <- asLValue p lhs rhs <- assignExpr return (AssignExpr p op lhs rhs) assign <|> return lhs parseExpression:: Stream s Identity Char => ExpressionParser s parseExpression = parseListExpr -- | A parser that parses ECMAScript expressions expression :: Stream s Identity Char => Parser s (Expression SourcePos) expression = parseExpression parseListExpr :: Stream s Identity Char => ExpressionParser s parseListExpr = assignExpr `sepBy1` comma >>= \exprs -> case exprs of [expr] -> return expr es -> liftM2 ListExpr getPosition (return es) parseScript:: Stream s Identity Char => Parser s (JavaScript SourcePos) parseScript = do whiteSpace liftM2 Script getPosition (parseStatement `sepBy` whiteSpace) -- | A parser that parses an ECMAScript program. program :: Stream s Identity Char => Parser s (JavaScript SourcePos) program = parseScript -- | Parse from a stream given a parser, same as 'Text.Parsec.parse' -- in Parsec. We can use this to parse expressions or statements alone, -- not just whole programs. parse :: Stream s Identity Char => Parser s a -- ^ The parser to use -> SourceName -- ^ Name of the source file -> s -- ^ the stream to parse, usually a 'String' -> Either ParseError a parse p = runParser p initialParserState -- | A convenience function that takes a 'String' and tries to parse -- it as an ECMAScript program: -- -- > parseFromString = parse program "" parseFromString :: String -- ^ JavaScript source to parse -> Either ParseError (JavaScript SourcePos) parseFromString = parse program "" -- | A convenience function that takes a filename and tries to parse -- the file contents an ECMAScript program, it fails with an error -- message if it can't. parseFromFile :: (Error e, MonadIO m, MonadError e m) => String -- ^ file name -> m (JavaScript SourcePos) parseFromFile fname = liftIO (readFile fname) >>= \source -> case parse program fname source of Left err -> throwError $ strMsg $ show err Right js -> return js -- | Read a JavaScript program from file an parse it into a list of -- statements parseJavaScriptFromFile :: MonadIO m => String -- ^ file name -> m [Statement SourcePos] parseJavaScriptFromFile filename = do chars <- liftIO $ readFile filename case parse parseScript filename chars of Left err -> fail (show err) Right (Script _ stmts) -> return stmts -- | Parse a JavaScript program from a string parseScriptFromString :: String -- ^ source file name -> String -- ^ JavaScript source to parse -> Either ParseError (JavaScript SourcePos) parseScriptFromString = parse parseScript -- | Parse a JavaScript source string into a list of statements parseString :: String -- ^ JavaScript source -> [Statement SourcePos] parseString str = case parse parseScript "" str of Left err -> error (show err) Right (Script _ stmts) -> stmts