├── .github └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── Setup.hs ├── changelog.md ├── default.nix ├── language-js.cabal ├── makefile ├── readme.md ├── shell.nix ├── src └── Language │ └── JS │ ├── Common.hs │ ├── Operators.hs │ ├── Parser.hs │ └── Types.hs └── t ├── Spec.hs └── Test ├── AssignmentAndOperation.hs ├── Classes.hs ├── Exports.hs ├── Functions.hs ├── Helpers.hs ├── If.hs ├── Imports.hs ├── Iteration.hs ├── MemberExpression.hs ├── Switch.hs ├── Try.hs └── Variables.hs /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Haskell CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-haskell@v1 17 | with: 18 | ghc-version: '8.10.3' 19 | cabal-version: '3.2' 20 | 21 | - name: Cache 22 | uses: actions/cache@v1 23 | env: 24 | cache-name: cache-cabal 25 | with: 26 | path: ~/.cabal 27 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }} 28 | restore-keys: | 29 | ${{ runner.os }}-build-${{ env.cache-name }}- 30 | ${{ runner.os }}-build- 31 | ${{ runner.os }}- 32 | 33 | - name: Install dependencies 34 | run: | 35 | cabal update 36 | cabal build --only-dependencies --enable-tests --enable-benchmarks 37 | - name: Build 38 | run: cabal build --enable-tests --enable-benchmarks all 39 | - name: Run tests 40 | run: cabal test all 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | Gemfile 4 | Guardfile 5 | .#* 6 | *.lock 7 | dist-newstyle/ 8 | cabal.project.local -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Bruno Dias (c) 2018 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Author name here nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 2 | 3 | - fixed parsing escaped strings. 4 | - now using cabal with nix. 5 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, hspec, lib, parsec }: 2 | mkDerivation { 3 | pname = "language-js"; 4 | version = "0.3.0"; 5 | src = ./.; 6 | libraryHaskellDepends = [ base parsec ]; 7 | testHaskellDepends = [ base hspec parsec ]; 8 | description = "javascript parser for es6 and es7"; 9 | license = lib.licenses.mit; 10 | } 11 | -------------------------------------------------------------------------------- /language-js.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: language-js 3 | version: 0.3.0 4 | 5 | synopsis: javascript parser for es6 and es7. 6 | description: a javascript parser for es6 and es7. 7 | 8 | bug-reports: https://github.com/diasbruno/language-js/issues 9 | 10 | category: Language 11 | license: MIT 12 | license-file: LICENSE 13 | author: Bruno Dias 14 | maintainer: dias.h.bruno@gmail.com 15 | 16 | extra-source-files: 17 | changelog.md 18 | readme.md 19 | 20 | library 21 | default-language: Haskell2010 22 | build-depends: base <5 23 | , parsec >=3.1 && <3.2 24 | hs-source-dirs: src 25 | exposed-modules: Language.JS.Common 26 | , Language.JS.Operators 27 | , Language.JS.Parser 28 | , Language.JS.Types 29 | 30 | test-suite tests 31 | default-language: Haskell2010 32 | type: exitcode-stdio-1.0 33 | hs-source-dirs: t 34 | main-is: Spec.hs 35 | other-modules: Test.AssignmentAndOperation 36 | , Test.Classes 37 | , Test.Exports 38 | , Test.Functions 39 | , Test.Helpers 40 | , Test.If 41 | , Test.Imports 42 | , Test.Iteration 43 | , Test.MemberExpression 44 | , Test.Switch 45 | , Test.Try 46 | , Test.Variables 47 | build-depends: base 48 | , hspec 49 | , parsec 50 | , language-js 51 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | configure-dev: 2 | cabal v2-configure --enable-tests 3 | 4 | build: 5 | cabal v2-build 6 | 7 | tests: 8 | cabal v2-test 9 | 10 | release: 11 | cabal v2-haddock --haddock-for-hackage --enable-doc 12 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # language-js ![example workflow](https://github.com/diasbruno/language-js/actions/workflows/tests.yml/badge.svg) 2 | 3 | javascript parser for es6 and es7. 4 | 5 | NOTE: work in progress... 6 | 7 | ## references 8 | 9 | - [ECMAScript® 2017 Language Specification (ECMA-262, 8th edition, June 2017)](http://www.ecma-international.org/ecma-262/8.0/index.html) 10 | - [MDN Operator precedence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) 11 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: 2 | 3 | let 4 | 5 | inherit (nixpkgs) pkgs; 6 | 7 | f = import ./default.nix; 8 | 9 | haskellPackages = if compiler == "default" 10 | then pkgs.haskellPackages 11 | else pkgs.haskell.packages.${compiler}; 12 | 13 | variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id; 14 | 15 | drv = variant (haskellPackages.callPackage f {}); 16 | 17 | in (if pkgs.lib.inNixShell then drv.env else drv) 18 | -------------------------------------------------------------------------------- /src/Language/JS/Common.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | module Language.JS.Common where 3 | 4 | import qualified Text.Parsec as P 5 | 6 | parens, braces, angles, brackets :: P.Stream s m Char => 7 | P.ParsecT s u m a -> P.ParsecT s u m a 8 | parens = P.between (P.string "(") (P.string ")") 9 | braces = P.between (P.string "{") (P.string "}") 10 | angles = P.between (P.string "<") (P.string ">") 11 | brackets = P.between (P.string "[") (P.string "]") 12 | 13 | semi, comma, dot, colon :: P.Stream s m Char => P.ParsecT s u m String 14 | semi = P.string ";" 15 | comma = P.string "," 16 | dot = P.string "." 17 | colon = P.string ":" 18 | 19 | dotSep, commaSep, semiSep :: P.Stream s m Char => P.ParsecT s u m a -> P.ParsecT s u m [a] 20 | dotSep p = P.sepBy1 p dot 21 | commaSep p = P.sepBy p comma 22 | semiSep p = P.sepBy p semi 23 | 24 | commaSep1, semiSep1 :: P.Stream s m Char => P.ParsecT s u m a -> P.ParsecT s u m [a] 25 | commaSep1 p = P.sepBy1 p comma 26 | semiSep1 p = P.sepBy1 p semi 27 | 28 | whiteSpace :: P.Stream s m Char => P.ParsecT s u m Char 29 | whiteSpace = P.oneOf " \n\t" 30 | 31 | whiteSpaces :: P.Stream s m Char => P.ParsecT s u m String 32 | whiteSpaces = P.many whiteSpace 33 | 34 | betweenSpaces :: P.Stream s m Char => P.ParsecT s u m a -> P.ParsecT s u m a 35 | betweenSpaces p = whiteSpaces *> p <* whiteSpaces 36 | 37 | lexeme :: P.Stream s m Char => P.ParsecT s u m a -> P.ParsecT s u m a 38 | lexeme p = p <* whiteSpaces 39 | 40 | keywordB :: P.Stream s m Char => String -> P.ParsecT s u m String 41 | keywordB = P.try . lexeme . P.string 42 | 43 | -- | reserved words 44 | reservedWords :: [String] 45 | reservedWords = ["async", "await", "yield", "delete", "void", "typeof", 46 | "instanceof", "new", "debugger", 47 | "var", "let", "const", 48 | "switch", "case", "break", "default", 49 | "try", "catch", "finally", 50 | "for", "while", "do", "in", "of", "continue", 51 | "if", "else", 52 | "with", 53 | "function", "return", 54 | "class", "extends", "static", "get", "set", "super", 55 | "import", "from", "export", "as" 56 | ] 57 | -------------------------------------------------------------------------------- /src/Language/JS/Operators.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | module Language.JS.Operators where 3 | 4 | import qualified Text.Parsec.Expr as P 5 | import qualified Text.Parsec as P 6 | import Language.JS.Types 7 | import Language.JS.Common 8 | 9 | opNotFollowedBy op p = keywordB op <* P.notFollowedBy p 10 | 11 | table :: P.Stream s m Char => Bool -> P.ParsecT s u m Expression -> [[P.Operator s u m Expression]] 12 | table withComma exp' = [[ P.Postfix (UnaryUpdate <$> keywordB "++" <*> pure False ) -- 17 13 | , P.Postfix (UnaryUpdate <$> keywordB "--" <*> pure False )] 14 | ,[ P.Prefix (Unary <$> P.try (opNotFollowedBy "!" (P.char '=')) ) -- 16 15 | , P.Prefix (Unary <$> keywordB "~" ) 16 | , P.Prefix (Unary <$> P.try (opNotFollowedBy "+" (P.oneOf "+=")) ) 17 | , P.Prefix (Unary <$> P.try (opNotFollowedBy "-" (P.oneOf "-=")) ) 18 | , P.Prefix (UnaryUpdate <$> keywordB "++" <*> pure True ) 19 | , P.Prefix (UnaryUpdate <$> keywordB "--" <*> pure True ) 20 | , P.Prefix (Unary <$> keywordB "typeof" ) 21 | , P.Prefix (Unary <$> keywordB "void" ) 22 | , P.Prefix (Unary <$> keywordB "delete" ) 23 | , P.Prefix (Unary <$> keywordB "await" )] 24 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "**" (P.char '=')) ) P.AssocRight] -- 15 25 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "*" (P.oneOf "*=")) ) P.AssocLeft -- 14 26 | , P.Infix (Operation <$> P.try (opNotFollowedBy "/" (P.oneOf "*=")) ) P.AssocLeft 27 | , P.Infix (Operation <$> P.try (opNotFollowedBy "%" (P.oneOf "/=")) ) P.AssocLeft] 28 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "+" (P.oneOf "+=")) ) P.AssocLeft -- 13 29 | , P.Infix (Operation <$> P.try (opNotFollowedBy "-" (P.oneOf "-=")) ) P.AssocLeft] 30 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "<<" (P.oneOf "=")) ) P.AssocLeft -- 12 31 | , P.Infix (Operation <$> P.try (opNotFollowedBy ">>" (P.oneOf ">=")) ) P.AssocLeft 32 | , P.Infix (Operation <$> P.try (opNotFollowedBy ">>>" (P.char '=')) ) P.AssocLeft] 33 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "<" (P.oneOf "<=")) ) P.AssocLeft -- 11 34 | , P.Infix (Operation <$> keywordB "<=" ) P.AssocLeft 35 | , P.Infix (Operation <$> P.try (opNotFollowedBy ">" (P.oneOf ">=")) ) P.AssocLeft 36 | , P.Infix (Operation <$> keywordB ">=" ) P.AssocLeft 37 | , P.Infix (Operation <$> P.try (opNotFollowedBy "in" (P.char 's')) ) P.AssocLeft 38 | , P.Infix (Operation <$> keywordB "instanceof" ) P.AssocLeft] 39 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "==" (P.char '=')) ) P.AssocLeft -- 10 40 | , P.Infix (Operation <$> P.try (opNotFollowedBy "!=" (P.char '=')) ) P.AssocLeft 41 | , P.Infix (Operation <$> keywordB "===" ) P.AssocLeft 42 | , P.Infix (Operation <$> keywordB "!==" ) P.AssocLeft] 43 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "&" (P.oneOf "&=")) ) P.AssocLeft] -- 9 44 | ,[ P.Infix (Operation <$> keywordB "^" ) P.AssocLeft] -- 8 45 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "|" (P.oneOf "|=")) ) P.AssocLeft] -- 7 46 | ,[ P.Infix (Operation <$> P.try (opNotFollowedBy "&&" (P.char '=')) ) P.AssocLeft] -- 6 47 | ,[ P.Infix (Operation <$> keywordB "||" ) P.AssocLeft] -- 5 48 | ,[ P.Infix (flip Condition <$> (keywordB "?" *> lexeme exp' <* keywordB ":")) P.AssocNone] -- 4 49 | ,[ P.Infix (Assignment <$> P.try (opNotFollowedBy "=" (P.oneOf ">=")) ) P.AssocRight -- 3 50 | , P.Infix (Assignment <$> keywordB "+=" ) P.AssocRight 51 | , P.Infix (Assignment <$> keywordB "-=" ) P.AssocRight 52 | , P.Infix (Assignment <$> keywordB "**=" ) P.AssocRight 53 | , P.Infix (Assignment <$> keywordB "*=" ) P.AssocRight 54 | , P.Infix (Assignment <$> keywordB "/=" ) P.AssocRight 55 | , P.Infix (Assignment <$> keywordB "%=" ) P.AssocRight 56 | , P.Infix (Assignment <$> keywordB "<<=" ) P.AssocRight 57 | , P.Infix (Assignment <$> keywordB ">>=" ) P.AssocRight 58 | , P.Infix (Assignment <$> keywordB ">>>=" ) P.AssocRight 59 | , P.Infix (Assignment <$> keywordB "&=" ) P.AssocRight 60 | , P.Infix (Assignment <$> keywordB "^=" ) P.AssocRight 61 | , P.Infix (Assignment <$> keywordB "|=" ) P.AssocRight] 62 | ,[ P.Prefix (Unary <$> keywordB "yield" )] -- 2 63 | ,if withComma then ([ P.Infix (Comma <$ keywordB ",") P.AssocLeft]) else [] -- 1 64 | ] 65 | 66 | -- withComma = isLiteral (used when parsing arrays, objects and parenthesis expression) 67 | operationExpression withComma exp' p = P.buildExpressionParser (table withComma exp') (lexeme p) P. "[operations]" 68 | -------------------------------------------------------------------------------- /src/Language/JS/Parser.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | module Language.JS.Parser where 3 | 4 | import Control.Applicative ((<|>)) 5 | import Control.Monad (liftM2) 6 | import qualified Text.Parsec as P 7 | import Language.JS.Types 8 | import Language.JS.Common 9 | import Language.JS.Operators 10 | 11 | -- | Identifier name. 12 | identifierName = 13 | liftM2 (++) (P.many (P.oneOf "_$")) (P.many1 P.alphaNum) 14 | 15 | -- | Parse identifier (no reserved words). 16 | identifier = 17 | P.try (LI <$> (do i <- identifierName 18 | case i `elem` reservedWords of 19 | True -> P.unexpected "reserved word" 20 | _ -> return i)) P. "[identifier]" 21 | 22 | -- | Parse numeric literal. 23 | -- Here we don't distinguish between kinds. 24 | numericLiteral = 25 | LN <$> (binN <|> octN <|> hexN <|> decN) 26 | P. "[number-literal]" 27 | where prefix p = do 28 | i <- P.char '0' 29 | x <- P.oneOf p 30 | return [i, x] 31 | combine = liftM2 (++) 32 | hexN = combine (P.try (prefix "xX")) (P.many1 (P.oneOf "0123456789abcdefABCDEF")) 33 | octN = combine (P.try (prefix "oO")) (P.many1 (P.oneOf "01234567")) 34 | binN = combine (P.try (prefix "bB")) (P.many1 (P.oneOf "01")) 35 | decN = do 36 | lead <- P.many1 P.digit 37 | fraction <- liftM2 (:) (P.char '.') (P.many P.digit) <|> return "" 38 | expo <- expoN 39 | return (lead ++ fraction ++ expo) 40 | expoN = liftM2 (:) (P.oneOf "eE") (P.many P.digit) <|> return "" 41 | 42 | -- | Parse boolean literal. 43 | booleanLiteral = 44 | P.try (boolA "true" <|> boolA "false") 45 | P. "[boolean]" 46 | where boolA = fmap (LB . toHask) . keywordB 47 | toHask s | s == "true" = True 48 | | otherwise = False 49 | 50 | -- | this identifier 51 | thisIdent = 52 | const LThis <$> keywordB "this" P. "[this]" 53 | 54 | -- | null identifier 55 | nullIdent = 56 | const LNull <$> keywordB "null" P. "[null]" 57 | 58 | -- | Parse string literal. 59 | stringLiteral = 60 | buildExpression '"' 61 | <|> buildExpression '\'' 62 | <|> LTS <$> (P.char '`' *> templateString "" []) 63 | P. "[string-literal]" 64 | where 65 | buildExpression wc = do 66 | P.char wc 67 | content <- P.many (escaped <|> P.noneOf [wc, '\n']) 68 | P.char wc 69 | return $ LS content 70 | escaped = do 71 | P.char '\\' 72 | P.choice $ zipWith escapedChar codes replacements 73 | escapedChar code replacement = do 74 | P.char code 75 | return replacement 76 | codes = ['b', 'n', 'f', 'r', 't', '\\', '\"', '\''] 77 | replacements = ['\b', '\n', '\f', '\r', '\t', '\\', '\"', '\''] 78 | 79 | 80 | 81 | -- | Parse template strings. 82 | templateString str ls = (do 83 | t <- P.anyToken 84 | case t of 85 | '$' -> do 86 | e <- TExpression <$> braces (expressionNonEmpty True) 87 | let s' = if length str > 0 then [TString str, e] else [e] 88 | templateString "" (ls ++ s') 89 | '`' -> return (ls ++ (if length str > 0 then [TString str] else [])) 90 | _ -> templateString (str ++ [t]) ls) <|> return ls 91 | 92 | -- | Parse regular expression literal. 93 | regexLiteral = 94 | let re = (P.string "/" >> return "") <|> 95 | (do es <- P.char '\\' -- escaped char 96 | t <- P.anyToken 97 | n <- re 98 | return (es:t:n)) <|> 99 | (liftM2 (:) P.anyToken re) 100 | in RegExp <$> ((P.char '/') *> re) <*> P.many (P.oneOf "mgi") 101 | 102 | -- | Parse elision (aka ',' without a value on array). 103 | elision = 104 | const Elision <$> keywordB "," 105 | P. "elision" 106 | 107 | -- | Parse many items on a array declaration. 108 | arrayItems ls = 109 | (lexeme (elision <|> item) >>= \x -> arrayItems (ls ++ [x])) <|> return ls 110 | where item = checkSpread Spread (expressionNonEmpty False) <* P.optional (P.char ',') 111 | 112 | -- | Parse array literal. 113 | arrayLiteral = 114 | P.try (LA <$> brackets (betweenSpaces (arrayItems []))) 115 | P. "[array]" 116 | 117 | -- | key and/or value property pair. 118 | objectBinds = do 119 | sp <- P.optionMaybe (keywordB "...") 120 | case sp of 121 | Just _ -> OPI . Spread <$> literals 122 | Nothing -> (OPM <$> (asyncMethodDef <|> classGetSetMethodDef <|> propertyMethodDef)) <|> (do 123 | k <- identifier 124 | x <- P.try (P.lookAhead (P.oneOf ",:}")) 125 | case x of 126 | ':' -> P.try (OPKV k <$> (lexeme (P.char ':') *> lexeme (checkSpread Spread (expressionNonEmpty False)) P. "[object-value-expression]")) 127 | _ -> return (OPI k)) 128 | 129 | -- | Parse object literal. 130 | -- objectLiteral :: P.ParsecT s u m Expression 131 | objectLiteral = 132 | LO <$> lexeme (braces (P.sepBy (betweenSpaces objectBinds) (P.char ','))) 133 | P. "[object-literal]" 134 | 135 | -- | Parse parenthesis expression. 136 | parensExpression = 137 | LP <$> parens (betweenSpaces (expressionNonEmpty True)) 138 | P. "[parenthesis]" 139 | 140 | -- | Check for spread operation before parse 'p'. 141 | checkSpread ctor p = 142 | do i <- P.optionMaybe (keywordB "...") 143 | case i of 144 | Just _ -> ctor <$> p 145 | Nothing -> p 146 | 147 | -- | Parse used by function declarations. 148 | formalParameter = 149 | betweenSpaces bindExpression 150 | P. "[formal-parameters]" 151 | 152 | -- | Parse function declaration 153 | functionDeclaration = 154 | keywordB "function" *> 155 | (Function <$> lexeme (P.optionMaybe identifier) 156 | <*> lexeme (parens (commaSep formalParameter)) 157 | <*> lexeme (SBlock <$> braces (betweenSpaces (P.many (lexeme statements))))) 158 | P. "[function]" 159 | 160 | -- | Prase arrow function declaration. 161 | arrowFunctionDeclaration = 162 | P.try (Arrow <$> (lexeme (parens manyParams <|> singleParam) <* keywordB "=>") 163 | <*> blockOrStatements) 164 | P. "[arrow-function]" 165 | where singleParam = Left <$> bindVar 166 | manyParams = Right <$> commaSep formalParameter 167 | 168 | -- | Parse any kind of funcion declaration (function or arrow function). 169 | functionExpression = 170 | arrowFunctionDeclaration <|> functionDeclaration 171 | 172 | -- | Parse property method of a class or object literal. 173 | propertyMethodDef = 174 | P.try (PropertyMethod <$> lexeme identifier 175 | <*> lexeme (parens (commaSep formalParameter)) 176 | <*> (SBlock <$> braces (whiteSpaces *> P.many (lexeme statements)))) 177 | P. "[class-method-definition]" 178 | 179 | -- | Parse a static property of a class. 180 | classStaticDef = 181 | lexeme (keywordB "static") *> 182 | (ClassStatic <$> (propertyMethodDef <|> classPropertyDef)) 183 | 184 | -- | Parse a getter or setter method. 185 | classGetSetMethodDef = 186 | (keywordB "set" *> (ClassSetMethod <$> propertyMethodDef)) <|> 187 | (keywordB "get" *> (ClassGetMethod <$> propertyMethodDef)) 188 | P. "[class-get-set-definition]" 189 | 190 | -- | Check for a async property method. 191 | asyncMethodDef = 192 | keywordB "async" *> (Async <$> propertyMethodDef) 193 | P. "[async-definition]" 194 | 195 | -- | Parse a class property definition. 196 | classPropertyDef = 197 | P.try (ClassProperty <$> (lexeme identifier <* P.char '=' <* whiteSpaces) 198 | <*> lexeme (expressionNonEmpty False)) 199 | P. "[class-property]" 200 | 201 | -- | Parse a class declaration. 202 | classDeclaration = 203 | keywordB "class" *> (Class <$> (lexeme (P.optionMaybe identifier)) 204 | <*> P.optionMaybe (keywordB "extends" *> lexeme identifier) 205 | <*> (SBlock <$> braces (whiteSpaces *> classBlock))) 206 | P. "[class-expression]" 207 | where classBlock = P.many (lexeme (toStatement <$> classBlockDecls)) 208 | classBlockDecls = (classPropertyDef 209 | <|> asyncMethodDef 210 | <|> classStaticDef 211 | <|> classGetSetMethodDef 212 | <|> propertyMethodDef) 213 | 214 | -- | Dot member. 215 | dotMember p = 216 | Dot p <$> (lexeme (P.char '.') *> identifier) 217 | P. "[dot-expression]" 218 | 219 | -- | Array like accessor. 220 | accessor p = 221 | Acc p <$> brackets (betweenSpaces (expressionNonEmpty True)) 222 | P. "[array-expression]" 223 | 224 | -- | Function call. 225 | functionCall p = 226 | FCall p <$> lexeme (parens (commaSep (whiteSpaces *> expressionNonEmpty False))) 227 | P. "[function-call]" 228 | 229 | -- | new 230 | newIdent = 231 | const Nothing <$> keywordB "new" P. "[new]" 232 | 233 | -- | Parse member expression. 234 | memberExpression (Just p) = 235 | (do dt <- (functionCall p <|> dotMember p <|> accessor p) P. "[member-expression]" 236 | memberExpression (Just dt)) <|> return p 237 | memberExpression Nothing = 238 | (New <$> expressions) P. "[new-expression]" 239 | 240 | -- | Parse literals. 241 | literals = 242 | thisIdent 243 | <|> nullIdent 244 | <|> booleanLiteral 245 | <|> stringLiteral 246 | <|> arrayLiteral 247 | <|> objectLiteral 248 | <|> regexLiteral 249 | <|> numericLiteral 250 | <|> identifier 251 | 252 | -- | Parse primary expressions. 253 | primaryExpression = 254 | literals 255 | <|> functionDeclaration 256 | <|> classDeclaration 257 | <|> parensExpression 258 | 259 | -- | Check for maybe semi. 260 | -- TODO: There are some rules for expression termination...need to check that. 261 | maybeSemi = 262 | P.optional (P.char ';') 263 | 264 | -- | Parse a empty expression. 265 | emptyExpression = 266 | (const Empty) <$> (P.char ';') 267 | P. "[empty-expressions]" 268 | 269 | -- | Parse rules for left hand side expression. 270 | leftHandSideExpression = 271 | (newIdent <|> (Just <$> lexeme primaryExpression)) >>= memberExpression 272 | P. "left-hand-side-expression" 273 | 274 | -- | Parse expressions. 275 | expressions = 276 | emptyExpression <|> expressionNonEmpty True P. "[expressions]" 277 | 278 | -- | Parse single line comment. 279 | comment = 280 | P.try (Comment <$> (P.string "//" *> P.many (P.satisfy (\c -> c /= '\n')))) 281 | 282 | -- | Parse multiline comment. 283 | multilineComment = 284 | P.try (MultilineComment <$> (P.between (P.string "/*") (P.string "*/") (P.many P.anyToken))) 285 | 286 | -- | Parse comment like an expression. 287 | commentExpression = 288 | comment <|> multilineComment 289 | 290 | -- | Parse expressions excluding emptyExpression. 291 | expressionNonEmpty notComma = 292 | commentExpression 293 | <|> functionExpression 294 | <|> classDeclaration 295 | <|> (operationExpression notComma (expressionNonEmpty notComma) leftHandSideExpression) 296 | <|> primaryExpression 297 | P. "[non-empty-expressions]" 298 | 299 | -- | Convert a expression into a statement. 300 | toStatement :: Expression -> Statement 301 | toStatement (Function (Just (LI a)) b c) = SF a b c 302 | toStatement (Class (Just (LI a)) b c) = SC a b c 303 | toStatement a = SExp a 304 | 305 | -- Statements 306 | 307 | -- | Parse import namespace clauses. 308 | importNamespaceClause = 309 | Namespace <$> ((keywordB "*" *> keywordB "as") *> identifier) 310 | 311 | -- | Parse import bind clauses. 312 | importBindClause = 313 | BindNames <$> braces (commaSep (betweenSpaces identifier)) 314 | 315 | -- | Parse default clauses. 316 | importDefaultNameClause = 317 | DefaultName <$> lexeme identifier 318 | 319 | -- | Parse import clauses excluding namespace clause. 320 | importManyClauses = 321 | commaSep1 (whiteSpaces *> (importBindClause <|> importDefaultNameClause)) 322 | 323 | -- | Parse all import clauses. 324 | importClauses = 325 | (Left <$> importNamespaceClause) <|> 326 | (Right <$> importManyClauses) 327 | 328 | -- | Parse import file statement. 329 | importFileStatement = 330 | SImportFile <$> lexeme stringLiteral 331 | 332 | -- | Parse import statement. 333 | importStatement = 334 | SImport <$> (lexeme importClauses <* keywordB "from") <*> lexeme stringLiteral 335 | 336 | -- | Parse import statements. 337 | importStatements = 338 | keywordB "import" *> (importStatement <|> importFileStatement) 339 | P. "[import-statement]" 340 | 341 | reexportStatement = P.try (SRExport <$> (lexeme (expressionNonEmpty False) <* keywordB "from") <*> lexeme stringLiteral) 342 | exportDefaultStatement = keywordB "default" *> (SExportDefault <$> expressionNonEmpty False) 343 | exportStatement = SExport <$> statements 344 | 345 | -- | Parse export statements. 346 | exportStatements = keywordB "export" *> (reexportStatement <|> exportDefaultStatement <|> exportStatement) 347 | P. "[export-statement]" 348 | 349 | -- | Parse continue statement. 350 | continueStatement = 351 | keywordB "continue" *> (SContinue <$> (P.optionMaybe identifier)) 352 | P. "[continue-statement]" 353 | 354 | -- | Parse break statement. 355 | breakStatement = 356 | keywordB "break" *> (SBreak <$> (P.optionMaybe identifier)) 357 | P. "[break-statement]" 358 | 359 | -- | Parse block statement. 360 | blockStatement allowedStmt = 361 | SBlock <$> P.try (braces (betweenSpaces (P.many allowedStmt))) 362 | P. "[block-statement]" 363 | 364 | blockOrStatements = 365 | SBlock <$> braces (whiteSpaces *> P.many (lexeme statements)) <|> statements 366 | 367 | -- | Parse if statement. 368 | ifStatement = 369 | keywordB "if" *> (SIf <$> (lexeme parensExpression) 370 | <*> lexeme blockOrStatements 371 | <*> P.optionMaybe (keywordB "else" *> blockOrStatements)) 372 | P. "[if-statement]" 373 | 374 | -- | Parse catch part of try statement. 375 | catchBlock = 376 | keywordB "catch" *> (SCatch <$> lexeme (P.optionMaybe parensExpression) 377 | <*> blockStatement statements) 378 | P. "[try/catch-statement]" 379 | 380 | -- | Parse finally part of try statement. 381 | finallyBlock = 382 | keywordB "finally" *> (SFinally <$> (blockStatement statements)) 383 | P. "[try/catch/finally-statement]" 384 | 385 | -- | Parse try statement. 386 | tryStatement = 387 | keywordB "try" *> (STry <$> lexeme (blockStatement statements) 388 | <*> catchBlock 389 | <*> P.optionMaybe finallyBlock) 390 | P. "[try-statement]" 391 | 392 | -- | Parse throw statement. 393 | throwStatement = 394 | keywordB "throw" *> (SThrow <$> (expressionNonEmpty False)) 395 | P. "[throw-statement]" 396 | 397 | -- | Parse return statement. 398 | returnStatement = 399 | keywordB "return" *> (SReturn <$> expressions) 400 | P. "[return-statement]" 401 | 402 | bindVar = 403 | BindVar <$> lexeme identifier <*> P.optionMaybe (P.notFollowedBy (keywordB "=>") *> (lexeme (P.char '=') *> (expressionNonEmpty False))) 404 | bindPatternDecl = 405 | BindPattern <$> (lexeme (objectLiteral <|> arrayLiteral)) <*> P.optionMaybe (lexeme (P.char '=') *> (expressionNonEmpty False)) 406 | bindSpread = 407 | BindRest <$> (keywordB "..." *> leftHandSideExpression) 408 | bindExpression = 409 | (bindVar <|> bindPatternDecl <|> bindSpread) P. "[var-binds]" 410 | 411 | constVariableStatement = 412 | P.try (SVariable <$> (keywordB "const") <*> commaSep1 (betweenSpaces bindExpression)) 413 | notConstVariableStatement = 414 | P.try (SVariable <$> (keywordB "let" <|> keywordB "var") <*> commaSep1 (betweenSpaces bindExpression)) 415 | 416 | -- | Parse variable statement. 417 | variableStatement = 418 | constVariableStatement <|> notConstVariableStatement P. "[variable-statement]" 419 | 420 | -- | Parse case clause switch statement. 421 | caseClause = 422 | lexeme ((caseB <|> defaultCase) <* (P.char ':')) 423 | P. "[switch/case-expression]" 424 | where defaultCase = const DefaultCase <$> (keywordB "default") 425 | caseB = keywordB "case" *> (Case <$> literals) 426 | 427 | -- | Parse case clause switch statement. 428 | caseDeclaration = 429 | SCase <$> lexeme (P.many1 caseClause) 430 | <*> P.many (lexeme ((breakStatement <* maybeSemi) <|> statements)) 431 | 432 | -- | Parse switch statement. 433 | switchStatement = 434 | keywordB "switch" *> 435 | (SSwitch <$> lexeme parensExpression 436 | <*> braces (betweenSpaces (P.many caseDeclaration))) 437 | P. "[switch-statement]" 438 | 439 | -- | Parse debugger statement. 440 | debuggerStatement = 441 | const SDebugger <$> keywordB "debugger" 442 | P. "[debugger-statement]" 443 | 444 | -- | Parse breakable statement. 445 | -- TODO: this parser can be improved to parse vaild javascript code 446 | -- by passing to the break statement to subsequent statements. 447 | breakableStatement = 448 | blockStatement ((breakStatement <* maybeSemi) <|> statements) <|> statements 449 | 450 | -- | Parse while statement. 451 | whileStatement = 452 | keywordB "while" *> 453 | (SWhile <$> lexeme (parens (P.many1 expressions)) 454 | <*> breakableStatement) 455 | P. "[while-statement]" 456 | 457 | -- | parse do-while statement. 458 | doWhileStatement = 459 | keywordB "do" *> 460 | (SDoWhile <$> lexeme breakableStatement 461 | <*> (keywordB "while" *> parens (P.many1 expressions))) 462 | P. "[do/while-statement]" 463 | 464 | forInVStyle = 465 | P.try (ForInV <$> lexeme (keywordB "let" <|> keywordB "const" <|> keywordB "var") 466 | <*> bindExpression 467 | <*> (keywordB "in" *> (expressionNonEmpty False))) 468 | forOfVStyle = 469 | P.try (ForOfV <$> lexeme (keywordB "let" <|> keywordB "const" <|> keywordB "var") 470 | <*> bindExpression 471 | <*> (keywordB "of" *> expressionNonEmpty False )) 472 | forInStyle = 473 | P.try (ForIn <$> bindExpression <*> (keywordB "in" *> expressionNonEmpty False)) 474 | forOfStyle = 475 | P.try (ForOf <$> bindExpression <*> (keywordB "of" *> expressionNonEmpty False)) 476 | forRegularStyle = 477 | ForRegular <$> P.try (P.optionMaybe bindExpression <* (P.char ';')) 478 | <*> P.try (P.optionMaybe (expressionNonEmpty True) <* (P.char ';')) 479 | <*> P.optionMaybe (expressionNonEmpty True) 480 | forStyle = 481 | forInVStyle <|> forOfVStyle <|> forInStyle <|> forOfStyle <|> forRegularStyle P. "[for-style]" 482 | 483 | -- | Parse for statement. 484 | forStatement = 485 | SFor <$> lexeme (keywordB "for" *> (parens forStyle)) <*> breakableStatement 486 | 487 | -- | Parse iteration statements (for, white, do/while). 488 | iterationStatement = forStatement <|> whileStatement <|> doWhileStatement 489 | 490 | -- | Parse with statement. 491 | withStatement = 492 | keywordB "with" *> (SWith <$> lexeme (parens (expressionNonEmpty True)) 493 | <*> (SBlock <$> braces (whiteSpaces *> P.many (lexeme statements)) <|> statements)) 494 | P. "[with-statement]" 495 | 496 | -- | Parse labelled statement. 497 | labelledStatement = 498 | SLabel <$> P.try (lexeme (identifier <* P.char ':')) <*> statements 499 | P. "[labelled-statement]" 500 | 501 | -- | Parse statements. 502 | statements = ((blockStatement statements 503 | <|> ifStatement 504 | <|> iterationStatement 505 | <|> debuggerStatement 506 | <|> labelledStatement 507 | <|> continueStatement 508 | <|> tryStatement 509 | <|> throwStatement 510 | <|> returnStatement 511 | <|> switchStatement 512 | <|> withStatement 513 | <|> variableStatement 514 | <|> fmap toStatement expressions) <* maybeSemi) P. "[statements]" 515 | 516 | -- | Parse all statements allowed to be on top level. 517 | -- This helps to not allow import and export expressions 518 | -- in any other part of the code. 519 | topLevelStatements = importStatements <|> exportStatements <|> statements 520 | 521 | -- | parser 522 | parseJs = P.many (betweenSpaces (topLevelStatements <* maybeSemi)) 523 | 524 | -- | Parse a script with a filename. 525 | parse = P.parse parseJs 526 | 527 | -- | Parse a script from a file. Just for convinience. 528 | parseFromFile filename = P.parse parseJs filename <$> readFile filename 529 | -------------------------------------------------------------------------------- /src/Language/JS/Types.hs: -------------------------------------------------------------------------------- 1 | module Language.JS.Types where 2 | 3 | data ObjectProperty = OPI Expression 4 | | OPKV Expression Expression 5 | | OP Expression 6 | | OPM Expression 7 | deriving (Show) 8 | 9 | type IsPrefix = Bool 10 | 11 | data SwitchCase = Case Expression | DefaultCase 12 | deriving (Show) 13 | 14 | type ExpressionOpt = Maybe Expression 15 | type StatementOpt = Maybe Statement 16 | 17 | data ForStyle = ForIn BindExpression Expression 18 | | ForInV String BindExpression Expression 19 | | ForOf BindExpression Expression 20 | | ForOfV String BindExpression Expression 21 | | ForRegular (Maybe BindExpression) ExpressionOpt ExpressionOpt 22 | deriving (Show) 23 | 24 | -- | import and export binds 25 | -- * as identifier 26 | -- A, ... 27 | -- {x,...} 28 | data ImportClause = Namespace Expression 29 | | DefaultName Expression 30 | | BindNames [Expression] 31 | deriving (Show) 32 | 33 | -- possible binds 34 | -- <> 35 | -- a 36 | -- a=1 37 | -- {a} 38 | -- {a}={a:1} 39 | -- ...a 40 | -- ...{a} 41 | -- ...[a] 42 | data BindExpression = BindVar Expression (Maybe Expression) 43 | | BindPattern Expression (Maybe Expression) 44 | | BindRest Expression 45 | deriving (Show) 46 | 47 | data TemplateString = TString String 48 | | TExpression Expression 49 | deriving (Show) 50 | 51 | data Expression = -- literals 52 | LThis 53 | | LNull 54 | | LI String 55 | | LN String 56 | | LS String 57 | | LTS [TemplateString] -- LS + Expression 58 | | LB Bool 59 | | RegExp String String 60 | | UnaryUpdate String IsPrefix Expression 61 | | Unary String Expression 62 | | Spread Expression 63 | | Elision -- single comma 64 | | LA [Expression] 65 | | LO [ObjectProperty] 66 | | LP Expression 67 | | Condition Expression Expression Expression -- exp ? exp : exp 68 | | Assignment String Expression Expression 69 | | Operation String Expression Expression 70 | -- function expression 71 | | Function ExpressionOpt [BindExpression] Statement 72 | | Arrow (Either BindExpression [BindExpression]) Statement -- arrow function 73 | -- class expression 74 | | Class ExpressionOpt ExpressionOpt Statement 75 | | ClassProperty Expression Expression 76 | | PropertyMethod Expression [BindExpression] Statement 77 | | ClassStatic Expression 78 | | ClassGetMethod Expression 79 | | ClassSetMethod Expression 80 | -- async expression 81 | | Async Expression 82 | -- member expression 83 | | Dot Expression Expression 84 | | Acc Expression Expression 85 | | FCall Expression [Expression] 86 | | New Expression -- new + expression 87 | -- comma / sequence 88 | | Comma Expression Expression 89 | -- empty expression ; 90 | | Empty 91 | | Comment String 92 | | MultilineComment String 93 | deriving (Show) 94 | 95 | data Statement = SExp Expression 96 | -- module statements 97 | | SImportFile Expression 98 | | SImport (Either ImportClause [ImportClause]) Expression 99 | | SRExport Expression Expression 100 | | SExport Statement 101 | | SExportDefault Expression 102 | -- function and class declaration 103 | | SC String ExpressionOpt Statement 104 | | SF String [BindExpression] Statement 105 | -- variable statements 106 | | SVariable String [BindExpression] 107 | -- iteration statements 108 | | SWhile [Expression] Statement 109 | | SDoWhile Statement [Expression] 110 | | SFor ForStyle Statement 111 | | SLabel Expression Statement -- identifier: statement 112 | | SDebugger 113 | -- control statements 114 | | SContinue ExpressionOpt 115 | | SBreak ExpressionOpt 116 | -- general block statement 117 | | SBlock [Statement] 118 | | SIf Expression Statement StatementOpt 119 | -- switch statement 120 | | SSwitch Expression [Statement] 121 | | SCase [SwitchCase] [Statement] -- cases + statementslist 122 | -- try/catch/finally/throw statement 123 | | SThrow Expression 124 | | STry Statement Statement StatementOpt 125 | | SCatch ExpressionOpt Statement 126 | | SFinally Statement 127 | -- return statement 128 | | SReturn Expression 129 | -- with statement 130 | | SWith Expression Statement 131 | deriving (Show) 132 | -------------------------------------------------------------------------------- /t/Spec.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Control.Monad (when) 4 | import System.Exit 5 | import Test.Hspec 6 | import Test.Hspec.Runner 7 | import Test.Helpers 8 | import Test.MemberExpression 9 | import Test.AssignmentAndOperation 10 | import Test.Variables 11 | import Test.If 12 | import Test.Try 13 | import Test.Switch 14 | import Test.Functions 15 | import Test.Classes 16 | import Test.Iteration 17 | import Test.Exports 18 | import Test.Imports 19 | 20 | testExpressions :: Spec 21 | testExpressions = describe "Parse literals:" $ do 22 | it "this" $ do 23 | shouldBe (testExpression "this") 24 | "Right LThis" 25 | 26 | it "undefined/null" $ do 27 | shouldBe (testExpression "null") 28 | "Right LNull" 29 | 30 | it "booleans" $ do 31 | shouldBe (testExpression "true") 32 | "Right (LB True)" 33 | 34 | shouldBe (testExpression "false") 35 | "Right (LB False)" 36 | 37 | it "ident" $ do 38 | shouldBe (testExpression "test") 39 | "Right (LI \"test\")" 40 | 41 | it "numbers" $ do 42 | shouldBe (testExpression "1") 43 | "Right (LN \"1\")" 44 | 45 | shouldBe (testExpression "1.10") 46 | "Right (LN \"1.10\")" 47 | 48 | shouldBe (testExpression "0.10e3") 49 | "Right (LN \"0.10e3\")" 50 | 51 | shouldBe (testExpression "0x11") 52 | "Right (LN \"0x11\")" 53 | 54 | shouldBe (testExpression "0b11") 55 | "Right (LN \"0b11\")" 56 | 57 | it "strings" $ do 58 | shouldBe (testExpression "\"test\"") 59 | "Right (LS \"test\")" 60 | shouldBe (testExpression "'test'") 61 | "Right (LS \"test\")" 62 | shouldBe (testExpression "'\\''") 63 | "Right (LS \"'\")" 64 | shouldBe (testExpression "'\\n'") 65 | "Right (LS \"\\n\")" 66 | shouldBe (testExpression "'\n'") 67 | "Left \"dummy.js\" (line 1, column 2):\nunexpected \"\\n\"\nexpecting \"\\\\\" or \"'\"" 68 | 69 | it "template string" $ do 70 | shouldBe (testExpression "`test`") 71 | "Right (LTS [TString \"test\"])" 72 | 73 | shouldBe (testExpression "`${\"a\"}`") 74 | "Right (LTS [TExpression (LS \"a\")])" 75 | 76 | shouldBe (testExpression "`test ${a + 1} test`") 77 | "Right (LTS [TString \"test \",TExpression (Operation \"+\" (LI \"a\") (LN \"1\")),TString \" test\"])" 78 | 79 | shouldBe (testExpression "`${a} test ${b}`") 80 | "Right (LTS [TExpression (LI \"a\"),TString \" test \",TExpression (LI \"b\")])" 81 | 82 | it "arrays" $ do 83 | shouldBe (testExpression "[]") 84 | "Right (LA [])" 85 | 86 | shouldBe (testExpression "[,]") 87 | "Right (LA [Elision])" 88 | 89 | shouldBe (testExpression "[1,2]") 90 | "Right (LA [LN \"1\",LN \"2\"])" 91 | 92 | shouldBe (testExpression "[a,2]") 93 | "Right (LA [LI \"a\",LN \"2\"])" 94 | 95 | shouldBe (testExpression "[a,...b]") 96 | "Right (LA [LI \"a\",Spread (LI \"b\")])" 97 | 98 | shouldBe (testExpression "[a,...[1, 2]]") 99 | "Right (LA [LI \"a\",Spread (LA [LN \"1\",LN \"2\"])])" 100 | 101 | shouldBe (testExpression "[a,,b]") 102 | "Right (LA [LI \"a\",Elision,LI \"b\"])" 103 | 104 | it "objects" $ do 105 | shouldBe (testExpression "{}") 106 | "Right (LO [])" 107 | 108 | shouldBe (testExpression "{a:1}") 109 | "Right (LO [OPKV (LI \"a\") (LN \"1\")])" 110 | 111 | shouldBe (testExpression "{b:2,c: \"d\"}") 112 | "Right (LO [OPKV (LI \"b\") (LN \"2\"),OPKV (LI \"c\") (LS \"d\")])" 113 | 114 | shouldBe (testExpression "{b:2, c:function() {}}") 115 | "Right (LO [OPKV (LI \"b\") (LN \"2\"),OPKV (LI \"c\") (Function Nothing [] (SBlock []))])" 116 | 117 | shouldBe (testExpression "{e}") 118 | "Right (LO [OPI (LI \"e\")])" 119 | 120 | shouldBe (testExpression "{e,f:1}") 121 | "Right (LO [OPI (LI \"e\"),OPKV (LI \"f\") (LN \"1\")])" 122 | 123 | shouldBe (testExpression "{e,...f}") 124 | "Right (LO [OPI (LI \"e\"),OPI (Spread (LI \"f\"))])" 125 | 126 | shouldBe (testExpression "{e,...{a:1}}") 127 | "Right (LO [OPI (LI \"e\"),OPI (Spread (LO [OPKV (LI \"a\") (LN \"1\")]))])" 128 | 129 | shouldBe (testExpression "{e,i:...{a:1}}") 130 | "Right (LO [OPI (LI \"e\"),OPKV (LI \"i\") (Spread (LO [OPKV (LI \"a\") (LN \"1\")]))])" 131 | 132 | shouldBe (testExpression "{e(){}}") 133 | "Right (LO [OPM (PropertyMethod (LI \"e\") [] (SBlock []))])" 134 | 135 | shouldBe (testExpression "{get e(){}}") 136 | "Right (LO [OPM (ClassGetMethod (PropertyMethod (LI \"e\") [] (SBlock [])))])" 137 | 138 | shouldBe (testExpression "{set e(){}}") 139 | "Right (LO [OPM (ClassSetMethod (PropertyMethod (LI \"e\") [] (SBlock [])))])" 140 | 141 | shouldBe (testExpression "{async e(){}}") 142 | "Right (LO [OPM (Async (PropertyMethod (LI \"e\") [] (SBlock [])))])" 143 | 144 | it "regular expression" $ do 145 | shouldBe (testExpression "/test\\/asdf/") 146 | "Right (RegExp \"test\\\\/asdf\" \"\")" 147 | 148 | shouldBe (testExpression "/test\\/asdf/gmi") 149 | "Right (RegExp \"test\\\\/asdf\" \"gmi\")" 150 | 151 | shouldBe (testExpression "/test\\/asdf/gmi.exec(test)") 152 | "Right (FCall (Dot (RegExp \"test\\\\/asdf\" \"gmi\") (LI \"exec\")) [LI \"test\"])" 153 | 154 | it "parens expression" $ do 155 | shouldBe (testExpression "(a,b)") 156 | "Right (LP (Comma (LI \"a\") (LI \"b\")))" 157 | 158 | it "new expression" $ testNewExpression 159 | 160 | it "function call expression" $ testFunctionCall 161 | 162 | it "dotted accesor" $ testDotAccessor 163 | 164 | it "array accessor" $ testArrayAccessor 165 | 166 | it "function expression" $ testFunctions 167 | 168 | it "class expression" $ testClasses 169 | 170 | it "unary expression" $ testUnaryOperations 171 | 172 | it "assignment expression" $ testAssignments 173 | 174 | it "operation expressions" $ testOperations 175 | 176 | it "spread expression" $ do 177 | shouldBe (testExpression "{...a}") 178 | "Right (LO [OPI (Spread (LI \"a\"))])" 179 | 180 | it "empty expression" $ do 181 | shouldBe (testExpression ";") 182 | "Right Empty" 183 | 184 | testStatements = describe "Statements" $ do 185 | it "empty statement" $ do 186 | shouldBe (testStatement ";") 187 | "Right (SExp Empty)" 188 | 189 | it "if statement" $ testIf 190 | 191 | it "try statement" $ testTry 192 | 193 | it "throw statements" $ do 194 | shouldBe (testStatement "throw x;") 195 | "Right (SThrow (LI \"x\"))" 196 | 197 | it "variable statements" $ testVariables 198 | 199 | it "switch statement" $ testSwitch 200 | 201 | it "continue statement" $ do 202 | shouldBe (testStatement "continue") 203 | "Right (SContinue Nothing)" 204 | 205 | shouldBe (testStatement "continue x") 206 | "Right (SContinue (Just (LI \"x\")))" 207 | 208 | it "debugger statement" $ do 209 | shouldBe (testStatement "debugger") 210 | "Right SDebugger" 211 | 212 | it "iteration statement" $ testIteration 213 | 214 | it "with statement" $ do 215 | shouldBe (testStatement "with (a) {}") 216 | "Right (SWith (LI \"a\") (SBlock []))" 217 | 218 | it "labelled statement" $ do 219 | shouldBe (testStatement "a: x = 1") 220 | "Right (SLabel (LI \"a\") (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))))" 221 | 222 | it "import statement" $ testImports 223 | 224 | it "export statement" $ testExports 225 | 226 | it "function declaration" $ testFunctionDeclaration 227 | 228 | it "class declaration" $ testClassesDeclaration 229 | 230 | testAll :: Spec 231 | testAll = do 232 | testExpressions 233 | testStatements 234 | 235 | main :: IO () 236 | main = do 237 | summary <- hspecWithResult defaultConfig testAll 238 | when (summaryFailures summary == 0) 239 | exitSuccess 240 | exitFailure 241 | -------------------------------------------------------------------------------- /t/Test/AssignmentAndOperation.hs: -------------------------------------------------------------------------------- 1 | module Test.AssignmentAndOperation where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testUnaryOperations = do 7 | shouldBe (testExpression "+a") 8 | "Right (Unary \"+\" (LI \"a\"))" 9 | 10 | shouldBe (testExpression "-a") 11 | "Right (Unary \"-\" (LI \"a\"))" 12 | 13 | shouldBe (testExpression "~a") 14 | "Right (Unary \"~\" (LI \"a\"))" 15 | 16 | shouldBe (testExpression "!a") 17 | "Right (Unary \"!\" (LI \"a\"))" 18 | 19 | shouldBe (testExpression "delete a") 20 | "Right (Unary \"delete\" (LI \"a\"))" 21 | 22 | shouldBe (testExpression "typeof a") 23 | "Right (Unary \"typeof\" (LI \"a\"))" 24 | 25 | shouldBe (testExpression "void a") 26 | "Right (Unary \"void\" (LI \"a\"))" 27 | 28 | shouldBe (testExpression "++a") 29 | "Right (UnaryUpdate \"++\" True (LI \"a\"))" 30 | 31 | shouldBe (testExpression "a++") 32 | "Right (UnaryUpdate \"++\" False (LI \"a\"))" 33 | 34 | shouldBe (testExpression "--a") 35 | "Right (UnaryUpdate \"--\" True (LI \"a\"))" 36 | 37 | shouldBe (testExpression "a--") 38 | "Right (UnaryUpdate \"--\" False (LI \"a\"))" 39 | 40 | testAssignments = do 41 | shouldBe (testExpression "x = 1") 42 | "Right (Assignment \"=\" (LI \"x\") (LN \"1\"))" 43 | 44 | shouldBe (testExpression "x += 1") 45 | "Right (Assignment \"+=\" (LI \"x\") (LN \"1\"))" 46 | 47 | shouldBe (testExpression "x -= 1") 48 | "Right (Assignment \"-=\" (LI \"x\") (LN \"1\"))" 49 | 50 | shouldBe (testExpression "x *= 1") 51 | "Right (Assignment \"*=\" (LI \"x\") (LN \"1\"))" 52 | 53 | shouldBe (testExpression "x /= 1") 54 | "Right (Assignment \"/=\" (LI \"x\") (LN \"1\"))" 55 | 56 | shouldBe (testExpression "x %= 1") 57 | "Right (Assignment \"%=\" (LI \"x\") (LN \"1\"))" 58 | 59 | shouldBe (testExpression "x &= 1") 60 | "Right (Assignment \"&=\" (LI \"x\") (LN \"1\"))" 61 | 62 | shouldBe (testExpression "x |= 1") 63 | "Right (Assignment \"|=\" (LI \"x\") (LN \"1\"))" 64 | 65 | shouldBe (testExpression "x **= 1") 66 | "Right (Assignment \"**=\" (LI \"x\") (LN \"1\"))" 67 | 68 | shouldBe (testExpression "x >>= 1") 69 | "Right (Assignment \">>=\" (LI \"x\") (LN \"1\"))" 70 | 71 | shouldBe (testExpression "x <<= 1") 72 | "Right (Assignment \"<<=\" (LI \"x\") (LN \"1\"))" 73 | 74 | shouldBe (testExpression "x >>>= 1") 75 | "Right (Assignment \">>>=\" (LI \"x\") (LN \"1\"))" 76 | 77 | shouldBe (testExpression "x.a += 1") 78 | "Right (Assignment \"+=\" (Dot (LI \"x\") (LI \"a\")) (LN \"1\"))" 79 | 80 | testOperations = do 81 | shouldBe (testExpression "a + b") 82 | "Right (Operation \"+\" (LI \"a\") (LI \"b\"))" 83 | 84 | shouldBe (testExpression "a - b") 85 | "Right (Operation \"-\" (LI \"a\") (LI \"b\"))" 86 | 87 | shouldBe (testExpression "a * b") 88 | "Right (Operation \"*\" (LI \"a\") (LI \"b\"))" 89 | 90 | shouldBe (testExpression "a / b") 91 | "Right (Operation \"/\" (LI \"a\") (LI \"b\"))" 92 | 93 | shouldBe (testExpression "a = b - c") 94 | "Right (Assignment \"=\" (LI \"a\") (Operation \"-\" (LI \"b\") (LI \"c\")))" 95 | 96 | shouldBe (testExpression "a + b - c") 97 | "Right (Operation \"-\" (Operation \"+\" (LI \"a\") (LI \"b\")) (LI \"c\"))" 98 | 99 | shouldBe (testExpression "a + x.a - x.c") 100 | "Right (Operation \"-\" (Operation \"+\" (LI \"a\") (Dot (LI \"x\") (LI \"a\"))) (Dot (LI \"x\") (LI \"c\")))" 101 | 102 | shouldBe (testExpression "a + x.a && x.c") 103 | "Right (Operation \"&&\" (Operation \"+\" (LI \"a\") (Dot (LI \"x\") (LI \"a\"))) (Dot (LI \"x\") (LI \"c\")))" 104 | 105 | shouldBe (testExpression "a == x.a + x.c") 106 | "Right (Operation \"==\" (LI \"a\") (Operation \"+\" (Dot (LI \"x\") (LI \"a\")) (Dot (LI \"x\") (LI \"c\"))))" 107 | 108 | shouldBe (testExpression "!a") 109 | "Right (Unary \"!\" (LI \"a\"))" 110 | 111 | shouldBe (testExpression "a = x.a && x.c") 112 | "Right (Assignment \"=\" (LI \"a\") (Operation \"&&\" (Dot (LI \"x\") (LI \"a\")) (Dot (LI \"x\") (LI \"c\"))))" 113 | 114 | shouldBe (testExpression "a instanceof c") 115 | "Right (Operation \"instanceof\" (LI \"a\") (LI \"c\"))" 116 | 117 | shouldBe (testExpression "typeof a == \"number\"") 118 | "Right (Operation \"==\" (Unary \"typeof\" (LI \"a\")) (LS \"number\"))" 119 | 120 | shouldBe (testExpression "x ? y : z") 121 | "Right (Condition (LI \"x\") (LI \"y\") (LI \"z\"))" 122 | 123 | shouldBe (testExpression "x == 1 ? y : z") 124 | "Right (Condition (Operation \"==\" (LI \"x\") (LN \"1\")) (LI \"y\") (LI \"z\"))" 125 | 126 | shouldBe (testExpression "a = x == 1 ? y + 1 : z && 6") 127 | "Right (Assignment \"=\" (LI \"a\") (Condition (Operation \"==\" (LI \"x\") (LN \"1\")) (Operation \"+\" (LI \"y\") (LN \"1\")) (Operation \"&&\" (LI \"z\") (LN \"6\"))))" 128 | 129 | shouldBe (testExpression "a.b() || x") 130 | "Right (Operation \"||\" (FCall (Dot (LI \"a\") (LI \"b\")) []) (LI \"x\"))" 131 | 132 | shouldBe (testExpression "(x)\n.y") 133 | "Right (Dot (LP (LI \"x\")) (LI \"y\"))" 134 | -------------------------------------------------------------------------------- /t/Test/Classes.hs: -------------------------------------------------------------------------------- 1 | module Test.Classes where 2 | 3 | import Test.Helpers 4 | import Test.Hspec 5 | 6 | testClasses = do 7 | shouldBe (testExpression "class {}") 8 | "Right (Class Nothing Nothing (SBlock []))" 9 | 10 | shouldBe (testExpression "class test {}") 11 | "Right (Class (Just (LI \"test\")) Nothing (SBlock []))" 12 | 13 | shouldBe (testExpression "class test extends A {}") 14 | "Right (Class (Just (LI \"test\")) (Just (LI \"A\")) (SBlock []))" 15 | 16 | shouldBe (testExpression "class extends A {}") 17 | "Right (Class Nothing (Just (LI \"A\")) (SBlock []))" 18 | 19 | shouldBe (testExpression "class extends A { x = 1 }") 20 | "Right (Class Nothing (Just (LI \"A\")) (SBlock [SExp (ClassProperty (LI \"x\") (LN \"1\"))]))" 21 | 22 | shouldBe (testExpression "class extends A { x() {} }") 23 | "Right (Class Nothing (Just (LI \"A\")) (SBlock [SExp (PropertyMethod (LI \"x\") [] (SBlock []))]))" 24 | 25 | shouldBe (testExpression "class { static x = 1 }") 26 | "Right (Class Nothing Nothing (SBlock [SExp (ClassStatic (ClassProperty (LI \"x\") (LN \"1\")))]))" 27 | 28 | shouldBe (testExpression "class { set x() { return 1 } }") 29 | "Right (Class Nothing Nothing (SBlock [SExp (ClassSetMethod (PropertyMethod (LI \"x\") [] (SBlock [SReturn (LN \"1\")])))]))" 30 | 31 | shouldBe (testExpression "class { get x() { return 1 } }") 32 | "Right (Class Nothing Nothing (SBlock [SExp (ClassGetMethod (PropertyMethod (LI \"x\") [] (SBlock [SReturn (LN \"1\")])))]))" 33 | 34 | shouldBe (testExpression "class { async x() { return 1 } }") 35 | "Right (Class Nothing Nothing (SBlock [SExp (Async (PropertyMethod (LI \"x\") [] (SBlock [SReturn (LN \"1\")])))]))" 36 | 37 | 38 | testClassesDeclaration = do 39 | shouldBe (testTopLevelStatement "class a {}") 40 | "Right (SC \"a\" Nothing (SBlock []))" 41 | -------------------------------------------------------------------------------- /t/Test/Exports.hs: -------------------------------------------------------------------------------- 1 | module Test.Exports where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testExports = do 7 | shouldBe (testTopLevelStatement "export {}") 8 | "Right (SExport (SBlock []))" 9 | 10 | shouldBe (testTopLevelStatement "export default {}") 11 | "Right (SExportDefault (LO []))" 12 | 13 | shouldBe (testTopLevelStatement "export {} from \"test.js\"") 14 | "Right (SRExport (LO []) (LS \"test.js\"))" 15 | 16 | shouldBe (testTopLevelStatement "export const a = 1;") 17 | "Right (SExport (SVariable \"const\" [BindVar (LI \"a\") (Just (LN \"1\"))]))" 18 | -------------------------------------------------------------------------------- /t/Test/Functions.hs: -------------------------------------------------------------------------------- 1 | module Test.Functions where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testFunctions = do 7 | shouldBe (testExpression "function a() {}") 8 | "Right (Function (Just (LI \"a\")) [] (SBlock []))" 9 | 10 | shouldBe (testExpression "function() {}") 11 | "Right (Function Nothing [] (SBlock []))" 12 | 13 | shouldBe (testExpression "function(a, ...b) {}") 14 | "Right (Function Nothing [BindVar (LI \"a\") Nothing,BindRest (LI \"b\")] (SBlock []))" 15 | 16 | shouldBe (testExpression "function(a=1) {}") 17 | "Right (Function Nothing [BindVar (LI \"a\") (Just (LN \"1\"))] (SBlock []))" 18 | 19 | shouldBe (testExpression "function(a=1) { return 1; }") 20 | "Right (Function Nothing [BindVar (LI \"a\") (Just (LN \"1\"))] (SBlock [SReturn (LN \"1\")]))" 21 | 22 | shouldBe (testExpression "function(a=1) {\n function b() {} }") 23 | "Right (Function Nothing [BindVar (LI \"a\") (Just (LN \"1\"))] (SBlock [SF \"b\" [] (SBlock [])]))" 24 | 25 | shouldBe (testExpression "()=>x") 26 | "Right (Arrow (Right []) (SExp (LI \"x\")))" 27 | 28 | shouldBe (testExpression "()=>{x}") 29 | "Right (Arrow (Right []) (SBlock [SExp (LI \"x\")]))" 30 | 31 | shouldBe (testExpression "x=>{x}") 32 | "Right (Arrow (Left (BindVar (LI \"x\") Nothing)) (SBlock [SExp (LI \"x\")]))" 33 | 34 | testFunctionDeclaration = do 35 | shouldBe (testTopLevelStatement "function a() {} function b() {}") 36 | "Right (SF \"a\" [] (SBlock []))" 37 | 38 | shouldBe (testTopLevelStatement "function a() { var i; return 1; }") 39 | "Right (SF \"a\" [] (SBlock [SVariable \"var\" [BindVar (LI \"i\") Nothing],SReturn (LN \"1\")]))" 40 | 41 | shouldBe (testTopLevelStatement "function a() { function b() { return () => 1; } return 1; }") 42 | "Right (SF \"a\" [] (SBlock [SF \"b\" [] (SBlock [SReturn (Arrow (Right []) (SExp (LN \"1\")))]),SReturn (LN \"1\")]))" 43 | -------------------------------------------------------------------------------- /t/Test/Helpers.hs: -------------------------------------------------------------------------------- 1 | module Test.Helpers where 2 | 3 | import Language.JS.Parser hiding (parse) 4 | import qualified Language.JS.Parser as JS 5 | import Text.Parsec 6 | 7 | testExpression = parse expressions "dummy.js" >>= return . show 8 | testStatement = parse statements "dummy.js" >>= return . show 9 | testTopLevelStatement = parse topLevelStatements "dummy.js" >>= return . show 10 | 11 | testParseJs = JS.parse "dummy.js" >>= return . show 12 | -------------------------------------------------------------------------------- /t/Test/If.hs: -------------------------------------------------------------------------------- 1 | module Test.If where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testIf = do 7 | shouldBe (testStatement "if (1) x = 1") 8 | "Right (SIf (LP (LN \"1\")) (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))) Nothing)" 9 | 10 | shouldBe (testStatement "if (1)\nx = 1") 11 | "Right (SIf (LP (LN \"1\")) (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))) Nothing)" 12 | 13 | shouldBe (testStatement "if (1) {x = 1}") 14 | "Right (SIf (LP (LN \"1\")) (SBlock [SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))]) Nothing)" 15 | 16 | shouldBe (testStatement "if (1) {x = 1} else x = 2") 17 | "Right (SIf (LP (LN \"1\")) (SBlock [SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))]) (Just (SExp (Assignment \"=\" (LI \"x\") (LN \"2\")))))" 18 | 19 | shouldBe (testStatement "if (a in [1, 2]) {}") 20 | "Right (SIf (LP (Operation \"in\" (LI \"a\") (LA [LN \"1\",LN \"2\"]))) (SBlock []) Nothing)" 21 | -------------------------------------------------------------------------------- /t/Test/Imports.hs: -------------------------------------------------------------------------------- 1 | module Test.Imports where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testImports = do 7 | shouldBe (testTopLevelStatement "import \"test.js\"") 8 | "Right (SImportFile (LS \"test.js\"))" 9 | 10 | shouldBe (testTopLevelStatement "import * as A from \"test.js\"") 11 | "Right (SImport (Left (Namespace (LI \"A\"))) (LS \"test.js\"))" 12 | 13 | shouldBe (testTopLevelStatement "import {x} from \"test.js\"") 14 | "Right (SImport (Right [BindNames [LI \"x\"]]) (LS \"test.js\"))" 15 | 16 | shouldBe (testTopLevelStatement "import B, {x} from \"test.js\"") 17 | "Right (SImport (Right [DefaultName (LI \"B\"),BindNames [LI \"x\"]]) (LS \"test.js\"))" 18 | -------------------------------------------------------------------------------- /t/Test/Iteration.hs: -------------------------------------------------------------------------------- 1 | module Test.Iteration where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testIteration = do 7 | shouldBe (testStatement "while (1) {}") 8 | "Right (SWhile [LN \"1\"] (SBlock []))" 9 | 10 | -- break is allowed here 11 | shouldBe (testStatement "while (1) {break}") 12 | "Right (SWhile [LN \"1\"] (SBlock [SBreak Nothing]))" 13 | 14 | shouldBe (testStatement "while (1) {\nbreak\n;}") 15 | "Right (SWhile [LN \"1\"] (SBlock [SBreak Nothing]))" 16 | 17 | shouldBe (testStatement "while (1) x = 1") 18 | "Right (SWhile [LN \"1\"] (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))))" 19 | 20 | shouldBe (testStatement "do {} while (1)") 21 | "Right (SDoWhile (SBlock []) [LN \"1\"])" 22 | 23 | shouldBe (testStatement "do {break} while (1)") 24 | "Right (SDoWhile (SBlock [SBreak Nothing]) [LN \"1\"])" 25 | 26 | shouldBe (testStatement "do x = 1; while (1)") 27 | "Right (SDoWhile (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))) [LN \"1\"])" 28 | 29 | shouldBe (testStatement "for (;;) {}") 30 | "Right (SFor (ForRegular Nothing Nothing Nothing) (SBlock []))" 31 | 32 | shouldBe (testStatement "for (;;) {break}") 33 | "Right (SFor (ForRegular Nothing Nothing Nothing) (SBlock [SBreak Nothing]))" 34 | 35 | shouldBe (testStatement "for (;;) x = 1") 36 | "Right (SFor (ForRegular Nothing Nothing Nothing) (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))))" 37 | 38 | shouldBe (testStatement "for (x;;) x = 1") 39 | "Right (SFor (ForRegular (Just (BindVar (LI \"x\") Nothing)) Nothing Nothing) (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))))" 40 | 41 | shouldBe (testStatement "for (x;;x++) x = 1") 42 | "Right (SFor (ForRegular (Just (BindVar (LI \"x\") Nothing)) Nothing (Just (UnaryUpdate \"++\" False (LI \"x\")))) (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))))" 43 | 44 | shouldBe (testStatement "for (x;;x+=1) x = 1") 45 | "Right (SFor (ForRegular (Just (BindVar (LI \"x\") Nothing)) Nothing (Just (Assignment \"+=\" (LI \"x\") (LN \"1\")))) (SExp (Assignment \"=\" (LI \"x\") (LN \"1\"))))" 46 | 47 | shouldBe (testStatement "for (;x&&y;) {}") 48 | "Right (SFor (ForRegular Nothing (Just (Operation \"&&\" (LI \"x\") (LI \"y\"))) Nothing) (SBlock []))" 49 | 50 | shouldBe (testStatement "for (x in a) {}") 51 | "Right (SFor (ForIn (BindVar (LI \"x\") Nothing) (LI \"a\")) (SBlock []))" 52 | 53 | shouldBe (testStatement "for (let x in a) {}") 54 | "Right (SFor (ForInV \"let\" (BindVar (LI \"x\") Nothing) (LI \"a\")) (SBlock []))" 55 | 56 | shouldBe (testStatement "for (x of a) {}") 57 | "Right (SFor (ForOf (BindVar (LI \"x\") Nothing) (LI \"a\")) (SBlock []))" 58 | 59 | shouldBe (testStatement "for (let x of a) {}") 60 | "Right (SFor (ForOfV \"let\" (BindVar (LI \"x\") Nothing) (LI \"a\")) (SBlock []))" 61 | -------------------------------------------------------------------------------- /t/Test/MemberExpression.hs: -------------------------------------------------------------------------------- 1 | module Test.MemberExpression where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testNewExpression = do 7 | shouldBe (testExpression "new A") 8 | "Right (New (LI \"A\"))" 9 | 10 | shouldBe (testExpression "new A()") 11 | "Right (New (FCall (LI \"A\") []))" 12 | 13 | testFunctionCall = do 14 | shouldBe (testExpression "a()") 15 | "Right (FCall (LI \"a\") [])" 16 | 17 | shouldBe (testExpression "a.b()") 18 | "Right (FCall (Dot (LI \"a\") (LI \"b\")) [])" 19 | 20 | shouldBe (testExpression "a().b") 21 | "Right (Dot (FCall (LI \"a\") []) (LI \"b\"))" 22 | 23 | shouldBe (testExpression "a(1).b") 24 | "Right (Dot (FCall (LI \"a\") [LN \"1\"]) (LI \"b\"))" 25 | 26 | testDotAccessor = do 27 | shouldBe (testExpression "a.b") 28 | "Right (Dot (LI \"a\") (LI \"b\"))" 29 | 30 | testArrayAccessor = do 31 | shouldBe (testExpression "a[0]") 32 | "Right (Acc (LI \"a\") (LN \"0\"))" 33 | 34 | shouldBe (testExpression "a[b.x]") 35 | "Right (Acc (LI \"a\") (Dot (LI \"b\") (LI \"x\")))" 36 | 37 | shouldBe (testExpression "a[b.x].c") 38 | "Right (Dot (Acc (LI \"a\") (Dot (LI \"b\") (LI \"x\"))) (LI \"c\"))" 39 | 40 | shouldBe (testExpression "a.c[b.x]") 41 | "Right (Acc (Dot (LI \"a\") (LI \"c\")) (Dot (LI \"b\") (LI \"x\")))" 42 | -------------------------------------------------------------------------------- /t/Test/Switch.hs: -------------------------------------------------------------------------------- 1 | module Test.Switch where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testSwitch = do 7 | shouldBe (testStatement "var x;") 8 | "Right (SVariable \"var\" [BindVar (LI \"x\") Nothing])" 9 | 10 | shouldBe (testStatement "let x;") 11 | "Right (SVariable \"let\" [BindVar (LI \"x\") Nothing])" 12 | 13 | shouldBe (testStatement "var x = 1") 14 | "Right (SVariable \"var\" [BindVar (LI \"x\") (Just (LN \"1\"))])" 15 | 16 | shouldBe (testStatement "var x = 1, y;") 17 | "Right (SVariable \"var\" [BindVar (LI \"x\") (Just (LN \"1\")),BindVar (LI \"y\") Nothing])" 18 | 19 | shouldBe (testStatement "const x = 1;") 20 | "Right (SVariable \"const\" [BindVar (LI \"x\") (Just (LN \"1\"))])" 21 | 22 | shouldBe (testStatement "const x = 1, y = 2;") 23 | "Right (SVariable \"const\" [BindVar (LI \"x\") (Just (LN \"1\")),BindVar (LI \"y\") (Just (LN \"2\"))])" 24 | 25 | shouldBe (testStatement "const {a} = {a:1};") 26 | "Right (SVariable \"const\" [BindPattern (LO [OPI (LI \"a\")]) (Just (LO [OPKV (LI \"a\") (LN \"1\")]))])" 27 | -------------------------------------------------------------------------------- /t/Test/Try.hs: -------------------------------------------------------------------------------- 1 | module Test.Try where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testTry = do 7 | shouldBe (testStatement "try {} catch {}") 8 | "Right (STry (SBlock []) (SCatch Nothing (SBlock [])) Nothing)" 9 | 10 | shouldBe (testStatement "try {} catch {} finally {}") 11 | "Right (STry (SBlock []) (SCatch Nothing (SBlock [])) Nothing)" 12 | 13 | shouldBe (testStatement "try {} catch (e) {} finally {}") 14 | "Right (STry (SBlock []) (SCatch (Just (LP (LI \"e\"))) (SBlock [])) Nothing)" 15 | 16 | shouldBe (testStatement "return 1") 17 | "Right (SReturn (LN \"1\"))" 18 | -------------------------------------------------------------------------------- /t/Test/Variables.hs: -------------------------------------------------------------------------------- 1 | module Test.Variables where 2 | 3 | import Test.Hspec 4 | import Test.Helpers 5 | 6 | testVariables = do 7 | shouldBe (testStatement "var x;") 8 | "Right (SVariable \"var\" [BindVar (LI \"x\") Nothing])" 9 | 10 | shouldBe (testStatement "let x;") 11 | "Right (SVariable \"let\" [BindVar (LI \"x\") Nothing])" 12 | 13 | shouldBe (testStatement "var x = 1") 14 | "Right (SVariable \"var\" [BindVar (LI \"x\") (Just (LN \"1\"))])" 15 | 16 | shouldBe (testStatement "var x = 1, y;") 17 | "Right (SVariable \"var\" [BindVar (LI \"x\") (Just (LN \"1\")),BindVar (LI \"y\") Nothing])" 18 | 19 | shouldBe (testStatement "const x = 1;") 20 | "Right (SVariable \"const\" [BindVar (LI \"x\") (Just (LN \"1\"))])" 21 | 22 | shouldBe (testStatement "const x = 1, y = 2;") 23 | "Right (SVariable \"const\" [BindVar (LI \"x\") (Just (LN \"1\")),BindVar (LI \"y\") (Just (LN \"2\"))])" 24 | 25 | shouldBe (testStatement "const {a} = {a:1};") 26 | "Right (SVariable \"const\" [BindPattern (LO [OPI (LI \"a\")]) (Just (LO [OPKV (LI \"a\") (LN \"1\")]))])" 27 | --------------------------------------------------------------------------------