├── .nvmrc ├── .gitignore ├── elm-tooling.json ├── src ├── Elm │ ├── Internal │ │ └── RawFile.elm │ ├── Parser │ │ ├── Base.elm │ │ ├── File.elm │ │ ├── Comments.elm │ │ ├── Expose.elm │ │ ├── Imports.elm │ │ ├── Modules.elm │ │ └── Layout.elm │ ├── Syntax │ │ ├── Documentation.elm │ │ ├── Comments.elm │ │ ├── ModuleName.elm │ │ ├── Signature.elm │ │ ├── File.elm │ │ ├── Import.elm │ │ ├── TypeAlias.elm │ │ ├── Node.elm │ │ ├── Infix.elm │ │ ├── Type.elm │ │ ├── Declaration.elm │ │ ├── Range.elm │ │ ├── Module.elm │ │ ├── Exposing.elm │ │ ├── TypeAnnotation.elm │ │ └── Pattern.elm │ ├── Json │ │ └── Util.elm │ ├── Dependency.elm │ ├── RawFile.elm │ ├── Parser.elm │ ├── Processing.elm │ └── Interface.elm ├── List │ └── Extra.elm ├── Rope.elm └── ParserWithComments.elm ├── review ├── suppressed │ ├── NoImportingEverything.json │ └── NoDeprecated.json ├── src │ ├── AstFormatting.elm │ └── ReviewConfig.elm └── elm.json ├── tests └── Elm │ ├── Parser │ ├── TestUtil.elm │ ├── BaseTest.elm │ ├── GlslTests.elm │ ├── InfixTests.elm │ ├── CommentTest.elm │ ├── ParserWithCommentsTestUtil.elm │ ├── LayoutTests.elm │ ├── ImportsTests.elm │ ├── NumbersTests.elm │ ├── LambdaExpressionTests.elm │ ├── TokenTests.elm │ ├── ExposeTests.elm │ └── CaseExpressionTests.elm │ └── Syntax │ ├── ExposingTests.elm │ ├── ImportTests.elm │ └── RangeTests.elm ├── find-regressions ├── current │ └── elm.json ├── published │ └── elm.json ├── src │ └── ParseMain.elm └── index.js ├── demo ├── elm.json └── src │ ├── NodeCollector.elm │ └── Demo.elm ├── LICENSE ├── elm-community_list-extra-LICENSE ├── package.json ├── benchmark └── elm.json ├── find-regressions.sh ├── elm.json ├── README.md ├── dmy_pratt-elm-pratt-parser-LICENSE ├── package-tests.js └── .github └── workflows └── test.yml /.nvmrc: -------------------------------------------------------------------------------- 1 | v10 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | parse.js 3 | node_modules 4 | .idea 5 | *.html 6 | main.js 7 | 8 | find-regressions/**/*.js -------------------------------------------------------------------------------- /elm-tooling.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": { 3 | "elm": "0.19.1", 4 | "elm-format": "0.8.7" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Elm/Internal/RawFile.elm: -------------------------------------------------------------------------------- 1 | module Elm.Internal.RawFile exposing (RawFile(..), fromFile) 2 | 3 | import Elm.Syntax.File exposing (File) 4 | 5 | 6 | type RawFile 7 | = Raw File 8 | 9 | 10 | fromFile : File -> RawFile 11 | fromFile = 12 | Raw 13 | -------------------------------------------------------------------------------- /review/suppressed/NoImportingEverything.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "automatically created by": "elm-review suppress", 4 | "learn more": "elm-review suppress --help", 5 | "suppressions": [ 6 | { "count": 13, "filePath": "src/Elm/Writer.elm" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /review/suppressed/NoDeprecated.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "automatically created by": "elm-review suppress", 4 | "learn more": "elm-review suppress --help", 5 | "suppressions": [ 6 | { "count": 2, "filePath": "tests/Elm/ProcessingTests.elm" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/Elm/Parser/TestUtil.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.TestUtil exposing (parse, parseToResult) 2 | 3 | import Parser 4 | import ParserFast 5 | 6 | 7 | parse : String -> ParserFast.Parser a -> Maybe a 8 | parse source p = 9 | ParserFast.run p source |> Result.toMaybe 10 | 11 | 12 | parseToResult : String -> ParserFast.Parser a -> Result (List Parser.DeadEnd) a 13 | parseToResult source p = 14 | ParserFast.run p source 15 | -------------------------------------------------------------------------------- /find-regressions/current/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "../src", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.1", 8 | "dependencies": { 9 | "direct": { 10 | "elm/core": "1.0.5", 11 | "elm/json": "1.1.3", 12 | "elm/parser": "1.1.0", 13 | "miniBill/elm-unicode": "1.1.1", 14 | "rtfeldman/elm-hex": "1.0.0" 15 | }, 16 | "indirect": {} 17 | }, 18 | "test-dependencies": { 19 | "direct": {}, 20 | "indirect": {} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /find-regressions/published/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "../src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "elm/core": "1.0.5", 10 | "elm/json": "1.1.3", 11 | "elm/parser": "1.1.0", 12 | "stil4m/elm-syntax": "7.3.8" 13 | }, 14 | "indirect": { 15 | "rtfeldman/elm-hex": "1.0.0", 16 | "stil4m/structured-writer": "1.0.3" 17 | } 18 | }, 19 | "test-dependencies": { 20 | "direct": {}, 21 | "indirect": {} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elm/Parser/Base.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.Base exposing (moduleName) 2 | 3 | import Elm.Parser.Tokens as Tokens 4 | import Elm.Syntax.ModuleName exposing (ModuleName) 5 | import Elm.Syntax.Node exposing (Node(..)) 6 | import ParserFast 7 | 8 | 9 | moduleName : ParserFast.Parser (Node ModuleName) 10 | moduleName = 11 | ParserFast.map2WithRange 12 | (\range head tail -> 13 | Node range (head :: tail) 14 | ) 15 | Tokens.typeName 16 | moduleNameOrEmpty 17 | 18 | 19 | moduleNameOrEmpty : ParserFast.Parser ModuleName 20 | moduleNameOrEmpty = 21 | ParserFast.map2OrSucceed 22 | (\head tail -> head :: tail) 23 | (ParserFast.symbolFollowedBy "." Tokens.typeName) 24 | (ParserFast.lazy (\() -> moduleNameOrEmpty)) 25 | [] 26 | -------------------------------------------------------------------------------- /tests/Elm/Parser/BaseTest.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.BaseTest exposing (all) 2 | 3 | import Elm.Parser.Base as Parser 4 | import Elm.Parser.TestUtil as TestUtil 5 | import Elm.Syntax.Node as Node 6 | import Expect 7 | import Test exposing (Test, describe, test) 8 | 9 | 10 | all : Test 11 | all = 12 | describe "BaseTest" 13 | [ test "moduleName" <| 14 | \() -> 15 | TestUtil.parseToResult "Foo" Parser.moduleName 16 | |> Result.map Node.value 17 | |> Expect.equal (Ok [ "Foo" ]) 18 | , test "moduleNameDir" <| 19 | \() -> 20 | TestUtil.parseToResult "Foo.Bar" Parser.moduleName 21 | |> Result.map Node.value 22 | |> Expect.equal (Ok [ "Foo", "Bar" ]) 23 | ] 24 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Documentation.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Documentation exposing 2 | ( Documentation 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents documentation comments in Elm. 7 | 8 | 9 | ## Types 10 | 11 | @docs Documentation 12 | 13 | 14 | ## Serialization 15 | 16 | @docs encode, decoder 17 | 18 | -} 19 | 20 | import Json.Decode as JD exposing (Decoder) 21 | import Json.Encode as JE exposing (Value) 22 | 23 | 24 | {-| Type representing the documentation syntax 25 | -} 26 | type alias Documentation = 27 | String 28 | 29 | 30 | 31 | -- Serialization 32 | 33 | 34 | {-| Encode a `Documentation` syntax element to JSON. 35 | -} 36 | encode : Documentation -> Value 37 | encode = 38 | JE.string 39 | 40 | 41 | {-| JSON decoder for a `Documentation` syntax element. 42 | -} 43 | decoder : Decoder Documentation 44 | decoder = 45 | JD.string 46 | -------------------------------------------------------------------------------- /tests/Elm/Parser/GlslTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.GlslTests exposing (all) 2 | 3 | import Elm.Parser.Expression exposing (expression) 4 | import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil 5 | import Elm.Syntax.Expression exposing (..) 6 | import Elm.Syntax.Node exposing (Node(..)) 7 | import Expect 8 | import Test exposing (..) 9 | 10 | 11 | all : Test 12 | all = 13 | describe "GlslTests" 14 | [ test "case block" <| 15 | \() -> 16 | "[glsl| precision mediump float; |]" 17 | |> expectAst 18 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 37 } } 19 | (GLSLExpression " precision mediump float; ") 20 | ) 21 | ] 22 | 23 | 24 | expectAst : Node Expression -> String -> Expect.Expectation 25 | expectAst = 26 | ParserWithCommentsUtil.expectAst expression 27 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Comments.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Comments exposing 2 | ( Comment 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents both single and multi line comments in Elm. For example: 7 | 8 | -- A comment 9 | 10 | 11 | 12 | 13 | {- Some 14 | multi 15 | line 16 | comment 17 | -} 18 | 19 | 20 | ## Types 21 | 22 | @docs Comment 23 | 24 | 25 | ## Serialization 26 | 27 | @docs encode, decoder 28 | 29 | -} 30 | 31 | import Json.Decode as JD exposing (Decoder) 32 | import Json.Encode as JE exposing (Value) 33 | 34 | 35 | {-| Type representing the comment syntax 36 | -} 37 | type alias Comment = 38 | String 39 | 40 | 41 | {-| Encode a `Comment` syntax element to JSON. 42 | -} 43 | encode : Comment -> Value 44 | encode = 45 | JE.string 46 | 47 | 48 | {-| JSON decoder for a `Comment` syntax element. 49 | -} 50 | decoder : Decoder Comment 51 | decoder = 52 | JD.string 53 | -------------------------------------------------------------------------------- /demo/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src", 5 | "../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.0", 11 | "elm/core": "1.0.0", 12 | "elm/html": "1.0.0", 13 | "elm/json": "1.0.0", 14 | "elm/parser": "1.1.0", 15 | "elm-community/json-extra": "4.0.0", 16 | "elm-community/list-extra": "8.0.0", 17 | "klazuka/elm-json-tree-view": "1.0.0", 18 | "rtfeldman/elm-hex": "1.0.0" 19 | }, 20 | "indirect": { 21 | "elm/time": "1.0.0", 22 | "elm/url": "1.0.0", 23 | "elm/virtual-dom": "1.0.0", 24 | "rtfeldman/elm-iso8601-date-strings": "1.1.0" 25 | } 26 | }, 27 | "test-dependencies": { 28 | "direct": {}, 29 | "indirect": {} 30 | } 31 | } -------------------------------------------------------------------------------- /src/Elm/Json/Util.elm: -------------------------------------------------------------------------------- 1 | module Elm.Json.Util exposing (decodeTyped, encodeTyped) 2 | 3 | import Json.Decode as JD exposing (Decoder) 4 | import Json.Encode as JE exposing (Value) 5 | 6 | 7 | encodeTyped : String -> Value -> Value 8 | encodeTyped x v = 9 | JE.object 10 | [ ( "type", JE.string x ) 11 | , ( x, v ) 12 | ] 13 | 14 | 15 | decodeTyped : List ( String, Decoder a ) -> Decoder a 16 | decodeTyped opts = 17 | JD.lazy 18 | (\() -> 19 | JD.field "type" JD.string 20 | |> JD.andThen 21 | (\t -> 22 | case List.filter (\( opt, _ ) -> opt == t) opts |> List.head of 23 | Just m -> 24 | JD.field (Tuple.first m) <| Tuple.second m 25 | 26 | Nothing -> 27 | JD.fail ("No decoder for type: " ++ t) 28 | ) 29 | ) 30 | -------------------------------------------------------------------------------- /src/Elm/Dependency.elm: -------------------------------------------------------------------------------- 1 | module Elm.Dependency exposing (Dependency, Version) 2 | 3 | {-| This module contains types regarding dependencies of a codebase. 4 | To gain the most information of a codebase, information of the dependencies may be required. 5 | For example, what operators does it define, or what constructors are defined for a custom type. 6 | 7 | 8 | ## Types 9 | 10 | @docs Dependency, Version 11 | 12 | -} 13 | 14 | import Dict exposing (Dict) 15 | import Elm.Interface exposing (Interface) 16 | import Elm.Syntax.ModuleName exposing (ModuleName) 17 | 18 | 19 | {-| Record that represents a dependency. For example: 20 | 21 | { name = "elm/core" 22 | , version = "1.0.0" 23 | , interfaces = Dict.fromList [ ( "Basics", basicsInterface ), ... ] 24 | } 25 | 26 | -} 27 | type alias Dependency = 28 | { name : String 29 | , version : Version 30 | , interfaces : Dict ModuleName Interface 31 | } 32 | 33 | 34 | {-| Alias for a version string. For example "1.2.3". 35 | -} 36 | type alias Version = 37 | String 38 | -------------------------------------------------------------------------------- /src/Elm/Syntax/ModuleName.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.ModuleName exposing 2 | ( ModuleName 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents the module names in Elm. These can be used for imports, module names (duh), and for qualified access. 7 | For example: 8 | 9 | module Elm.Syntax.ModuleName ... 10 | 11 | import Foo.Bar ... 12 | 13 | import ... as Something 14 | 15 | My.Module.something 16 | 17 | My.Module.SomeType 18 | 19 | 20 | ## Types 21 | 22 | @docs ModuleName 23 | 24 | 25 | ## Serialization 26 | 27 | @docs encode, decoder 28 | 29 | -} 30 | 31 | import Json.Decode as JD exposing (Decoder) 32 | import Json.Encode as JE exposing (Value) 33 | 34 | 35 | {-| Base representation for a module name 36 | -} 37 | type alias ModuleName = 38 | List String 39 | 40 | 41 | 42 | -- Serialization 43 | 44 | 45 | {-| Encode a `ModuleName` syntax element to JSON. 46 | -} 47 | encode : ModuleName -> Value 48 | encode = 49 | JE.list JE.string 50 | 51 | 52 | {-| JSON decoder for a `ModuleName` syntax element. 53 | -} 54 | decoder : Decoder ModuleName 55 | decoder = 56 | JD.list JD.string 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Mats Stijlaart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /elm-community_list-extra-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CircuitHub Inc., Elm Community members 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/Elm/Syntax/ExposingTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.ExposingTests exposing (suite) 2 | 3 | import Elm.Syntax.Exposing as Exposing exposing (..) 4 | import Elm.Syntax.Node as Node 5 | import Expect 6 | import Json.Decode exposing (Decoder, Value) 7 | import Test exposing (..) 8 | 9 | 10 | symmetric : a -> (a -> Value) -> Decoder a -> Expect.Expectation 11 | symmetric v enc dec = 12 | Expect.equal (Json.Decode.decodeValue dec (enc v)) (Ok v) 13 | 14 | 15 | suite : Test 16 | suite = 17 | describe "Elm.Syntax.Exposing" 18 | [ describe "serialization" 19 | [ test "exposing (beginnerProgram, div, button, text)" <| 20 | \() -> 21 | symmetric 22 | (Explicit 23 | [ Node.empty (FunctionExpose "beginnerProgram") 24 | , Node.empty (FunctionExpose "div") 25 | , Node.empty (FunctionExpose "button") 26 | , Node.empty (FunctionExpose "text") 27 | ] 28 | ) 29 | Exposing.encode 30 | Exposing.decoder 31 | ] 32 | ] 33 | -------------------------------------------------------------------------------- /src/List/Extra.elm: -------------------------------------------------------------------------------- 1 | module List.Extra exposing 2 | ( find 3 | , unique 4 | ) 5 | 6 | {-| Helper for List functions. find and unique taken from elm-community/list-extra. 7 | 8 | @docs find 9 | @docs unique 10 | 11 | -} 12 | 13 | 14 | {-| Remove duplicate values, keeping the first instance of each element which appears more than once. 15 | 16 | unique [ 0, 1, 1, 0, 1 ] 17 | --> [ 0, 1 ] 18 | 19 | -} 20 | unique : List a -> List a 21 | unique list = 22 | uniqueHelp [] list [] 23 | 24 | 25 | uniqueHelp : List a -> List a -> List a -> List a 26 | uniqueHelp existing remaining accumulator = 27 | case remaining of 28 | [] -> 29 | accumulator 30 | 31 | first :: rest -> 32 | if List.member first existing then 33 | uniqueHelp existing rest accumulator 34 | 35 | else 36 | uniqueHelp (first :: existing) rest (first :: accumulator) 37 | 38 | 39 | find : (a -> Bool) -> List a -> Maybe a 40 | find predicate list = 41 | case list of 42 | [] -> 43 | Nothing 44 | 45 | x :: xs -> 46 | if predicate x then 47 | Just x 48 | 49 | else 50 | find predicate xs 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm-syntax", 3 | "version": "0.13.0-dev", 4 | "description": "Elm syntax", 5 | "repository": { 6 | "type": "git", 7 | "url": "github.com/stil4m/elm-syntax" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/stil4m/elm-syntax/issues" 11 | }, 12 | "scripts": { 13 | "test": "npm-run-all --print-name --silent --sequential test:make test:format test:run test:review", 14 | "test:make": "elm make --docs=docs.json", 15 | "test:format": "elm-format --validate src/ tests/", 16 | "test:run": "elm-test", 17 | "test:review": "elm-review", 18 | "preview-docs": "elm-doc-preview", 19 | "elm-bump": "npm-run-all --print-name --silent --sequential test bump-version 'test:review -- --fix-all-without-prompt'", 20 | "bump-version": "(yes | elm bump)", 21 | "postinstall": "elm-tooling install" 22 | }, 23 | "author": "Mats Stijlaart", 24 | "license": "MIT", 25 | "keywords": [ 26 | "elm", 27 | "syntax", 28 | "ast" 29 | ], 30 | "dependencies": { 31 | "elm-doc-preview": "^5.0.5", 32 | "elm-review": "^2.13.0", 33 | "elm-test": "^0.19.1-revision7", 34 | "elm-tooling": "^1.3.0", 35 | "fdir": "^6.3.0", 36 | "npm-run-all": "^4.1.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /benchmark/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src", 5 | "../src" 6 | ], 7 | "elm-version": "0.19.1", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.2", 11 | "elm/bytes": "1.0.8", 12 | "elm/core": "1.0.5", 13 | "elm/html": "1.0.0", 14 | "elm/json": "1.1.3", 15 | "elm/parser": "1.1.0", 16 | "elm/regex": "1.0.0", 17 | "elm-explorations/benchmark": "1.0.2", 18 | "lue-bird/elm-alternative-benchmark-runner": "1.0.0", 19 | "miniBill/elm-unicode": "1.1.1", 20 | "rtfeldman/elm-hex": "1.0.0", 21 | "stil4m/structured-writer": "1.0.3" 22 | }, 23 | "indirect": { 24 | "BrianHicks/elm-trend": "2.1.3", 25 | "avh4/elm-color": "1.0.0", 26 | "elm/time": "1.0.0", 27 | "elm/url": "1.0.0", 28 | "elm/virtual-dom": "1.0.3", 29 | "mdgriffith/elm-ui": "1.1.8", 30 | "mdgriffith/style-elements": "5.0.2", 31 | "miniBill/elm-ui-with-context": "1.1.0", 32 | "robinheghan/murmur3": "1.0.0" 33 | } 34 | }, 35 | "test-dependencies": { 36 | "direct": {}, 37 | "indirect": {} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Signature.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Signature exposing 2 | ( Signature 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents type signatures in Elm. 7 | 8 | For example : 9 | 10 | add : Int -> Int -> Int 11 | 12 | 13 | ## Types 14 | 15 | @docs Signature 16 | 17 | 18 | ## Serialization 19 | 20 | @docs encode, decoder 21 | 22 | -} 23 | 24 | import Elm.Syntax.Node as Node exposing (Node) 25 | import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation) 26 | import Json.Decode as JD exposing (Decoder) 27 | import Json.Encode as JE exposing (Value) 28 | 29 | 30 | {-| Type alias representing a signature in Elm. 31 | -} 32 | type alias Signature = 33 | { name : Node String 34 | , typeAnnotation : Node TypeAnnotation 35 | } 36 | 37 | 38 | {-| Encode a `Signature` syntax element to JSON. 39 | -} 40 | encode : Signature -> Value 41 | encode { name, typeAnnotation } = 42 | JE.object 43 | [ ( "name", Node.encode JE.string name ) 44 | , ( "typeAnnotation", Node.encode TypeAnnotation.encode typeAnnotation ) 45 | ] 46 | 47 | 48 | {-| JSON decoder for a `Signature` syntax element. 49 | -} 50 | decoder : Decoder Signature 51 | decoder = 52 | JD.map2 Signature 53 | (JD.field "name" (Node.decoder JD.string)) 54 | (JD.field "typeAnnotation" (Node.decoder TypeAnnotation.decoder)) 55 | -------------------------------------------------------------------------------- /find-regressions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # # Instructions 4 | # 5 | # Run the script like (from the root folder) : 6 | # 7 | # ./find-regressions.sh some-folder/ 8 | # 9 | # To run it on all Elm packages in your ELM_HOME, run: 10 | # 11 | # ./find-regressions.sh ~/.elm/0.19.1/ 12 | # 13 | # The version it will compare to is the one in `find-regressions/published/elm.json` 14 | # under the `dependencies.direct.['stil4m/elm-syntax']` field. 15 | # 16 | # The script will also give performance metrics. 17 | # If you only want to get that (and don't care about the regression check), run: 18 | # 19 | # ./find-regressions.sh --no-check some-folder/ 20 | # 21 | 22 | cd find-regressions/current 23 | elm make --optimize ../src/ParseMain.elm --output elm.js > /dev/null 24 | sed -e 's/$author$project$ParseMain$timeStart(version)/globalThis.performance.now()/' \ 25 | -e 's/$author$project$ParseMain$timeEnd(version)/globalThis.measurements[version].push(globalThis.performance.now() - start)/' \ 26 | -i elm.js 27 | 28 | cd ../.. 29 | cd find-regressions/published 30 | elm make --optimize ../src/ParseMain.elm --output elm.js > /dev/null 31 | sed -e 's/$author$project$ParseMain$timeStart(version)/globalThis.performance.now()/' \ 32 | -e 's/$author$project$ParseMain$timeEnd(version)/globalThis.measurements[version].push(globalThis.performance.now() - start)/' \ 33 | -i elm.js 34 | 35 | cd ../.. 36 | node find-regressions $@ -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "stil4m/elm-syntax", 4 | "summary": "Elm Syntax in Elm: for parsing and writing Elm in Elm", 5 | "license": "MIT", 6 | "version": "7.3.9", 7 | "exposed-modules": [ 8 | "Elm.Dependency", 9 | "Elm.Interface", 10 | "Elm.Parser", 11 | "Elm.Processing", 12 | "Elm.RawFile", 13 | "Elm.Writer", 14 | "Elm.Syntax.Comments", 15 | "Elm.Syntax.Declaration", 16 | "Elm.Syntax.Documentation", 17 | "Elm.Syntax.Exposing", 18 | "Elm.Syntax.Expression", 19 | "Elm.Syntax.File", 20 | "Elm.Syntax.Import", 21 | "Elm.Syntax.Infix", 22 | "Elm.Syntax.Module", 23 | "Elm.Syntax.ModuleName", 24 | "Elm.Syntax.Node", 25 | "Elm.Syntax.Pattern", 26 | "Elm.Syntax.Range", 27 | "Elm.Syntax.Signature", 28 | "Elm.Syntax.TypeAlias", 29 | "Elm.Syntax.TypeAnnotation", 30 | "Elm.Syntax.Type" 31 | ], 32 | "elm-version": "0.19.0 <= v < 0.20.0", 33 | "dependencies": { 34 | "elm/core": "1.0.0 <= v < 2.0.0", 35 | "elm/json": "1.0.0 <= v < 2.0.0", 36 | "elm/parser": "1.0.0 <= v < 2.0.0", 37 | "rtfeldman/elm-hex": "1.0.0 <= v < 2.0.0", 38 | "stil4m/structured-writer": "1.0.1 <= v < 2.0.0" 39 | }, 40 | "test-dependencies": { 41 | "elm-explorations/test": "2.0.0 <= v < 3.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Elm/RawFile.elm: -------------------------------------------------------------------------------- 1 | module Elm.RawFile exposing 2 | ( RawFile 3 | , moduleName, imports 4 | , encode, decoder 5 | ) 6 | 7 | {-| 8 | 9 | @docs RawFile 10 | 11 | @docs moduleName, imports 12 | 13 | 14 | ## Serialization 15 | 16 | @docs encode, decoder 17 | 18 | -} 19 | 20 | import Elm.Internal.RawFile as InternalRawFile 21 | import Elm.Syntax.File as File 22 | import Elm.Syntax.Import exposing (Import) 23 | import Elm.Syntax.Module as Module 24 | import Elm.Syntax.ModuleName exposing (ModuleName) 25 | import Elm.Syntax.Node as Node 26 | import Json.Decode as JD exposing (Decoder) 27 | import Json.Encode exposing (Value) 28 | 29 | 30 | {-| A Raw file 31 | -} 32 | type alias RawFile = 33 | InternalRawFile.RawFile 34 | 35 | 36 | {-| Retrieve the module name for a raw file 37 | -} 38 | moduleName : RawFile -> ModuleName 39 | moduleName (InternalRawFile.Raw file) = 40 | Module.moduleName <| Node.value file.moduleDefinition 41 | 42 | 43 | {-| Encode a `RawFile` syntax element to JSON. 44 | -} 45 | imports : RawFile -> List Import 46 | imports (InternalRawFile.Raw file) = 47 | List.map Node.value file.imports 48 | 49 | 50 | {-| Encode a file to a value 51 | -} 52 | encode : RawFile -> Value 53 | encode (InternalRawFile.Raw file) = 54 | File.encode file 55 | 56 | 57 | {-| JSON decoder for a `RawFile` syntax element. 58 | -} 59 | decoder : Decoder RawFile 60 | decoder = 61 | JD.map InternalRawFile.Raw File.decoder 62 | -------------------------------------------------------------------------------- /src/Elm/Parser.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser exposing 2 | ( parseToFile 3 | , parse 4 | ) 5 | 6 | {-| 7 | 8 | @docs parseToFile 9 | @docs parse 10 | 11 | -} 12 | 13 | import Elm.Internal.RawFile as InternalRawFile 14 | import Elm.Parser.File exposing (file) 15 | import Elm.RawFile exposing (RawFile) 16 | import Elm.Syntax.File exposing (File) 17 | import Parser 18 | import ParserFast 19 | 20 | 21 | {-| **@deprecated** Use [`parseToFile`](#parseToFile) instead, which is simpler and doesn't require post-processing. 22 | Since v7.3.3, post-processing is unnecessary. 23 | 24 | Parse some text as if it is an Elm source file. 25 | When parsing fails, the result will contain a list of errors indicating what went wrong (and/or where). 26 | If it succeeds, you will get a `RawFile`. 27 | This `RawFile` will require some post-processing to properly setup documentation and ensure that operator precedence is applied correctly (based on dependencies). 28 | To process a `RawFile`, check out the `Processing` module. 29 | 30 | -} 31 | parse : String -> Result (List Parser.DeadEnd) RawFile 32 | parse input = 33 | parseToFile input 34 | |> Result.map InternalRawFile.fromFile 35 | 36 | 37 | {-| Parse some text as if it is an Elm source file. 38 | When parsing fails, the result will contain a list of errors indicating what went wrong (and/or where). 39 | -} 40 | parseToFile : String -> Result (List Parser.DeadEnd) File 41 | parseToFile input = 42 | ParserFast.run file input 43 | -------------------------------------------------------------------------------- /review/src/AstFormatting.elm: -------------------------------------------------------------------------------- 1 | module AstFormatting exposing (rule) 2 | 3 | import Elm.Syntax.Expression as Expression exposing (Expression) 4 | import Elm.Syntax.Node as Node exposing (Node(..)) 5 | import Review.Fix 6 | import Review.Rule as Rule exposing (Error, Rule) 7 | 8 | 9 | rule : Rule 10 | rule = 11 | Rule.newModuleRuleSchema "AstFormatting" () 12 | |> Rule.withSimpleExpressionVisitor expressionVisitor 13 | |> Rule.fromModuleRuleSchema 14 | 15 | 16 | expressionVisitor : Node Expression -> List (Error {}) 17 | expressionVisitor node = 18 | case Node.value node of 19 | Expression.Application [ Node _ (Expression.FunctionOrValue [] "Application"), Node _ (Expression.ListExpr ((Node firstItemRange _) :: _)) ] -> 20 | let 21 | { start, end } = 22 | Node.range node 23 | in 24 | if start.row == end.row then 25 | [ Rule.errorWithFix 26 | { message = "Split Application on multiple lines" 27 | , details = 28 | [ "If like me you have the bad habit of copying nodes from test outputs, this rule will cover for you." ] 29 | } 30 | (Node.range node) 31 | [ Review.Fix.insertAt firstItemRange.start ("\n" ++ String.repeat firstItemRange.start.column " ") ] 32 | ] 33 | 34 | else 35 | [] 36 | 37 | _ -> 38 | [] 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elm-syntax 2 | 3 | Elm Syntax in Elm: for parsing and writing Elm in Elm. 4 | 5 | ## How does this work? 6 | 7 | When Elm code is parsed, it's converted into an Abstract Syntax Tree (AST). 8 | The AST lets us represent the code in a way that's much easier to work with when programming. 9 | 10 | Here's an example of that: 11 | Code: `3 + 4 * 2` 12 | AST: 13 | ```elm 14 | OperatorApplication 15 | (Integer 3) 16 | "+" 17 | (OperatorApplication 18 | (Integer 4) 19 | "*" 20 | (Integer 2) 21 | ) 22 | ``` 23 | 24 | Notice how it forms a tree structure where we first multiply together 4 and 2, and then add the result with 3. 25 | That's where the "tree" part of AST comes from. 26 | 27 | ## Getting Started 28 | 29 | ```elm 30 | import Elm.Parser 31 | import Html exposing (Html) 32 | 33 | src : String 34 | src = 35 | """module Foo exposing (foo) 36 | 37 | foo = 1 38 | """ 39 | 40 | parse : String -> String 41 | parse input = 42 | case Elm.Parser.parseToFile input of 43 | Err e -> 44 | "Failed: " ++ Debug.toString e 45 | 46 | Ok v -> 47 | "Success: " ++ Debug.toString v 48 | 49 | main : Html msg 50 | main = 51 | Html.text (parse src) 52 | ``` 53 | 54 | Used in: 55 | 56 | * [`elm-review`](https://elm-review.com/) 57 | * [`elm-codegen`](https://package.elm-lang.org/packages/mdgriffith/elm-codegen/latest/) 58 | * [`elm-analyse`](https://github.com/stil4m/elm-analyse) 59 | * [`elm-xref`](https://github.com/zwilias/elm-xref) 60 | * [`elm-lens`](https://github.com/mbuscemi/elm-lens) 61 | -------------------------------------------------------------------------------- /dmy_pratt-elm-pratt-parser-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2020, Rémi Lefèvre 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /find-regressions/src/ParseMain.elm: -------------------------------------------------------------------------------- 1 | port module ParseMain exposing (main) 2 | 3 | import Elm.Parser as Parser 4 | import Elm.Syntax.File exposing (File) 5 | import Json.Encode as Encode 6 | import Parser exposing (DeadEnd) 7 | 8 | 9 | port requestParsing : (String -> msg) -> Sub msg 10 | 11 | 12 | port parseResult : Encode.Value -> Cmd msg 13 | 14 | 15 | type alias Version = 16 | String 17 | 18 | 19 | main : Program Version Version Msg 20 | main = 21 | Platform.worker 22 | { init = \version -> ( version, Cmd.none ) 23 | , update = \msg version -> ( version, update version msg ) 24 | , subscriptions = always subscriptions 25 | } 26 | 27 | 28 | subscriptions : Sub Msg 29 | subscriptions = 30 | requestParsing GotFile 31 | 32 | 33 | type Msg 34 | = GotFile String 35 | 36 | 37 | update : Version -> Msg -> Cmd Msg 38 | update version (GotFile source) = 39 | let 40 | json : Encode.Value 41 | json = 42 | case parseAndBenchmark version source of 43 | Ok ast -> 44 | Elm.Syntax.File.encode ast 45 | 46 | Err _ -> 47 | Encode.null 48 | in 49 | parseResult json 50 | 51 | 52 | parseAndBenchmark : Version -> String -> Result (List DeadEnd) File 53 | parseAndBenchmark version source = 54 | let 55 | start : () 56 | start = 57 | timeStart version 58 | in 59 | Parser.parseToFile source 60 | |> (\parsed -> always parsed (timeEnd version)) 61 | 62 | 63 | timeStart : Version -> () 64 | timeStart version = 65 | () 66 | 67 | 68 | timeEnd : Version -> () 69 | timeEnd version = 70 | () 71 | -------------------------------------------------------------------------------- /review/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "SiriusStarr/elm-review-no-unsorted": "1.1.8", 10 | "elm/core": "1.0.5", 11 | "elm/json": "1.1.3", 12 | "elm/project-metadata-utils": "1.0.2", 13 | "jfmengels/elm-review": "2.15.0", 14 | "jfmengels/elm-review-code-style": "1.2.0", 15 | "jfmengels/elm-review-common": "1.3.3", 16 | "jfmengels/elm-review-debug": "1.0.8", 17 | "jfmengels/elm-review-documentation": "2.0.4", 18 | "jfmengels/elm-review-simplify": "2.1.6", 19 | "jfmengels/elm-review-unused": "1.2.4", 20 | "stil4m/elm-syntax": "7.3.8" 21 | }, 22 | "indirect": { 23 | "avh4/elm-fifo": "1.0.4", 24 | "elm/bytes": "1.0.8", 25 | "elm/html": "1.0.0", 26 | "elm/parser": "1.1.0", 27 | "elm/random": "1.0.0", 28 | "elm/regex": "1.0.0", 29 | "elm/time": "1.0.0", 30 | "elm/virtual-dom": "1.0.3", 31 | "elm-community/dict-extra": "2.4.0", 32 | "elm-community/graph": "6.0.0", 33 | "elm-community/intdict": "3.1.0", 34 | "elm-community/list-extra": "8.7.0", 35 | "elm-community/maybe-extra": "5.3.0", 36 | "elm-community/result-extra": "2.4.0", 37 | "elm-community/string-extra": "4.0.1", 38 | "elm-explorations/test": "2.2.0", 39 | "pzp1997/assoc-list": "1.0.0", 40 | "rtfeldman/elm-hex": "1.0.0", 41 | "stil4m/structured-writer": "1.0.3" 42 | } 43 | }, 44 | "test-dependencies": { 45 | "direct": {}, 46 | "indirect": {} 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Elm/Syntax/ImportTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.ImportTests exposing (suite) 2 | 3 | import Elm.Syntax.Exposing exposing (..) 4 | import Elm.Syntax.Import as Import exposing (Import) 5 | import Elm.Syntax.Node as Node 6 | import Elm.Syntax.Range exposing (empty) 7 | import Expect 8 | import Json.Decode exposing (Decoder, Value) 9 | import Test exposing (..) 10 | 11 | 12 | symmetric : a -> (a -> Value) -> Decoder a -> Expect.Expectation 13 | symmetric v enc dec = 14 | Expect.equal (Json.Decode.decodeValue dec (enc v)) (Ok v) 15 | 16 | 17 | suite : Test 18 | suite = 19 | describe "Elm.Syntax.Import" 20 | [ describe "serialization" 21 | [ test "case 1" <| 22 | \() -> 23 | symmetric (Import (Node.empty [ "A", "B" ]) (Just <| Node.empty [ "C" ]) (Just <| Node.empty <| All empty)) 24 | Import.encode 25 | Import.decoder 26 | , test "import Html exposing (beginnerProgram, div, button, text)" <| 27 | \() -> 28 | symmetric 29 | (Import (Node.empty [ "Html" ]) 30 | Nothing 31 | (Just <| 32 | Node.empty <| 33 | Explicit 34 | [ Node.empty (FunctionExpose "beginnerProgram") 35 | , Node.empty (FunctionExpose "div") 36 | , Node.empty (FunctionExpose "button") 37 | , Node.empty (FunctionExpose "text") 38 | ] 39 | ) 40 | ) 41 | Import.encode 42 | Import.decoder 43 | ] 44 | ] 45 | -------------------------------------------------------------------------------- /src/Elm/Syntax/File.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.File exposing 2 | ( File 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents a whole Elm file. 7 | 8 | 9 | ## Types 10 | 11 | @docs File 12 | 13 | 14 | ## Serialization 15 | 16 | @docs encode, decoder 17 | 18 | -} 19 | 20 | import Elm.Syntax.Comments as Comments exposing (Comment) 21 | import Elm.Syntax.Declaration as Declaration exposing (Declaration) 22 | import Elm.Syntax.Import as Import exposing (Import) 23 | import Elm.Syntax.Module as Module exposing (Module) 24 | import Elm.Syntax.Node as Node exposing (Node) 25 | import Json.Decode as JD exposing (Decoder) 26 | import Json.Encode as JE exposing (Value) 27 | 28 | 29 | {-| Type annotation for a file 30 | -} 31 | type alias File = 32 | { moduleDefinition : Node Module 33 | , imports : List (Node Import) 34 | , declarations : List (Node Declaration) 35 | , comments : List (Node Comment) 36 | } 37 | 38 | 39 | {-| Encode a `File` syntax element to JSON. 40 | -} 41 | encode : File -> Value 42 | encode { moduleDefinition, imports, declarations, comments } = 43 | JE.object 44 | [ ( "moduleDefinition", Node.encode Module.encode moduleDefinition ) 45 | , ( "imports", JE.list (Node.encode Import.encode) imports ) 46 | , ( "declarations", JE.list (Node.encode Declaration.encode) declarations ) 47 | , ( "comments", JE.list (Node.encode Comments.encode) comments ) 48 | ] 49 | 50 | 51 | {-| JSON decoder for a `File` syntax element. 52 | -} 53 | decoder : Decoder File 54 | decoder = 55 | JD.map4 File 56 | (JD.field "moduleDefinition" (Node.decoder Module.decoder)) 57 | (JD.field "imports" (JD.list (Node.decoder Import.decoder))) 58 | (JD.field "declarations" (JD.list (Node.decoder Declaration.decoder))) 59 | (JD.field "comments" (JD.list (Node.decoder Comments.decoder))) 60 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Import.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Import exposing 2 | ( Import 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents imports in Elm. 7 | For example: 8 | 9 | import Html.Attributes as HA exposing (style) 10 | 11 | 12 | ## Types 13 | 14 | @docs Import 15 | 16 | 17 | ## Serialization 18 | 19 | @docs encode, decoder 20 | 21 | -} 22 | 23 | import Elm.Syntax.Exposing as Exposing exposing (Exposing) 24 | import Elm.Syntax.ModuleName as ModuleName exposing (ModuleName) 25 | import Elm.Syntax.Node as Node exposing (Node) 26 | import Json.Decode as JD exposing (Decoder) 27 | import Json.Encode as JE exposing (Value) 28 | 29 | 30 | {-| Type alias representing an Import 31 | -} 32 | type alias Import = 33 | { moduleName : Node ModuleName 34 | , moduleAlias : Maybe (Node ModuleName) 35 | , exposingList : Maybe (Node Exposing) 36 | } 37 | 38 | 39 | {-| Encode a `Import` syntax element to JSON. 40 | -} 41 | encode : Import -> Value 42 | encode { moduleName, moduleAlias, exposingList } = 43 | JE.object 44 | [ ( "moduleName", Node.encode ModuleName.encode moduleName ) 45 | , ( "moduleAlias" 46 | , moduleAlias 47 | |> Maybe.map (Node.encode ModuleName.encode) 48 | |> Maybe.withDefault JE.null 49 | ) 50 | , ( "exposingList" 51 | , exposingList 52 | |> Maybe.map (Node.encode Exposing.encode) 53 | |> Maybe.withDefault JE.null 54 | ) 55 | ] 56 | 57 | 58 | {-| JSON decoder for a `Import` syntax element. 59 | -} 60 | decoder : Decoder Import 61 | decoder = 62 | JD.map3 Import 63 | (JD.field "moduleName" <| Node.decoder ModuleName.decoder) 64 | (JD.field "moduleAlias" (JD.nullable <| Node.decoder ModuleName.decoder)) 65 | (JD.field "exposingList" (JD.nullable <| Node.decoder Exposing.decoder)) 66 | -------------------------------------------------------------------------------- /src/Elm/Processing.elm: -------------------------------------------------------------------------------- 1 | module Elm.Processing exposing 2 | ( ProcessContext 3 | , init, addFile, addDependency, process 4 | ) 5 | 6 | {-| Processing raw files with the context of other files and dependencies. 7 | 8 | Since v7.3.3, post-processing is unnecessary. 9 | Use [`Elm.Parser.parseToFile`](Elm-Parser#parseToFile) instead. 10 | 11 | 12 | ## Types 13 | 14 | @docs ProcessContext 15 | 16 | 17 | ## Functions 18 | 19 | @docs init, addFile, addDependency, process 20 | 21 | -} 22 | 23 | import Dict exposing (Dict) 24 | import Elm.Dependency exposing (Dependency) 25 | import Elm.Interface as Interface exposing (Interface) 26 | import Elm.Internal.RawFile as InternalRawFile 27 | import Elm.RawFile as RawFile 28 | import Elm.Syntax.File exposing (File) 29 | import Elm.Syntax.ModuleName exposing (ModuleName) 30 | 31 | 32 | {-| Opaque type to hold context for the processing 33 | -} 34 | type ProcessContext 35 | = ProcessContext ModuleIndexInner 36 | 37 | 38 | type alias ModuleIndexInner = 39 | Dict ModuleName Interface 40 | 41 | 42 | {-| Initialise an empty context 43 | -} 44 | init : ProcessContext 45 | init = 46 | ProcessContext Dict.empty 47 | 48 | 49 | {-| Add a file to the context that may be a dependency for the file that will be processed. 50 | -} 51 | addFile : RawFile.RawFile -> ProcessContext -> ProcessContext 52 | addFile file (ProcessContext context) = 53 | ProcessContext 54 | (Dict.insert 55 | (RawFile.moduleName file) 56 | (Interface.build file) 57 | context 58 | ) 59 | 60 | 61 | {-| Add a whole dependency with its modules to the context. 62 | -} 63 | addDependency : Dependency -> ProcessContext -> ProcessContext 64 | addDependency dep (ProcessContext x) = 65 | ProcessContext (Dict.union dep.interfaces x) 66 | 67 | 68 | {-| Process a rawfile with a context. 69 | -} 70 | process : ProcessContext -> RawFile.RawFile -> File 71 | process _ (InternalRawFile.Raw file) = 72 | file 73 | -------------------------------------------------------------------------------- /demo/src/NodeCollector.elm: -------------------------------------------------------------------------------- 1 | module NodeCollector exposing (collect) 2 | 3 | import Elm.Inspector 4 | import Elm.Syntax.Expression exposing (Expression) 5 | import Elm.Syntax.File exposing (File) 6 | import Elm.Syntax.Node as Node exposing (Node(..)) 7 | import Elm.Syntax.Pattern exposing (Pattern) 8 | 9 | 10 | nodeTransformer : Elm.Inspector.Order Context (Node a) 11 | nodeTransformer = 12 | Elm.Inspector.Post addNode 13 | 14 | 15 | nodeTransformer2 = 16 | Elm.Inspector.Post (\( a, b ) c -> c |> addNode a |> addNode b) 17 | 18 | 19 | type alias Context = 20 | List (Node String) 21 | 22 | 23 | addNode : Node a -> Context -> Context 24 | addNode n c = 25 | Node.map Debug.toString n :: c 26 | 27 | 28 | onDestructuring : Node ( Node Pattern, Node Expression ) -> Context -> Context 29 | onDestructuring ((Node _ ( a, b )) as c) context = 30 | context |> addNode c |> addNode a |> addNode b 31 | 32 | 33 | onFile : File -> Context -> Context 34 | onFile f c = 35 | c 36 | |> (\context -> List.foldl addNode context f.comments) 37 | |> addNode f.moduleDefinition 38 | 39 | 40 | collect file = 41 | Elm.Inspector.inspect 42 | { onFile = Elm.Inspector.Post onFile 43 | , onImport = nodeTransformer 44 | , onFunction = nodeTransformer 45 | , onSignature = nodeTransformer 46 | , onPortDeclaration = nodeTransformer 47 | , onTypeAlias = nodeTransformer 48 | , onDestructuring = Elm.Inspector.Post onDestructuring 49 | , onInfixDeclaration = nodeTransformer 50 | , onExpression = nodeTransformer 51 | , onOperatorApplication = Elm.Inspector.Continue 52 | , onTypeAnnotation = nodeTransformer 53 | , onType = nodeTransformer 54 | , onLambda = Elm.Inspector.Continue 55 | , onLetBlock = Elm.Inspector.Continue 56 | , onCase = Elm.Inspector.Continue 57 | , onFunctionOrValue = Elm.Inspector.Continue 58 | , onRecordAccess = Elm.Inspector.Continue 59 | , onRecordUpdate = Elm.Inspector.Continue 60 | } 61 | file 62 | [] 63 | -------------------------------------------------------------------------------- /src/Elm/Syntax/TypeAlias.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.TypeAlias exposing 2 | ( TypeAlias 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents type aliases. 7 | For example: 8 | 9 | {-| This is a person 10 | -} 11 | type alias Person = 12 | { name : String 13 | , age : Int 14 | } 15 | 16 | 17 | ## Types 18 | 19 | @docs TypeAlias 20 | 21 | 22 | ## Serialization 23 | 24 | @docs encode, decoder 25 | 26 | -} 27 | 28 | import Elm.Syntax.Documentation as Documentation exposing (Documentation) 29 | import Elm.Syntax.Node as Node exposing (Node) 30 | import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation) 31 | import Json.Decode as JD exposing (Decoder) 32 | import Json.Encode as JE exposing (Value) 33 | 34 | 35 | {-| Type alias that defines the syntax for a type alias. 36 | A bit meta, but you get the idea. All information that you can define in a type alias is embedded. 37 | -} 38 | type alias TypeAlias = 39 | { documentation : Maybe (Node Documentation) 40 | , name : Node String 41 | , generics : List (Node String) 42 | , typeAnnotation : Node TypeAnnotation 43 | } 44 | 45 | 46 | 47 | -- Serialization 48 | 49 | 50 | {-| Encode a `TypeAlias` syntax element to JSON. 51 | -} 52 | encode : TypeAlias -> Value 53 | encode { documentation, name, generics, typeAnnotation } = 54 | JE.object 55 | [ ( "documentation", Maybe.map (Node.encode Documentation.encode) documentation |> Maybe.withDefault JE.null ) 56 | , ( "name", Node.encode JE.string name ) 57 | , ( "generics", JE.list (Node.encode JE.string) generics ) 58 | , ( "typeAnnotation", Node.encode TypeAnnotation.encode typeAnnotation ) 59 | ] 60 | 61 | 62 | {-| JSON decoder for a `Declaration` syntax element. 63 | -} 64 | decoder : Decoder TypeAlias 65 | decoder = 66 | JD.map4 TypeAlias 67 | (JD.field "documentation" (JD.nullable <| Node.decoder Documentation.decoder)) 68 | (JD.field "name" <| Node.decoder JD.string) 69 | (JD.field "generics" (JD.list <| Node.decoder JD.string)) 70 | (JD.field "typeAnnotation" (Node.decoder TypeAnnotation.decoder)) 71 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Node.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Node exposing 2 | ( Node(..) 3 | , empty, combine, range, value, map 4 | , encode, decoder 5 | ) 6 | 7 | {-| Represents a `Node` of the AST (Abstract Syntax Tree). 8 | 9 | The purpose of this type is to add the information of the [`Range`](./Elm-Syntax-Range), i.e. where in the source code the 10 | element of the tree was found. 11 | 12 | 13 | ## Types 14 | 15 | @docs Node 16 | 17 | 18 | ## Functions 19 | 20 | @docs empty, combine, range, value, map 21 | 22 | 23 | ## Serialization 24 | 25 | @docs encode, decoder 26 | 27 | -} 28 | 29 | import Elm.Syntax.Range as Range exposing (Range) 30 | import Json.Decode as JD exposing (Decoder) 31 | import Json.Encode as JE exposing (Value) 32 | 33 | 34 | {-| Base representation for a syntax node in a source file. 35 | -} 36 | type Node a 37 | = Node Range a 38 | 39 | 40 | {-| Create a Node with an empty range. 41 | -} 42 | empty : a -> Node a 43 | empty a = 44 | Node Range.empty a 45 | 46 | 47 | {-| Combine two nodes, constructing a new node which will have the outer most range of the child nodes 48 | -} 49 | combine : (Node a -> Node b -> c) -> Node a -> Node b -> Node c 50 | combine f ((Node { start } _) as a) ((Node { end } _) as b) = 51 | Node 52 | { start = start, end = end } 53 | (f a b) 54 | 55 | 56 | {-| Map the value within a node leaving the range untouched 57 | -} 58 | map : (a -> b) -> Node a -> Node b 59 | map f (Node r a) = 60 | Node r (f a) 61 | 62 | 63 | {-| Extract the range out of a `Node a` 64 | -} 65 | range : Node a -> Range 66 | range (Node r _) = 67 | r 68 | 69 | 70 | {-| Extract the value (`a`) out of a `Node a` 71 | -} 72 | value : Node a -> a 73 | value (Node _ v) = 74 | v 75 | 76 | 77 | {-| Encode a `Node` into JSON 78 | -} 79 | encode : (a -> Value) -> Node a -> Value 80 | encode f (Node r v) = 81 | JE.object 82 | [ ( "range", Range.encode r ) 83 | , ( "value", f v ) 84 | ] 85 | 86 | 87 | {-| A JSON decoder for `Node` 88 | -} 89 | decoder : Decoder a -> Decoder (Node a) 90 | decoder sub = 91 | JD.map2 Node 92 | (JD.field "range" Range.decoder) 93 | (JD.field "value" sub) 94 | -------------------------------------------------------------------------------- /src/Rope.elm: -------------------------------------------------------------------------------- 1 | module Rope exposing (Rope, RopeFilled(..), empty, filledPrependTo, one, prependTo, prependToFilled, toList) 2 | 3 | {-| inspired by [miniBill/elm-rope](https://dark.elm.dmy.fr/packages/miniBill/elm-rope/latest/) 4 | -} 5 | 6 | 7 | type alias Rope a = 8 | Maybe (RopeFilled a) 9 | 10 | 11 | type RopeFilled a 12 | = Leaf a () 13 | | Branch2 (RopeFilled a) (RopeFilled a) 14 | 15 | 16 | empty : Rope a 17 | empty = 18 | Nothing 19 | 20 | 21 | one : a -> RopeFilled a 22 | one onlyElement = 23 | Leaf onlyElement () 24 | 25 | 26 | filledPrependTo : Rope a -> RopeFilled a -> Rope a 27 | filledPrependTo right leftLikelyFilled = 28 | case right of 29 | Nothing -> 30 | Just leftLikelyFilled 31 | 32 | Just rightLikelyFilled -> 33 | Just (Branch2 leftLikelyFilled rightLikelyFilled) 34 | 35 | 36 | prependToFilled : RopeFilled a -> Rope a -> Rope a 37 | prependToFilled rightLikelyFilled left = 38 | case left of 39 | Nothing -> 40 | Just rightLikelyFilled 41 | 42 | Just leftLikelyFilled -> 43 | Just (Branch2 leftLikelyFilled rightLikelyFilled) 44 | 45 | 46 | prependTo : Rope a -> Rope a -> Rope a 47 | prependTo right left = 48 | case left of 49 | Nothing -> 50 | right 51 | 52 | Just leftLikelyFilled -> 53 | case right of 54 | Nothing -> 55 | left 56 | 57 | Just rightLikelyFilled -> 58 | Just (Branch2 leftLikelyFilled rightLikelyFilled) 59 | 60 | 61 | toList : Rope a -> List a 62 | toList rope = 63 | case rope of 64 | Nothing -> 65 | [] 66 | 67 | Just ropeLikelyFilled -> 68 | ropeLikelyFilledToListInto [] ropeLikelyFilled 69 | 70 | 71 | ropeLikelyFilledToListInto : List a -> RopeFilled a -> List a 72 | ropeLikelyFilledToListInto initialAcc ropeLikelyFilled = 73 | case ropeLikelyFilled of 74 | Leaf onlyElement () -> 75 | onlyElement :: initialAcc 76 | 77 | Branch2 left right -> 78 | ropeLikelyFilledToListInto 79 | (ropeLikelyFilledToListInto 80 | initialAcc 81 | right 82 | ) 83 | left 84 | -------------------------------------------------------------------------------- /src/ParserWithComments.elm: -------------------------------------------------------------------------------- 1 | module ParserWithComments exposing 2 | ( Comments 3 | , WithComments 4 | , many 5 | , manyWithoutReverse 6 | , until 7 | ) 8 | 9 | import Elm.Syntax.Node exposing (Node) 10 | import ParserFast exposing (Parser) 11 | import Rope exposing (Rope) 12 | 13 | 14 | type alias WithComments res = 15 | { comments : Comments, syntax : res } 16 | 17 | 18 | type alias Comments = 19 | Rope (Node String) 20 | 21 | 22 | until : Parser () -> Parser (WithComments a) -> Parser (WithComments (List a)) 23 | until end element = 24 | ParserFast.loopUntil 25 | end 26 | element 27 | ( Rope.empty, [] ) 28 | (\pResult ( commentsSoFar, itemsSoFar ) -> 29 | ( commentsSoFar |> Rope.prependTo pResult.comments 30 | , pResult.syntax :: itemsSoFar 31 | ) 32 | ) 33 | (\( commentsSoFar, itemsSoFar ) -> 34 | { comments = commentsSoFar 35 | , syntax = List.reverse itemsSoFar 36 | } 37 | ) 38 | 39 | 40 | many : Parser (WithComments a) -> Parser (WithComments (List a)) 41 | many p = 42 | ParserFast.loopWhileSucceeds p 43 | ( Rope.empty, [] ) 44 | (\pResult ( commentsSoFar, itemsSoFar ) -> 45 | ( commentsSoFar |> Rope.prependTo pResult.comments 46 | , pResult.syntax :: itemsSoFar 47 | ) 48 | ) 49 | (\( commentsSoFar, itemsSoFar ) -> 50 | { comments = commentsSoFar 51 | , syntax = List.reverse itemsSoFar 52 | } 53 | ) 54 | 55 | 56 | {-| Same as [`many`](#many), except that it doesn't reverse the list. 57 | This can be useful if you need to access the range of the last item. 58 | 59 | Mind you the comments will be reversed either way 60 | 61 | -} 62 | manyWithoutReverse : Parser (WithComments a) -> Parser (WithComments (List a)) 63 | manyWithoutReverse p = 64 | ParserFast.loopWhileSucceeds p 65 | ( Rope.empty, [] ) 66 | (\pResult ( commentsSoFar, itemsSoFar ) -> 67 | ( commentsSoFar |> Rope.prependTo pResult.comments 68 | , pResult.syntax :: itemsSoFar 69 | ) 70 | ) 71 | (\( commentsSoFar, itemsSoFar ) -> 72 | { comments = commentsSoFar 73 | , syntax = itemsSoFar 74 | } 75 | ) 76 | -------------------------------------------------------------------------------- /src/Elm/Parser/File.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.File exposing (file) 2 | 3 | import Elm.Parser.Comments as Comments 4 | import Elm.Parser.Declarations exposing (declaration) 5 | import Elm.Parser.Imports exposing (importDefinition) 6 | import Elm.Parser.Layout as Layout 7 | import Elm.Parser.Modules exposing (moduleDefinition) 8 | import Elm.Syntax.Declaration exposing (Declaration) 9 | import Elm.Syntax.File exposing (File) 10 | import Elm.Syntax.Node exposing (Node) 11 | import ParserFast exposing (Parser) 12 | import ParserWithComments exposing (WithComments) 13 | import Rope 14 | 15 | 16 | file : ParserFast.Parser File 17 | file = 18 | ParserFast.map4 19 | (\moduleDefinition moduleComments imports declarations -> 20 | { moduleDefinition = moduleDefinition.syntax 21 | , imports = imports.syntax 22 | , declarations = declarations.syntax 23 | , comments = 24 | moduleDefinition.comments 25 | |> Rope.prependTo moduleComments 26 | |> Rope.prependTo imports.comments 27 | |> Rope.prependTo declarations.comments 28 | |> Rope.toList 29 | } 30 | ) 31 | (Layout.layoutStrictFollowedByWithComments 32 | moduleDefinition 33 | ) 34 | (Layout.layoutStrictFollowedByComments 35 | (ParserFast.map2OrSucceed 36 | (\moduleDocumentation commentsAfter -> 37 | Rope.one moduleDocumentation |> Rope.filledPrependTo commentsAfter 38 | ) 39 | Comments.moduleDocumentation 40 | Layout.layoutStrict 41 | Rope.empty 42 | ) 43 | ) 44 | (ParserWithComments.many importDefinition) 45 | fileDeclarations 46 | 47 | 48 | fileDeclarations : Parser (WithComments (List (Node Declaration))) 49 | fileDeclarations = 50 | ParserWithComments.many 51 | (Layout.moduleLevelIndentationFollowedBy 52 | (ParserFast.map2 53 | (\declarationParsed commentsAfter -> 54 | { comments = declarationParsed.comments |> Rope.prependTo commentsAfter 55 | , syntax = declarationParsed.syntax 56 | } 57 | ) 58 | declaration 59 | Layout.optimisticLayout 60 | ) 61 | ) 62 | -------------------------------------------------------------------------------- /src/Elm/Parser/Comments.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.Comments exposing (declarationDocumentation, moduleDocumentation, multilineComment, singleLineComment) 2 | 3 | import Char.Extra 4 | import Elm.Syntax.Documentation exposing (Documentation) 5 | import Elm.Syntax.Node exposing (Node(..)) 6 | import ParserFast exposing (Parser) 7 | 8 | 9 | singleLineComment : ParserFast.Parser (Node String) 10 | singleLineComment = 11 | ParserFast.symbolFollowedBy "--" 12 | (ParserFast.whileMapWithRange 13 | (\c -> 14 | case c of 15 | '\u{000D}' -> 16 | False 17 | 18 | '\n' -> 19 | False 20 | 21 | _ -> 22 | not (Char.Extra.isUtf16Surrogate c) 23 | ) 24 | (\range content -> 25 | Node 26 | { start = { row = range.start.row, column = range.start.column - 2 } 27 | , end = 28 | { row = range.start.row 29 | , column = range.end.column 30 | } 31 | } 32 | ("--" ++ content) 33 | ) 34 | ) 35 | 36 | 37 | multilineComment : ParserFast.Parser (Node String) 38 | multilineComment = 39 | ParserFast.offsetSourceAndThen 40 | (\offset source -> 41 | case String.slice (offset + 2) (offset + 3) source of 42 | "|" -> 43 | problemUnexpectedDocumentation 44 | 45 | _ -> 46 | multiLineCommentNoCheck 47 | ) 48 | 49 | 50 | problemUnexpectedDocumentation : Parser a 51 | problemUnexpectedDocumentation = 52 | ParserFast.problem "unexpected documentation comment" 53 | 54 | 55 | multiLineCommentNoCheck : Parser (Node String) 56 | multiLineCommentNoCheck = 57 | ParserFast.nestableMultiCommentMapWithRange Node 58 | ( '{', "-" ) 59 | ( '-', "}" ) 60 | 61 | 62 | moduleDocumentation : Parser (Node String) 63 | moduleDocumentation = 64 | declarationDocumentation 65 | 66 | 67 | declarationDocumentation : ParserFast.Parser (Node Documentation) 68 | declarationDocumentation = 69 | -- technically making the whole parser fail on multi-line comments would be "correct" 70 | -- but in practice, all declaration comments allow layout before which already handles 71 | -- these. 72 | multiLineCommentNoCheck 73 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Infix.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Infix exposing 2 | ( Infix, InfixDirection(..) 3 | , encode, encodeDirection, decoder, decodeDirection 4 | ) 5 | 6 | {-| 7 | 8 | 9 | ## Types 10 | 11 | @docs Infix, InfixDirection 12 | 13 | 14 | ## Serialization 15 | 16 | @docs encode, encodeDirection, decoder, decodeDirection 17 | 18 | -} 19 | 20 | import Elm.Syntax.Node as Node exposing (Node) 21 | import Json.Decode as JD exposing (Decoder) 22 | import Json.Encode as JE exposing (Value) 23 | 24 | 25 | {-| Type annotation for a infix definition 26 | -} 27 | type alias Infix = 28 | { direction : Node InfixDirection 29 | , precedence : Node Int 30 | , operator : Node String 31 | , function : Node String 32 | } 33 | 34 | 35 | {-| Union type for infix direction 36 | -} 37 | type InfixDirection 38 | = Left 39 | | Right 40 | | Non 41 | 42 | 43 | {-| Encode an infix 44 | -} 45 | encode : Infix -> Value 46 | encode inf = 47 | JE.object 48 | [ ( "direction", Node.encode encodeDirection inf.direction ) 49 | , ( "precedence", Node.encode JE.int inf.precedence ) 50 | , ( "operator", Node.encode JE.string inf.operator ) 51 | , ( "function", Node.encode JE.string inf.function ) 52 | ] 53 | 54 | 55 | {-| Encode the infix direction 56 | -} 57 | encodeDirection : InfixDirection -> Value 58 | encodeDirection d = 59 | case d of 60 | Left -> 61 | JE.string "left" 62 | 63 | Right -> 64 | JE.string "right" 65 | 66 | Non -> 67 | JE.string "non" 68 | 69 | 70 | {-| Decode an infix 71 | -} 72 | decoder : Decoder Infix 73 | decoder = 74 | JD.map4 Infix 75 | (JD.field "direction" (Node.decoder decodeDirection)) 76 | (JD.field "precedence" (Node.decoder JD.int)) 77 | (JD.field "operator" (Node.decoder JD.string)) 78 | (JD.field "function" (Node.decoder JD.string)) 79 | 80 | 81 | {-| Decode a infix direction 82 | -} 83 | decodeDirection : Decoder InfixDirection 84 | decodeDirection = 85 | JD.string 86 | |> JD.andThen 87 | (\v -> 88 | case v of 89 | "left" -> 90 | JD.succeed Left 91 | 92 | "right" -> 93 | JD.succeed Right 94 | 95 | "non" -> 96 | JD.succeed Non 97 | 98 | _ -> 99 | JD.fail "Invalid direction" 100 | ) 101 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Type.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Type exposing 2 | ( Type, ValueConstructor 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents custom types. 7 | For example: 8 | 9 | {-| This is a color 10 | -} 11 | type Color 12 | = Blue 13 | | Red 14 | 15 | 16 | ## Types 17 | 18 | @docs Type, ValueConstructor 19 | 20 | 21 | ## Serialization 22 | 23 | @docs encode, decoder 24 | 25 | -} 26 | 27 | import Elm.Syntax.Documentation as Documentation exposing (Documentation) 28 | import Elm.Syntax.Node as Node exposing (Node) 29 | import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation) 30 | import Json.Decode as JD exposing (Decoder) 31 | import Json.Encode as JE exposing (Value) 32 | 33 | 34 | {-| Type alias that defines the syntax for a custom type. 35 | All information that you can define in a type alias is embedded. 36 | -} 37 | type alias Type = 38 | { documentation : Maybe (Node Documentation) 39 | , name : Node String 40 | , generics : List (Node String) 41 | , constructors : List (Node ValueConstructor) 42 | } 43 | 44 | 45 | {-| Syntax for a custom type value constructor. 46 | -} 47 | type alias ValueConstructor = 48 | { name : Node String 49 | , arguments : List (Node TypeAnnotation) 50 | } 51 | 52 | 53 | 54 | -- Serialization 55 | 56 | 57 | {-| Encode a `Type` syntax element to JSON. 58 | -} 59 | encode : Type -> Value 60 | encode { documentation, name, generics, constructors } = 61 | JE.object 62 | [ ( "documentation", Maybe.map (Node.encode Documentation.encode) documentation |> Maybe.withDefault JE.null ) 63 | , ( "name", Node.encode JE.string name ) 64 | , ( "generics", JE.list (Node.encode JE.string) generics ) 65 | , ( "constructors", JE.list (Node.encode encodeValueConstructor) constructors ) 66 | ] 67 | 68 | 69 | encodeValueConstructor : ValueConstructor -> Value 70 | encodeValueConstructor { name, arguments } = 71 | JE.object 72 | [ ( "name", Node.encode JE.string name ) 73 | , ( "arguments", JE.list (Node.encode TypeAnnotation.encode) arguments ) 74 | ] 75 | 76 | 77 | {-| JSON decoder for a `Type` syntax element. 78 | -} 79 | decoder : Decoder Type 80 | decoder = 81 | JD.map4 Type 82 | (JD.field "documentation" <| JD.nullable <| Node.decoder JD.string) 83 | (JD.field "name" <| Node.decoder JD.string) 84 | (JD.field "generics" (JD.list (Node.decoder JD.string))) 85 | (JD.field "constructors" (JD.list (Node.decoder valueConstructorDecoder))) 86 | 87 | 88 | valueConstructorDecoder : Decoder ValueConstructor 89 | valueConstructorDecoder = 90 | JD.map2 ValueConstructor 91 | (JD.field "name" (Node.decoder JD.string)) 92 | (JD.field "arguments" (JD.list (Node.decoder TypeAnnotation.decoder))) 93 | -------------------------------------------------------------------------------- /review/src/ReviewConfig.elm: -------------------------------------------------------------------------------- 1 | module ReviewConfig exposing (config) 2 | 3 | {-| Do not rename the ReviewConfig module or the config function, because 4 | `elm-review` will look for these. 5 | 6 | To add packages that contain rules, add them to this review project using 7 | 8 | `elm install author/packagename` 9 | 10 | when inside the directory containing this file. 11 | 12 | -} 13 | 14 | import AstFormatting 15 | import Docs.NoMissing exposing (exposedModules, onlyExposed) 16 | import Docs.ReviewAtDocs 17 | import Docs.ReviewLinksAndSections 18 | import Docs.UpToDateReadmeLinks 19 | import NoConfusingPrefixOperator 20 | import NoDebug.Log 21 | import NoDebug.TodoOrToString 22 | import NoDeprecated 23 | import NoExposingEverything 24 | import NoImportingEverything 25 | import NoMissingTypeAnnotation 26 | import NoMissingTypeAnnotationInLetIn 27 | import NoMissingTypeExpose 28 | import NoPrematureLetComputation 29 | import NoSimpleLetBody 30 | import NoUnsortedRecords 31 | import NoUnused.CustomTypeConstructorArgs 32 | import NoUnused.CustomTypeConstructors 33 | import NoUnused.Dependencies 34 | import NoUnused.Exports exposing (annotatedBy) 35 | import NoUnused.Parameters 36 | import NoUnused.Patterns 37 | import NoUnused.Variables 38 | import Review.Rule as Rule exposing (Rule) 39 | import Simplify 40 | 41 | 42 | config : List Rule 43 | config = 44 | [ Docs.NoMissing.rule 45 | { document = onlyExposed 46 | , from = exposedModules 47 | } 48 | , Docs.ReviewLinksAndSections.rule 49 | |> Rule.ignoreErrorsForFiles [ "src/Pratt.elm" ] 50 | , Docs.ReviewAtDocs.rule 51 | , Docs.UpToDateReadmeLinks.rule 52 | , NoConfusingPrefixOperator.rule 53 | , NoDebug.Log.rule 54 | , NoDebug.TodoOrToString.rule 55 | |> Rule.ignoreErrorsForDirectories [ "tests/" ] 56 | , NoDeprecated.rule NoDeprecated.defaults 57 | , NoExposingEverything.rule 58 | , NoImportingEverything.rule [] 59 | |> Rule.ignoreErrorsForDirectories [ "tests/" ] 60 | , NoMissingTypeAnnotation.rule 61 | , NoMissingTypeAnnotationInLetIn.rule 62 | , NoMissingTypeExpose.rule 63 | , NoSimpleLetBody.rule 64 | , NoPrematureLetComputation.rule 65 | , NoUnused.CustomTypeConstructors.rule [] 66 | , NoUnused.CustomTypeConstructorArgs.rule 67 | , NoUnused.Dependencies.rule 68 | , NoUnused.Exports.defaults 69 | |> NoUnused.Exports.reportUnusedProductionExports 70 | { isProductionFile = .isInSourceDirectories 71 | , exceptionsAre = [ annotatedBy "@test-helper" ] 72 | } 73 | |> NoUnused.Exports.toRule 74 | |> Rule.ignoreErrorsForDirectories [ "src/Benchmarks" ] 75 | , NoUnused.Parameters.rule 76 | , NoUnused.Patterns.rule 77 | , NoUnused.Variables.rule 78 | , Simplify.rule Simplify.defaults 79 | , AstFormatting.rule 80 | , NoUnsortedRecords.rule NoUnsortedRecords.defaults 81 | ] 82 | -------------------------------------------------------------------------------- /package-tests.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | 3 | var packages = []; 4 | var packageVersions = []; 5 | var modules = []; 6 | 7 | 8 | const charsPerMilli = []; 9 | const Elm = require('./parse'); 10 | 11 | function handleModules(artifact, version, cb) { 12 | const mod = modules.shift(); 13 | if (!mod) { 14 | cb(); 15 | return; 16 | } 17 | 18 | 19 | const fileName= mod.replace(new RegExp('\\.', 'g'), '/'); 20 | 21 | request(`https://raw.githubusercontent.com/${artifact.name}/${version}/src/${fileName}.elm`, function(err, res, body) { 22 | if (res.statusCode != 200) { 23 | console.log(`> Could not load ${artifact.name}@${version} | ${mod}`); 24 | cb(); 25 | return; 26 | } 27 | const name = `${artifact.name}@${version} | ${mod}`; 28 | const before = new Date().getTime(); 29 | Elm.Elm.ParseTest.init({ flags : { 30 | body: body, 31 | name: name 32 | }}); 33 | const after = new Date().getTime(); 34 | const millis = after - before 35 | charsPerMilli.push(body.length / millis); 36 | handleModules(artifact, version, cb); 37 | }) 38 | 39 | } 40 | 41 | function analyseVersion(artifact, version, definition, cb) { 42 | modules = []; 43 | if (!Array.isArray(definition['exposed-modules'])) { 44 | Object.keys(definition['exposed-modules']).forEach(key => { 45 | modules = modules.concat(definition['exposed-modules'][key]); 46 | }); 47 | } else { 48 | modules = definition['exposed-modules']; 49 | } 50 | handleModules(artifact, version, cb) 51 | } 52 | 53 | function handleNextVersion(next, cb) { 54 | const nextVersion = packageVersions.shift(); 55 | if (!nextVersion) { 56 | cb(); 57 | return; 58 | } 59 | 60 | request(`https://github.com/${next.name}/raw/${nextVersion}/elm.json`, function(err, res, body) { 61 | if (res.statusCode !== 200) { 62 | console.log(`> ${next.name}@${nextVersion} Could not fetch elm.json`); 63 | handleNextVersion(next, cb); 64 | return; 65 | } 66 | 67 | analyseVersion(next, nextVersion, JSON.parse(body), function() { 68 | handleNextVersion(next, cb); 69 | }); 70 | }); 71 | 72 | } 73 | 74 | function analysePackage(next, cb) { 75 | packageVersions = next.versions; 76 | 77 | handleNextVersion(next, function() { 78 | cb(); 79 | return; 80 | }) 81 | } 82 | 83 | function handleNextPackage() { 84 | const next = packages.shift(); 85 | 86 | if (!next) { 87 | const averageCharsPerMilli = charsPerMilli.reduce((a, b ) => a + b, 0) / charsPerMilli.length; 88 | console.log("Average char per milli:",averageCharsPerMilli); 89 | return; 90 | } 91 | 92 | analysePackage(next, function() { 93 | handleNextPackage(); 94 | }); 95 | } 96 | 97 | 98 | request('https://package.elm-lang.org/search.json', function(err, response, body) { 99 | packages = JSON.parse(body); 100 | 101 | handleNextPackage() 102 | }) 103 | -------------------------------------------------------------------------------- /tests/Elm/Parser/InfixTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.InfixTests exposing (all) 2 | 3 | import Elm.Parser.Declarations exposing (declaration) 4 | import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil 5 | import Elm.Syntax.Declaration as Declaration exposing (Declaration) 6 | import Elm.Syntax.Infix exposing (..) 7 | import Elm.Syntax.Node exposing (Node(..)) 8 | import Expect 9 | import Test exposing (..) 10 | 11 | 12 | all : Test 13 | all = 14 | describe "InfixTests" 15 | [ test "right infix" <| 16 | \() -> 17 | "infix right 7 () = slash" 18 | |> expectAst 19 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 28 } } 20 | (Declaration.InfixDeclaration 21 | { direction = Node { start = { row = 1, column = 7 }, end = { row = 1, column = 12 } } Right 22 | , precedence = Node { start = { row = 1, column = 13 }, end = { row = 1, column = 14 } } 7 23 | , operator = Node { start = { row = 1, column = 15 }, end = { row = 1, column = 20 } } "" 24 | , function = Node { start = { row = 1, column = 23 }, end = { row = 1, column = 28 } } "slash" 25 | } 26 | ) 27 | ) 28 | , test "left infix" <| 29 | \() -> 30 | "infix left 8 () = questionMark" 31 | |> expectAst 32 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 35 } } 33 | (Declaration.InfixDeclaration 34 | { direction = Node { start = { row = 1, column = 7 }, end = { row = 1, column = 11 } } Left 35 | , precedence = Node { start = { row = 1, column = 13 }, end = { row = 1, column = 14 } } 8 36 | , operator = Node { start = { row = 1, column = 15 }, end = { row = 1, column = 20 } } "" 37 | , function = Node { start = { row = 1, column = 23 }, end = { row = 1, column = 35 } } "questionMark" 38 | } 39 | ) 40 | ) 41 | , test "non infix" <| 42 | \() -> 43 | "infix non 4 (==) = eq" 44 | |> expectAst 45 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 24 } } 46 | (Declaration.InfixDeclaration 47 | { direction = Node { start = { row = 1, column = 7 }, end = { row = 1, column = 10 } } Non 48 | , precedence = Node { start = { row = 1, column = 13 }, end = { row = 1, column = 14 } } 4 49 | , operator = Node { start = { row = 1, column = 15 }, end = { row = 1, column = 19 } } "==" 50 | , function = Node { start = { row = 1, column = 22 }, end = { row = 1, column = 24 } } "eq" 51 | } 52 | ) 53 | ) 54 | ] 55 | 56 | 57 | expectAst : Node Declaration -> String -> Expect.Expectation 58 | expectAst = 59 | ParserWithCommentsUtil.expectAst declaration 60 | -------------------------------------------------------------------------------- /demo/src/Demo.elm: -------------------------------------------------------------------------------- 1 | module Demo exposing (main) 2 | 3 | import Browser 4 | import Elm.Parser 5 | import Elm.Processing 6 | import Elm.RawFile as Elm 7 | import Elm.Syntax.File exposing (File) 8 | import Html exposing (Html, text) 9 | import Html.Attributes 10 | import Html.Events 11 | import Json.Encode exposing (Value) 12 | import JsonTree 13 | import NodeCollector 14 | 15 | 16 | main = 17 | Browser.sandbox 18 | { init = init 19 | , view = view 20 | , update = update 21 | } 22 | 23 | 24 | type Msg 25 | = Change String 26 | | NoOp 27 | | TreeMsg JsonTree.State 28 | | SelectPath JsonTree.KeyPath 29 | 30 | 31 | type alias Model = 32 | { src : String 33 | , parseResult : 34 | Maybe (Result (List String) File) 35 | , value : Value 36 | , treeState : JsonTree.State 37 | , treeValue : Maybe JsonTree.Node 38 | , path : Maybe JsonTree.KeyPath 39 | } 40 | 41 | 42 | init : Model 43 | init = 44 | { src = "" 45 | , parseResult = Nothing 46 | , treeValue = Nothing 47 | , value = Json.Encode.null 48 | , treeState = JsonTree.defaultState 49 | , path = Nothing 50 | } 51 | |> update (Change """module Foo exposing (foo) 52 | 53 | -- some comment 54 | 55 | {-| Docs -} 56 | foo = 1 57 | """) 58 | 59 | 60 | update : Msg -> Model -> Model 61 | update msg model = 62 | case msg of 63 | SelectPath p -> 64 | { model | path = Just p } 65 | 66 | TreeMsg s -> 67 | { model | treeState = s } 68 | 69 | NoOp -> 70 | model 71 | 72 | Change v -> 73 | let 74 | parseResult = 75 | Elm.Parser.parse v 76 | |> Result.map (Elm.Processing.process Elm.Processing.init) 77 | |> Result.mapError (List.map Debug.toString) 78 | 79 | value = 80 | parseResult 81 | |> Result.toMaybe 82 | |> Maybe.map Elm.Syntax.File.encode 83 | |> Maybe.withDefault Json.Encode.null 84 | in 85 | { model 86 | | src = v 87 | , parseResult = Just parseResult 88 | , value = value 89 | , treeValue = JsonTree.parseValue value |> Result.toMaybe 90 | , treeState = JsonTree.defaultState 91 | } 92 | 93 | 94 | config = 95 | { onSelect = Just SelectPath 96 | , toMsg = TreeMsg 97 | } 98 | 99 | 100 | view : Model -> Html Msg 101 | view m = 102 | Html.div [] 103 | [ Html.div [] 104 | [ Html.textarea 105 | [ Html.Events.onInput Change 106 | , Html.Attributes.value m.src 107 | , Html.Attributes.cols 50 108 | , Html.Attributes.rows 20 109 | , Html.Attributes.style "font-family" "Courier New,Courier,Lucida Sans Typewriter,Lucida Typewriter,monospace" 110 | ] 111 | [] 112 | ] 113 | , m.parseResult 114 | |> Maybe.andThen Result.toMaybe 115 | |> Maybe.map NodeCollector.collect 116 | |> Maybe.withDefault [] 117 | |> List.map (\v -> Html.li [] [ Html.text (Debug.toString v) ]) 118 | |> Html.ul [] 119 | , m.treeValue 120 | |> Maybe.map (\tree -> JsonTree.view tree config m.treeState) 121 | |> Maybe.withDefault (text "Failed to parse JSON") 122 | , Html.div [] [ Html.text (Debug.toString m) ] 123 | ] 124 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Declaration.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Declaration exposing 2 | ( Declaration(..) 3 | , encode, decoder 4 | ) 5 | 6 | {-| Syntax for the different top-level declarations in Elm. 7 | These can be one of the following (all declared in `Declaration`): 8 | 9 | - Functions: `add x y = x + y` 10 | - Custom types: `type Color = Blue | Red` 11 | - Type aliases: `type alias Status = Int` 12 | - Port declaration: `port sendMessage: String -> Cmd msg` 13 | - Destructuring: `{name, age} = person` 14 | - Infix declarations. You will probably not need this, while only core packages can define these. 15 | 16 | 17 | ## Types 18 | 19 | @docs Declaration 20 | 21 | 22 | ## Serialization 23 | 24 | @docs encode, decoder 25 | 26 | -} 27 | 28 | import Elm.Json.Util exposing (decodeTyped, encodeTyped) 29 | import Elm.Syntax.Expression as Expression exposing (Expression, Function) 30 | import Elm.Syntax.Infix as Infix exposing (Infix) 31 | import Elm.Syntax.Node as Node exposing (Node) 32 | import Elm.Syntax.Pattern as Pattern exposing (Pattern) 33 | import Elm.Syntax.Signature as Signature exposing (Signature) 34 | import Elm.Syntax.Type as Type exposing (Type) 35 | import Elm.Syntax.TypeAlias as TypeAlias exposing (TypeAlias) 36 | import Json.Decode as JD exposing (Decoder) 37 | import Json.Encode as JE exposing (Value) 38 | 39 | 40 | {-| Custom type that represents all different top-level declarations. 41 | -} 42 | type Declaration 43 | = FunctionDeclaration Function 44 | | AliasDeclaration TypeAlias 45 | | CustomTypeDeclaration Type 46 | | PortDeclaration Signature 47 | | InfixDeclaration Infix 48 | | Destructuring (Node Pattern) (Node Expression) 49 | 50 | 51 | 52 | -- Serialization 53 | 54 | 55 | {-| Encode a `Declaration` syntax element to JSON. 56 | -} 57 | encode : Declaration -> Value 58 | encode decl = 59 | case decl of 60 | FunctionDeclaration function -> 61 | encodeTyped "function" (Expression.encodeFunction function) 62 | 63 | AliasDeclaration typeAlias -> 64 | encodeTyped "typeAlias" (TypeAlias.encode typeAlias) 65 | 66 | CustomTypeDeclaration typeDeclaration -> 67 | encodeTyped "typedecl" (Type.encode typeDeclaration) 68 | 69 | PortDeclaration sig -> 70 | encodeTyped "port" (Signature.encode sig) 71 | 72 | InfixDeclaration inf -> 73 | encodeTyped "infix" 74 | (Infix.encode inf) 75 | 76 | Destructuring pattern expression -> 77 | encodeTyped "destructuring" 78 | (JE.object 79 | [ ( "pattern", Node.encode Pattern.encode pattern ) 80 | , ( "expression", Node.encode Expression.encode expression ) 81 | ] 82 | ) 83 | 84 | 85 | {-| JSON decoder for a `Declaration` syntax element. 86 | -} 87 | decoder : Decoder Declaration 88 | decoder = 89 | JD.lazy 90 | (\() -> 91 | decodeTyped 92 | [ ( "function", Expression.functionDecoder |> JD.map FunctionDeclaration ) 93 | , ( "typeAlias", TypeAlias.decoder |> JD.map AliasDeclaration ) 94 | , ( "typedecl", Type.decoder |> JD.map CustomTypeDeclaration ) 95 | , ( "port", Signature.decoder |> JD.map PortDeclaration ) 96 | , ( "infix", Infix.decoder |> JD.map InfixDeclaration ) 97 | , ( "destructuring", JD.map2 Destructuring (JD.field "pattern" (Node.decoder Pattern.decoder)) (JD.field "expression" (Node.decoder Expression.decoder)) ) 98 | ] 99 | ) 100 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Range.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Range exposing 2 | ( Range, Location 3 | , empty, combine 4 | , compare, compareLocations 5 | , encode, decoder 6 | , emptyRange 7 | ) 8 | 9 | {-| 10 | 11 | 12 | ## Types 13 | 14 | @docs Range, Location 15 | 16 | 17 | ## Functions 18 | 19 | @docs empty, combine 20 | 21 | 22 | ## Comparison 23 | 24 | See also [Basics.compare](https://package.elm-lang.org/packages/elm/core/latest/Basics#compare). 25 | 26 | @docs compare, compareLocations 27 | 28 | 29 | ## Serialization 30 | 31 | @docs encode, decoder 32 | 33 | 34 | ## Deprecated 35 | 36 | @docs emptyRange 37 | 38 | -} 39 | 40 | import Json.Decode as JD exposing (Decoder) 41 | import Json.Encode as JE exposing (Value) 42 | 43 | 44 | {-| Source location 45 | -} 46 | type alias Location = 47 | { row : Int 48 | , column : Int 49 | } 50 | 51 | 52 | {-| Range for a piece of code with a start and end 53 | -} 54 | type alias Range = 55 | { start : Location 56 | , end : Location 57 | } 58 | 59 | 60 | {-| Construct an empty range 61 | -} 62 | empty : Range 63 | empty = 64 | { start = { row = 0, column = 0 } 65 | , end = { row = 0, column = 0 } 66 | } 67 | 68 | 69 | {-| **@deprecated** Use [`empty`](#empty) instead. It does the same thing but the name is more Elm-y. 70 | 71 | Construct an empty range 72 | 73 | -} 74 | emptyRange : Range 75 | emptyRange = 76 | empty 77 | 78 | 79 | {-| Encode a range 80 | -} 81 | encode : Range -> Value 82 | encode { start, end } = 83 | JE.list JE.int 84 | [ start.row 85 | , start.column 86 | , end.row 87 | , end.column 88 | ] 89 | 90 | 91 | {-| Decode a range 92 | -} 93 | decoder : Decoder Range 94 | decoder = 95 | JD.list JD.int 96 | |> JD.andThen fromList 97 | 98 | 99 | fromList : List Int -> Decoder Range 100 | fromList input = 101 | case input of 102 | [ a, b, c, d ] -> 103 | JD.succeed 104 | { start = { row = a, column = b } 105 | , end = { row = c, column = d } 106 | } 107 | 108 | _ -> 109 | JD.fail "Invalid input list" 110 | 111 | 112 | {-| Compute the largest area of a list of ranges. 113 | -} 114 | combine : List Range -> Range 115 | combine ranges = 116 | case ranges of 117 | [] -> 118 | empty 119 | 120 | head :: tail -> 121 | combineHelp tail head.start head.end 122 | 123 | 124 | combineHelp : List Range -> Location -> Location -> Range 125 | combineHelp ranges previousStart previousEnd = 126 | case ranges of 127 | [] -> 128 | { start = previousStart, end = previousEnd } 129 | 130 | { start, end } :: rest -> 131 | let 132 | newStart : Location 133 | newStart = 134 | case compareLocations start previousStart of 135 | LT -> 136 | start 137 | 138 | _ -> 139 | previousStart 140 | 141 | newEnd : Location 142 | newEnd = 143 | case compareLocations end previousEnd of 144 | GT -> 145 | end 146 | 147 | _ -> 148 | previousEnd 149 | in 150 | combineHelp rest newStart newEnd 151 | 152 | 153 | {-| Compare the position of two Ranges. 154 | -} 155 | compare : Range -> Range -> Order 156 | compare left right = 157 | case compareLocations left.start right.start of 158 | EQ -> 159 | compareLocations left.end right.end 160 | 161 | order -> 162 | order 163 | 164 | 165 | {-| Compare two Locations. 166 | -} 167 | compareLocations : Location -> Location -> Order 168 | compareLocations left right = 169 | if left.row < right.row then 170 | LT 171 | 172 | else if left.row > right.row then 173 | GT 174 | 175 | else 176 | Basics.compare left.column right.column 177 | -------------------------------------------------------------------------------- /tests/Elm/Parser/CommentTest.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.CommentTest exposing (all) 2 | 3 | import Elm.Parser.Comments as Parser 4 | import Elm.Parser.ParserWithCommentsTestUtil exposing (..) 5 | import Elm.Syntax.Node exposing (Node(..)) 6 | import Expect 7 | import Parser 8 | import ParserFast 9 | import Rope 10 | import Test exposing (..) 11 | 12 | 13 | all : Test 14 | all = 15 | describe "CommentTests" 16 | [ test "singleLineComment" <| 17 | \() -> 18 | parseSingleLineComment "--bar" 19 | |> Expect.equal 20 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 6 } } "--bar")) 21 | , test "singleLineComment state" <| 22 | \() -> 23 | parseSingleLineComment "--bar" 24 | |> Expect.equal 25 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 6 } } "--bar")) 26 | , test "singleLineComment including 2-part utf-16 char range" <| 27 | \() -> 28 | parseSingleLineComment "--bar🔧" 29 | |> Expect.equal 30 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 7 } } "--bar🔧")) 31 | , test "singleLineComment does not include new line" <| 32 | \() -> 33 | parseSingleLineComment "--bar\n" 34 | |> Expect.err 35 | , test "multilineComment parse result" <| 36 | \() -> 37 | parseMultiLineComment "{-foo\nbar-}" 38 | |> Expect.equal 39 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 6 } } "{-foo\nbar-}")) 40 | , test "multilineComment range" <| 41 | \() -> 42 | parseMultiLineComment "{-foo\nbar-}" 43 | |> Expect.equal 44 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 6 } } "{-foo\nbar-}")) 45 | , test "multilineComment including 2-part utf-16 char range" <| 46 | \() -> 47 | parseMultiLineComment "{-foo\nbar🔧-}" 48 | |> Expect.equal 49 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 7 } } "{-foo\nbar🔧-}")) 50 | , test "nested multilineComment only open" <| 51 | \() -> 52 | parseMultiLineComment "{- {- -}" 53 | |> Expect.err 54 | , test "nested multilineComment open and close" <| 55 | \() -> 56 | parseMultiLineComment "{- {- -} -}" 57 | |> Expect.equal 58 | (Ok (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 12 } } "{- {- -} -}")) 59 | , test "multilineComment on module documentation" <| 60 | \() -> 61 | parseMultiLineComment "{-|foo\nbar-}" 62 | |> Expect.err 63 | , test "module documentation" <| 64 | \() -> 65 | parseWithState "{-|foo\nbar-}" (Parser.moduleDocumentation |> ParserFast.map (\c -> { comments = Just (Rope.one c), syntax = () })) 66 | |> Maybe.map .comments 67 | |> Expect.equal 68 | (Just [ Node { start = { row = 1, column = 1 }, end = { row = 2, column = 6 } } "{-|foo\nbar-}" ]) 69 | , test "module documentation can handle nested comments" <| 70 | \() -> 71 | parseWithState "{-| {- hello -} -}" (Parser.moduleDocumentation |> ParserFast.map (\c -> { comments = Just (Rope.one c), syntax = () })) 72 | |> Maybe.map .comments 73 | |> Expect.equal 74 | (Just [ Node { start = { row = 1, column = 1 }, end = { row = 1, column = 19 } } "{-| {- hello -} -}" ]) 75 | ] 76 | 77 | 78 | parseSingleLineComment : String -> Result (List Parser.DeadEnd) (Node String) 79 | parseSingleLineComment source = 80 | ParserFast.run 81 | Parser.singleLineComment 82 | source 83 | 84 | 85 | parseMultiLineComment : String -> Result (List Parser.DeadEnd) (Node String) 86 | parseMultiLineComment source = 87 | ParserFast.run 88 | Parser.multilineComment 89 | source 90 | -------------------------------------------------------------------------------- /tests/Elm/Parser/ParserWithCommentsTestUtil.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.ParserWithCommentsTestUtil exposing (expectAst, expectAstWithComments, expectAstWithIndent1, expectInvalid, parse, parseWithState) 2 | 3 | import Elm.Syntax.Node exposing (Node) 4 | import Expect 5 | import Parser 6 | import ParserFast 7 | import ParserWithComments exposing (WithComments) 8 | import Rope 9 | 10 | 11 | parseWithState : String -> ParserFast.Parser (WithComments a) -> Maybe { comments : List (Node String), syntax : a } 12 | parseWithState s p = 13 | case ParserFast.run p s of 14 | Err _ -> 15 | Nothing 16 | 17 | Ok commentsAndSyntax -> 18 | { comments = commentsAndSyntax.comments |> Rope.toList 19 | , syntax = commentsAndSyntax.syntax 20 | } 21 | |> Just 22 | 23 | 24 | parse : String -> ParserFast.Parser (WithComments a) -> Maybe a 25 | parse s p = 26 | parseWithState s p 27 | |> Maybe.map .syntax 28 | 29 | 30 | parseWithFailure : String -> ParserFast.Parser (WithComments a) -> Result (List Parser.DeadEnd) a 31 | parseWithFailure s p = 32 | case ParserFast.run p s of 33 | Err deadEnds -> 34 | Err deadEnds 35 | 36 | Ok commentsAndSyntax -> 37 | commentsAndSyntax.syntax |> Ok 38 | 39 | 40 | expectAstWithIndent1 : ParserFast.Parser (WithComments a) -> a -> String -> Expect.Expectation 41 | expectAstWithIndent1 parser = 42 | \expected source -> 43 | case ParserFast.run parser source of 44 | Err error -> 45 | Expect.fail ("Expected the source to be parsed correctly:\n" ++ Debug.toString error) 46 | 47 | Ok actual -> 48 | Expect.all 49 | [ \() -> actual.syntax |> Expect.equal expected 50 | , \() -> 51 | actual.comments 52 | |> Rope.toList 53 | |> Expect.equalLists [] 54 | |> Expect.onFail "This parser should not produce any comments. If this is expected, then you should use expectAstWithComments instead." 55 | ] 56 | () 57 | 58 | 59 | expectAst : ParserFast.Parser (WithComments a) -> a -> String -> Expect.Expectation 60 | expectAst parser = 61 | \expected source -> 62 | case ParserFast.run parser source of 63 | Err deadEnds -> 64 | Expect.fail ("Expected the source to be parsed correctly:\n[ " ++ (List.map deadEndToString deadEnds |> String.join "\n, ") ++ "\n]") 65 | 66 | Ok actual -> 67 | Expect.all 68 | [ \() -> actual.syntax |> Expect.equal expected 69 | , \() -> 70 | actual.comments 71 | |> Rope.toList 72 | |> Expect.equalLists [] 73 | |> Expect.onFail "This parser should not produce any comments. If this is expected, then you should use expectAstWithComments instead." 74 | ] 75 | () 76 | 77 | 78 | deadEndToString : Parser.DeadEnd -> String 79 | deadEndToString { row, col, problem } = 80 | "{ problem: " ++ Debug.toString problem ++ ", row = " ++ String.fromInt row ++ ", col = " ++ String.fromInt col ++ " }" 81 | 82 | 83 | expectAstWithComments : ParserFast.Parser (WithComments a) -> { ast : a, comments : List (Node String) } -> String -> Expect.Expectation 84 | expectAstWithComments parser = 85 | \expected source -> 86 | case ParserFast.run parser source of 87 | Err error -> 88 | Expect.fail ("Expected the source to be parsed correctly:\n" ++ Debug.toString error) 89 | 90 | Ok actual -> 91 | Expect.all 92 | [ \() -> actual.syntax |> Expect.equal expected.ast 93 | , \() -> actual.comments |> Rope.toList |> Expect.equal expected.comments 94 | ] 95 | () 96 | 97 | 98 | expectInvalid : ParserFast.Parser (WithComments a) -> String -> Expect.Expectation 99 | expectInvalid parser = 100 | \source -> 101 | case parseWithFailure source parser of 102 | Err _ -> 103 | Expect.pass 104 | 105 | Ok actual -> 106 | Expect.fail ("This source code is successfully parsed but it shouldn't:\n" ++ Debug.toString actual) 107 | -------------------------------------------------------------------------------- /tests/Elm/Parser/LayoutTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.LayoutTests exposing (all) 2 | 3 | import Elm.Parser.Layout as Layout 4 | import Elm.Parser.ParserWithCommentsTestUtil exposing (parseWithState) 5 | import Expect 6 | import ParserFast 7 | import ParserWithComments exposing (Comments) 8 | import Test exposing (..) 9 | 10 | 11 | all : Test 12 | all = 13 | describe "LayoutTests" 14 | [ test "empty" <| 15 | \() -> 16 | parse "." (ParserFast.symbolFollowedBy "." Layout.maybeLayout) 17 | |> Expect.equal (Just ()) 18 | , test "just whitespace" <| 19 | \() -> 20 | parse " " Layout.maybeLayout 21 | |> Expect.equal (Just ()) 22 | , test "spaces followed by new line" <| 23 | \() -> 24 | parse " \n" Layout.maybeLayout 25 | |> Expect.equal Nothing 26 | , test "with newline and higher indent 2" <| 27 | \() -> 28 | parse "\n " Layout.maybeLayout 29 | |> Expect.equal (Just ()) 30 | , test "with newline and higher indent 3" <| 31 | \() -> 32 | parse " \n " Layout.maybeLayout 33 | |> Expect.equal (Just ()) 34 | , test "maybeLayout with multiline comment" <| 35 | \() -> 36 | parse "\n--x\n{- foo \n-}\n " Layout.maybeLayout 37 | |> Expect.equal (Just ()) 38 | , test "maybeLayout with documentation comment fails" <| 39 | \() -> 40 | parse "\n--x\n{-| foo \n-}\n " Layout.maybeLayout 41 | |> Expect.equal Nothing 42 | , test "with newline and higher indent 4" <| 43 | \() -> 44 | parse " \n " Layout.maybeLayout 45 | |> Expect.equal (Just ()) 46 | , test "newlines spaces and single line comments" <| 47 | \() -> 48 | parse "\n\n --time\n " Layout.maybeLayout 49 | |> Expect.equal (Just ()) 50 | , test "layoutStrict" <| 51 | \() -> 52 | parse " \n" Layout.layoutStrict 53 | |> Expect.equal (Just ()) 54 | , test "layoutStrict multi line" <| 55 | \() -> 56 | parse " \n \n" Layout.layoutStrict 57 | |> Expect.equal (Just ()) 58 | , test "layoutStrict too much" <| 59 | \() -> 60 | parse " \n " Layout.layoutStrict 61 | |> Expect.equal Nothing 62 | , test "layoutStrict with comments" <| 63 | \() -> 64 | parse "-- foo\n --bar\n" Layout.layoutStrict 65 | |> Expect.equal (Just ()) 66 | , test "layoutStrict with comments 2" <| 67 | \() -> 68 | parse "\n--x\n{- foo \n-}\n" Layout.layoutStrict 69 | |> Expect.equal (Just ()) 70 | , test "layoutStrict with documentation comment fails" <| 71 | \() -> 72 | parse "\n--x\n{-| foo \n-}\n" Layout.layoutStrict 73 | |> Expect.equal Nothing 74 | , test "layoutStrict some" <| 75 | \() -> 76 | parse "..\n \n " 77 | (ParserFast.symbolFollowedBy ".." 78 | (ParserFast.withIndentSetToColumn Layout.layoutStrict) 79 | ) 80 | |> Expect.equal (Just ()) 81 | , test "layoutStrict with comments multi empty line preceding" <| 82 | \() -> 83 | parse "\n\n --bar\n" Layout.layoutStrict 84 | |> Expect.equal (Just ()) 85 | , test "layoutStrict with multiple new lines" <| 86 | \() -> 87 | parse "..\n \n \n\n " 88 | (ParserFast.symbolFollowedBy ".." 89 | (ParserFast.withIndentSetToColumn Layout.layoutStrict) 90 | ) 91 | |> Expect.equal (Just ()) 92 | , test "layoutStrict with multiline comment plus trailing whitespace" <| 93 | \() -> 94 | parse "\n{- some note -} \n" Layout.layoutStrict 95 | |> Expect.equal (Just ()) 96 | ] 97 | 98 | 99 | parse : String -> ParserFast.Parser Comments -> Maybe () 100 | parse source parser = 101 | parseWithState source 102 | (parser |> ParserFast.map (\c -> { comments = c, syntax = () })) 103 | |> Maybe.map .syntax 104 | -------------------------------------------------------------------------------- /find-regressions/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Run this through find-regressions.sh at the root of the project. 4 | // More instructions on how to run the script is available in that file. 5 | 6 | const fs = require('node:fs'); 7 | const os = require('node:os'); 8 | const path = require('node:path'); 9 | const {spawn} = require('node:child_process'); 10 | const { fdir } = require('fdir'); 11 | 12 | try { 13 | require('./published/elm.js'); 14 | require('./current/elm.js'); 15 | } catch (error) { 16 | console.error(`ERROR: You have not compiled the Elm applications yet. 17 | 18 | Please run this script through "find-regressions.sh". 19 | `) 20 | process.exit(1); 21 | } 22 | const publishedElm = require('./published/elm.js'); 23 | const currentElm = require('./current/elm.js'); 24 | 25 | const disableEquality = process.argv.includes('--no-check'); 26 | const pathArguments = process.argv.slice(2).filter(arg => arg !== '--no-check'); 27 | 28 | const filesToParse = 29 | pathArguments.map(pathArgument => { 30 | if (pathArgument.endsWith(".elm")) { 31 | return [pathArgument]; 32 | } 33 | return new fdir() 34 | .withFullPaths() 35 | .glob('./**/*.elm') 36 | .crawl(pathArgument) 37 | .sync(); 38 | }).reduce((acc, array) => acc.concat(array)); 39 | 40 | const publishedApp = publishedElm.Elm.ParseMain.init({ flags: 'published' }); 41 | const currentApp = currentElm.Elm.ParseMain.init({ flags: 'current' }); 42 | 43 | globalThis.measurements = { 44 | 'current': [], 45 | 'published': [], 46 | }; 47 | 48 | (async function() { 49 | const tmpDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'elm-syntax-regressions-')); 50 | 51 | for await (const filePath of filesToParse) { 52 | const source = fs.readFileSync(filePath, 'utf8'); 53 | const published = await parse(publishedApp, source) 54 | .catch(error => { 55 | console.error(`Failure parsing ${filePath} with PUBLISHED`, error); 56 | return 'FAILURE'; 57 | }) 58 | const current = await parse(currentApp, source) 59 | .catch(error => { 60 | console.error(`Failure parsing ${filePath} with CURRENT`, error); 61 | return 'FAILURE'; 62 | }) 63 | if (!disableEquality) { 64 | if (JSON.stringify(published) !== JSON.stringify(current)) { 65 | const regressionFolder = path.join(tmpDirectory, path.basename(filePath, '.elm')); 66 | const pathPublished = path.join(regressionFolder, 'published.txt'); 67 | const pathCurrent = path.join(regressionFolder, 'current.txt'); 68 | fs.mkdirSync(path.dirname(pathPublished), {recursive: true}); 69 | fs.writeFileSync(pathPublished, JSON.stringify(published, null, 4), {encoding: 'utf8'}); 70 | fs.writeFileSync(pathCurrent, JSON.stringify(current, null, 4), {encoding: 'utf8'}); 71 | fs.writeFileSync(path.join(regressionFolder, path.basename(filePath)), source, {encoding: 'utf8'}); 72 | const diff = fs.createWriteStream(path.join(regressionFolder, 'diff.diff'), { flags: 'a' }); 73 | diff.on('open', () => { 74 | spawn( 75 | "diff", 76 | [pathPublished, pathCurrent], 77 | { stdio: [diff, diff, diff] } 78 | ); 79 | }); 80 | 81 | console.error(`DIFFERENT: ${filePath}`); 82 | console.error(` Folder: ${path.dirname(pathPublished)}`); 83 | console.error() 84 | } 85 | } 86 | } 87 | 88 | const publishedAvg = avg(globalThis.measurements.published); 89 | const currentAvg = avg(globalThis.measurements.current); 90 | const speedup = (publishedAvg / currentAvg - 1) * 100 91 | console.log('Published: avg ' + publishedAvg); 92 | console.log('Current: avg ' + currentAvg); 93 | console.log(`Diff: ${speedup}% faster`); 94 | })() 95 | 96 | function parse(elmApp, source) { 97 | return new Promise((resolve) => { 98 | elmApp.ports.parseResult.subscribe(fn); 99 | elmApp.ports.requestParsing.send(source); 100 | function fn(data) { 101 | elmApp.ports.parseResult.unsubscribe(fn); 102 | resolve(data); 103 | } 104 | }); 105 | } 106 | 107 | 108 | function avg(array) { 109 | return array.reduce((a, b) => a + b, 0) / array.length; 110 | } -------------------------------------------------------------------------------- /tests/Elm/Parser/ImportsTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.ImportsTests exposing (all) 2 | 3 | import Elm.Parser.Imports as Parser 4 | import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil 5 | import Elm.Syntax.Exposing exposing (..) 6 | import Elm.Syntax.Import exposing (Import) 7 | import Elm.Syntax.Node exposing (Node(..)) 8 | import Expect 9 | import Test exposing (..) 10 | 11 | 12 | all : Test 13 | all = 14 | describe "ImportTest" 15 | [ test "import with explicits" <| 16 | \() -> 17 | "import Foo exposing (Model, Msg(..))" 18 | |> expectAst 19 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 37 } } 20 | { moduleName = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 11 } } [ "Foo" ] 21 | , moduleAlias = Nothing 22 | , exposingList = 23 | Just 24 | (Node { start = { row = 1, column = 12 }, end = { row = 1, column = 37 } } 25 | (Explicit 26 | [ Node { start = { row = 1, column = 22 }, end = { row = 1, column = 27 } } (TypeOrAliasExpose "Model"), Node { start = { row = 1, column = 29 }, end = { row = 1, column = 36 } } (TypeExpose { name = "Msg", open = Just { start = { row = 1, column = 32 }, end = { row = 1, column = 36 } } }) ] 27 | ) 28 | ) 29 | } 30 | ) 31 | , test "import with explicits 2" <| 32 | \() -> 33 | "import Html exposing (text)" 34 | |> expectAst 35 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 28 } } 36 | { moduleName = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 12 } } [ "Html" ] 37 | , moduleAlias = Nothing 38 | , exposingList = 39 | Just 40 | (Node { start = { row = 1, column = 13 }, end = { row = 1, column = 28 } } 41 | (Explicit 42 | [ Node { start = { row = 1, column = 23 }, end = { row = 1, column = 27 } } (FunctionExpose "text") ] 43 | ) 44 | ) 45 | } 46 | ) 47 | , test "import minimal" <| 48 | \() -> 49 | "import Foo" 50 | |> expectAst 51 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 11 } } 52 | { moduleName = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 11 } } [ "Foo" ] 53 | , moduleAlias = Nothing 54 | , exposingList = Nothing 55 | } 56 | ) 57 | , test "import with alias" <| 58 | \() -> 59 | "import Foo as Bar" 60 | |> expectAst 61 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 18 } } 62 | { moduleName = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 11 } } [ "Foo" ] 63 | , moduleAlias = Just (Node { start = { row = 1, column = 15 }, end = { row = 1, column = 18 } } [ "Bar" ]) 64 | , exposingList = Nothing 65 | } 66 | ) 67 | , test "import with alias and exposing all" <| 68 | \() -> 69 | "import Foo as Bar exposing (..)" 70 | |> expectAst 71 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 32 } } 72 | { moduleName = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 11 } } [ "Foo" ] 73 | , moduleAlias = Just (Node { start = { row = 1, column = 15 }, end = { row = 1, column = 18 } } [ "Bar" ]) 74 | , exposingList = 75 | Just 76 | (Node { start = { row = 1, column = 19 }, end = { row = 1, column = 32 } } 77 | (All { start = { row = 1, column = 29 }, end = { row = 1, column = 31 } }) 78 | ) 79 | } 80 | ) 81 | , test "import with invalid alias containing ." <| 82 | \() -> 83 | "import Foo as Bar.Buzz" 84 | |> ParserWithCommentsUtil.expectInvalid Parser.importDefinition 85 | ] 86 | 87 | 88 | expectAst : Node Import -> String -> Expect.Expectation 89 | expectAst = 90 | ParserWithCommentsUtil.expectAst Parser.importDefinition 91 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Module.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Module exposing 2 | ( Module(..), DefaultModuleData, EffectModuleData 3 | , exposingList, moduleName, isPortModule, isEffectModule 4 | , encode, decoder 5 | ) 6 | 7 | {-| This syntax represents module definitions in Elm. 8 | For example: 9 | 10 | module Html.Attributes exposing (style) 11 | 12 | 13 | ## Module 14 | 15 | @docs Module, DefaultModuleData, EffectModuleData 16 | 17 | @docs exposingList, moduleName, isPortModule, isEffectModule 18 | 19 | 20 | ## Serialization 21 | 22 | @docs encode, decoder 23 | 24 | -} 25 | 26 | import Elm.Json.Util exposing (decodeTyped, encodeTyped) 27 | import Elm.Syntax.Exposing as Exposing exposing (Exposing) 28 | import Elm.Syntax.ModuleName as ModuleName exposing (ModuleName) 29 | import Elm.Syntax.Node as Node exposing (Node) 30 | import Json.Decode as JD exposing (Decoder) 31 | import Json.Encode as JE exposing (Value) 32 | 33 | 34 | {-| Union type for different kind of modules 35 | -} 36 | type Module 37 | = NormalModule DefaultModuleData 38 | | PortModule DefaultModuleData 39 | | EffectModule EffectModuleData 40 | 41 | 42 | {-| Data for a default default 43 | -} 44 | type alias DefaultModuleData = 45 | { moduleName : Node ModuleName 46 | , exposingList : Node Exposing 47 | } 48 | 49 | 50 | {-| Data for an effect module 51 | -} 52 | type alias EffectModuleData = 53 | { moduleName : Node ModuleName 54 | , exposingList : Node Exposing 55 | , command : Maybe (Node String) 56 | , subscription : Maybe (Node String) 57 | } 58 | 59 | 60 | {-| Get the name for a module. For older modules this may not be present. 61 | -} 62 | moduleName : Module -> ModuleName 63 | moduleName m = 64 | case m of 65 | NormalModule x -> 66 | Node.value x.moduleName 67 | 68 | PortModule x -> 69 | Node.value x.moduleName 70 | 71 | EffectModule x -> 72 | Node.value x.moduleName 73 | 74 | 75 | {-| Get the exposing list for a module. 76 | -} 77 | exposingList : Module -> Exposing 78 | exposingList m = 79 | case m of 80 | NormalModule x -> 81 | Node.value x.exposingList 82 | 83 | PortModule x -> 84 | Node.value x.exposingList 85 | 86 | EffectModule x -> 87 | Node.value x.exposingList 88 | 89 | 90 | {-| Check whether a module is defined as a port-module 91 | -} 92 | isPortModule : Module -> Bool 93 | isPortModule m = 94 | case m of 95 | PortModule _ -> 96 | True 97 | 98 | _ -> 99 | False 100 | 101 | 102 | {-| Check whether a module is defined as an effect-module 103 | -} 104 | isEffectModule : Module -> Bool 105 | isEffectModule m = 106 | case m of 107 | EffectModule _ -> 108 | True 109 | 110 | _ -> 111 | False 112 | 113 | 114 | 115 | -- Serialization 116 | 117 | 118 | {-| Encode a `Module` syntax element to JSON. 119 | -} 120 | encode : Module -> Value 121 | encode m = 122 | case m of 123 | NormalModule d -> 124 | encodeTyped "normal" (encodeDefaultModuleData d) 125 | 126 | PortModule d -> 127 | encodeTyped "port" (encodeDefaultModuleData d) 128 | 129 | EffectModule d -> 130 | encodeTyped "effect" (encodeEffectModuleData d) 131 | 132 | 133 | encodeEffectModuleData : EffectModuleData -> Value 134 | encodeEffectModuleData moduleData = 135 | JE.object 136 | [ ( "moduleName", Node.encode ModuleName.encode moduleData.moduleName ) 137 | , ( "exposingList", Node.encode Exposing.encode moduleData.exposingList ) 138 | , ( "command", moduleData.command |> Maybe.map (Node.encode JE.string) |> Maybe.withDefault JE.null ) 139 | , ( "subscription", moduleData.subscription |> Maybe.map (Node.encode JE.string) |> Maybe.withDefault JE.null ) 140 | ] 141 | 142 | 143 | encodeDefaultModuleData : DefaultModuleData -> Value 144 | encodeDefaultModuleData moduleData = 145 | JE.object 146 | [ ( "moduleName", Node.encode ModuleName.encode moduleData.moduleName ) 147 | , ( "exposingList", Node.encode Exposing.encode moduleData.exposingList ) 148 | ] 149 | 150 | 151 | {-| JSON decoder for a `Module` syntax element. 152 | -} 153 | decoder : Decoder Module 154 | decoder = 155 | decodeTyped 156 | [ ( "normal", decodeDefaultModuleData |> JD.map NormalModule ) 157 | , ( "port", decodeDefaultModuleData |> JD.map PortModule ) 158 | , ( "effect", decodeEffectModuleData |> JD.map EffectModule ) 159 | ] 160 | 161 | 162 | decodeDefaultModuleData : Decoder DefaultModuleData 163 | decodeDefaultModuleData = 164 | JD.map2 DefaultModuleData 165 | (JD.field "moduleName" <| Node.decoder ModuleName.decoder) 166 | (JD.field "exposingList" <| Node.decoder Exposing.decoder) 167 | 168 | 169 | decodeEffectModuleData : Decoder EffectModuleData 170 | decodeEffectModuleData = 171 | JD.map4 EffectModuleData 172 | (JD.field "moduleName" <| Node.decoder ModuleName.decoder) 173 | (JD.field "exposingList" <| Node.decoder Exposing.decoder) 174 | (JD.field "command" (JD.nullable <| Node.decoder JD.string)) 175 | (JD.field "subscription" (JD.nullable <| Node.decoder JD.string)) 176 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Exposing.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Exposing exposing 2 | ( Exposing(..), TopLevelExpose(..), ExposedType 3 | , exposesFunction, operators 4 | , encode, decoder 5 | ) 6 | 7 | {-| This syntax represents the exposing declaration for both imports and module headers. 8 | For example: 9 | 10 | exposing (Foo(..)) 11 | exposing (..) 12 | 13 | 14 | ## Types 15 | 16 | @docs Exposing, TopLevelExpose, ExposedType 17 | 18 | 19 | ## Functions 20 | 21 | @docs exposesFunction, operators 22 | 23 | 24 | ## Serialization 25 | 26 | @docs encode, decoder 27 | 28 | -} 29 | 30 | import Elm.Json.Util exposing (decodeTyped, encodeTyped) 31 | import Elm.Syntax.Node as Node exposing (Node(..)) 32 | import Elm.Syntax.Range as Range exposing (Range) 33 | import Json.Decode as JD exposing (Decoder) 34 | import Json.Encode as JE exposing (Value) 35 | 36 | 37 | {-| Different kind of exposing declarations 38 | -} 39 | type Exposing 40 | = All Range 41 | | Explicit (List (Node TopLevelExpose)) 42 | 43 | 44 | {-| An exposed entity 45 | -} 46 | type TopLevelExpose 47 | = InfixExpose String 48 | | FunctionExpose String 49 | | TypeOrAliasExpose String 50 | | TypeExpose ExposedType 51 | 52 | 53 | {-| Exposed Type 54 | -} 55 | type alias ExposedType = 56 | { name : String 57 | , open : Maybe Range 58 | } 59 | 60 | 61 | 62 | -- Functions 63 | 64 | 65 | {-| Check whether an import/module exposing list exposes a certain function. Will yield `True` if `Exposing` is exposing everything (`All`). 66 | 67 | exposesFunction "something" (All someRange) == True 68 | 69 | exposesFunction "divide" (Explicit [ Node someRange (FunctionExpose "add") ]) == False 70 | 71 | exposesFunction "add" (Explicit [ Node someRange (FunctionExpose "add") ]) == True 72 | 73 | -} 74 | exposesFunction : String -> Exposing -> Bool 75 | exposesFunction s exposure = 76 | case exposure of 77 | All _ -> 78 | True 79 | 80 | Explicit l -> 81 | List.any 82 | (\(Node _ value) -> 83 | case value of 84 | FunctionExpose fun -> 85 | fun == s 86 | 87 | _ -> 88 | False 89 | ) 90 | l 91 | 92 | 93 | {-| Collect all operator names from a list of TopLevelExposes 94 | -} 95 | operators : List TopLevelExpose -> List String 96 | operators l = 97 | List.filterMap operator l 98 | 99 | 100 | operator : TopLevelExpose -> Maybe String 101 | operator t = 102 | case t of 103 | InfixExpose s -> 104 | Just s 105 | 106 | _ -> 107 | Nothing 108 | 109 | 110 | 111 | -- Serialization 112 | 113 | 114 | {-| Encode an `Exposing` syntax element to JSON. 115 | -} 116 | encode : Exposing -> Value 117 | encode exp = 118 | case exp of 119 | All r -> 120 | encodeTyped "all" <| Range.encode r 121 | 122 | Explicit l -> 123 | encodeTyped "explicit" (JE.list encodeTopLevelExpose l) 124 | 125 | 126 | {-| JSON decoder for an `Exposing` syntax element. 127 | -} 128 | decoder : Decoder Exposing 129 | decoder = 130 | decodeTyped 131 | [ ( "all", Range.decoder |> JD.map All ) 132 | , ( "explicit", JD.list topLevelExposeDecoder |> JD.map Explicit ) 133 | ] 134 | 135 | 136 | encodeTopLevelExpose : Node TopLevelExpose -> Value 137 | encodeTopLevelExpose = 138 | Node.encode 139 | (\exp -> 140 | case exp of 141 | InfixExpose x -> 142 | encodeTyped "infix" <| 143 | JE.object 144 | [ ( "name", JE.string x ) 145 | ] 146 | 147 | FunctionExpose x -> 148 | encodeTyped "function" <| 149 | JE.object 150 | [ ( "name", JE.string x ) 151 | ] 152 | 153 | TypeOrAliasExpose x -> 154 | encodeTyped "typeOrAlias" <| 155 | JE.object 156 | [ ( "name", JE.string x ) 157 | ] 158 | 159 | TypeExpose exposedType -> 160 | encodeTyped "typeexpose" (encodeExposedType exposedType) 161 | ) 162 | 163 | 164 | encodeExposedType : ExposedType -> Value 165 | encodeExposedType { name, open } = 166 | JE.object 167 | [ ( "name", JE.string name ) 168 | , ( "open", open |> Maybe.map Range.encode |> Maybe.withDefault JE.null ) 169 | ] 170 | 171 | 172 | topLevelExposeDecoder : Decoder (Node TopLevelExpose) 173 | topLevelExposeDecoder = 174 | Node.decoder 175 | (decodeTyped 176 | [ ( "infix", JD.map InfixExpose (JD.field "name" JD.string) ) 177 | , ( "function", JD.map FunctionExpose (JD.field "name" JD.string) ) 178 | , ( "typeOrAlias", JD.map TypeOrAliasExpose (JD.field "name" JD.string) ) 179 | , ( "typeexpose", JD.map TypeExpose exposedTypeDecoder ) 180 | ] 181 | ) 182 | 183 | 184 | exposedTypeDecoder : Decoder ExposedType 185 | exposedTypeDecoder = 186 | JD.map2 ExposedType 187 | (JD.field "name" JD.string) 188 | (JD.field "open" (JD.nullable Range.decoder)) 189 | -------------------------------------------------------------------------------- /src/Elm/Parser/Expose.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.Expose exposing (exposeDefinition) 2 | 3 | import Elm.Parser.Layout as Layout 4 | import Elm.Parser.Tokens as Tokens 5 | import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) 6 | import Elm.Syntax.Node exposing (Node(..)) 7 | import ParserFast exposing (Parser) 8 | import ParserWithComments exposing (WithComments) 9 | import Rope 10 | 11 | 12 | exposeDefinition : Parser (WithComments (Node Exposing)) 13 | exposeDefinition = 14 | ParserFast.map3WithRange 15 | (\range commentsAfterExposing commentsBefore exposingListInnerResult -> 16 | { comments = 17 | commentsAfterExposing 18 | |> Rope.prependTo commentsBefore 19 | |> Rope.prependTo exposingListInnerResult.comments 20 | , syntax = Node range exposingListInnerResult.syntax 21 | } 22 | ) 23 | (ParserFast.symbolFollowedBy "exposing" Layout.maybeLayout) 24 | (ParserFast.symbolFollowedBy "(" Layout.optimisticLayout) 25 | (exposingListInner 26 | |> ParserFast.followedBySymbol ")" 27 | ) 28 | 29 | 30 | exposingListInner : Parser (WithComments Exposing) 31 | exposingListInner = 32 | ParserFast.oneOf2 33 | (ParserFast.map3 34 | (\headElement commentsAfterHeadElement tailElements -> 35 | { comments = 36 | headElement.comments 37 | |> Rope.prependTo commentsAfterHeadElement 38 | |> Rope.prependTo tailElements.comments 39 | , syntax = 40 | Explicit 41 | (headElement.syntax 42 | :: tailElements.syntax 43 | ) 44 | } 45 | ) 46 | exposable 47 | Layout.maybeLayout 48 | (ParserWithComments.many 49 | (ParserFast.symbolFollowedBy "," 50 | (Layout.maybeAroundBothSides exposable) 51 | ) 52 | ) 53 | ) 54 | (ParserFast.mapWithRange 55 | (\range commentsAfterDotDot -> 56 | { comments = commentsAfterDotDot 57 | , syntax = All range 58 | } 59 | ) 60 | (ParserFast.symbolFollowedBy ".." Layout.maybeLayout) 61 | ) 62 | 63 | 64 | exposable : Parser (WithComments (Node TopLevelExpose)) 65 | exposable = 66 | ParserFast.oneOf3 67 | functionExpose 68 | typeExpose 69 | infixExpose 70 | 71 | 72 | infixExpose : ParserFast.Parser (WithComments (Node TopLevelExpose)) 73 | infixExpose = 74 | ParserFast.map2WithRange 75 | (\range infixName () -> 76 | { comments = Rope.empty 77 | , syntax = Node range (InfixExpose infixName) 78 | } 79 | ) 80 | (ParserFast.symbolFollowedBy "(" 81 | (ParserFast.ifFollowedByWhileWithoutLinebreak 82 | (\c -> 83 | case c of 84 | ')' -> 85 | False 86 | 87 | '\n' -> 88 | False 89 | 90 | ' ' -> 91 | False 92 | 93 | _ -> 94 | True 95 | ) 96 | (\c -> 97 | case c of 98 | ')' -> 99 | False 100 | 101 | '\n' -> 102 | False 103 | 104 | ' ' -> 105 | False 106 | 107 | _ -> 108 | True 109 | ) 110 | ) 111 | ) 112 | Tokens.parensEnd 113 | 114 | 115 | typeExpose : Parser (WithComments (Node TopLevelExpose)) 116 | typeExpose = 117 | ParserFast.map3 118 | (\(Node typeNameRange typeName) commentsBeforeMaybeOpen maybeOpen -> 119 | { comments = commentsBeforeMaybeOpen |> Rope.prependTo maybeOpen.comments 120 | , syntax = 121 | case maybeOpen.syntax of 122 | Nothing -> 123 | Node typeNameRange (TypeOrAliasExpose typeName) 124 | 125 | Just openRange -> 126 | Node 127 | { start = typeNameRange.start 128 | , end = openRange.end 129 | } 130 | (TypeExpose { name = typeName, open = maybeOpen.syntax }) 131 | } 132 | ) 133 | Tokens.typeNameNode 134 | Layout.optimisticLayout 135 | (ParserFast.map2WithRangeOrSucceed 136 | (\range left right -> 137 | { comments = left |> Rope.prependTo right, syntax = Just range } 138 | ) 139 | (ParserFast.symbolFollowedBy "(" Layout.maybeLayout) 140 | (ParserFast.symbolFollowedBy ".." Layout.maybeLayout 141 | |> ParserFast.followedBySymbol ")" 142 | ) 143 | { comments = Rope.empty, syntax = Nothing } 144 | ) 145 | 146 | 147 | functionExpose : Parser (WithComments (Node TopLevelExpose)) 148 | functionExpose = 149 | Tokens.functionNameMapWithRange 150 | (\range name -> 151 | { comments = Rope.empty 152 | , syntax = 153 | Node range (FunctionExpose name) 154 | } 155 | ) 156 | -------------------------------------------------------------------------------- /src/Elm/Parser/Imports.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.Imports exposing (importDefinition) 2 | 3 | import Elm.Parser.Base exposing (moduleName) 4 | import Elm.Parser.Expose exposing (exposeDefinition) 5 | import Elm.Parser.Layout as Layout 6 | import Elm.Parser.Tokens as Tokens 7 | import Elm.Syntax.Import exposing (Import) 8 | import Elm.Syntax.Node exposing (Node(..)) 9 | import ParserFast exposing (Parser) 10 | import ParserWithComments exposing (Comments, WithComments) 11 | import Rope 12 | 13 | 14 | importDefinition : Parser (WithComments (Node Import)) 15 | importDefinition = 16 | ParserFast.map5WithStartLocation 17 | (\start commentsAfterImport mod commentsAfterModuleName maybeModuleAlias maybeExposingList -> 18 | let 19 | commentsBeforeAlias : Comments 20 | commentsBeforeAlias = 21 | commentsAfterImport 22 | |> Rope.prependTo commentsAfterModuleName 23 | in 24 | case maybeModuleAlias of 25 | Nothing -> 26 | case maybeExposingList of 27 | Nothing -> 28 | let 29 | (Node modRange _) = 30 | mod 31 | in 32 | { comments = commentsBeforeAlias 33 | , syntax = 34 | Node { start = start, end = modRange.end } 35 | { moduleName = mod 36 | , moduleAlias = Nothing 37 | , exposingList = Nothing 38 | } 39 | } 40 | 41 | Just exposingListValue -> 42 | let 43 | (Node exposingRange _) = 44 | exposingListValue.syntax 45 | in 46 | { comments = 47 | commentsBeforeAlias |> Rope.prependTo exposingListValue.comments 48 | , syntax = 49 | Node { start = start, end = exposingRange.end } 50 | { moduleName = mod 51 | , moduleAlias = Nothing 52 | , exposingList = Just exposingListValue.syntax 53 | } 54 | } 55 | 56 | Just moduleAliasResult -> 57 | case maybeExposingList of 58 | Nothing -> 59 | let 60 | (Node aliasRange _) = 61 | moduleAliasResult.syntax 62 | in 63 | { comments = 64 | commentsBeforeAlias |> Rope.prependTo moduleAliasResult.comments 65 | , syntax = 66 | Node { start = start, end = aliasRange.end } 67 | { moduleName = mod 68 | , moduleAlias = Just moduleAliasResult.syntax 69 | , exposingList = Nothing 70 | } 71 | } 72 | 73 | Just exposingListValue -> 74 | let 75 | (Node exposingRange _) = 76 | exposingListValue.syntax 77 | in 78 | { comments = 79 | commentsBeforeAlias 80 | |> Rope.prependTo moduleAliasResult.comments 81 | |> Rope.prependTo exposingListValue.comments 82 | , syntax = 83 | Node { start = start, end = exposingRange.end } 84 | { moduleName = mod 85 | , moduleAlias = Just moduleAliasResult.syntax 86 | , exposingList = Just exposingListValue.syntax 87 | } 88 | } 89 | ) 90 | (ParserFast.keywordFollowedBy "import" Layout.maybeLayout) 91 | moduleName 92 | Layout.optimisticLayout 93 | (ParserFast.map3OrSucceed 94 | (\commentsBefore moduleAliasNode commentsAfter -> 95 | Just 96 | { comments = commentsBefore |> Rope.prependTo commentsAfter 97 | , syntax = moduleAliasNode 98 | } 99 | ) 100 | (ParserFast.keywordFollowedBy "as" Layout.maybeLayout) 101 | (Tokens.typeNameMapWithRange 102 | (\range moduleAlias -> 103 | Node range [ moduleAlias ] 104 | ) 105 | ) 106 | Layout.optimisticLayout 107 | Nothing 108 | ) 109 | (ParserFast.map2OrSucceed 110 | (\exposingResult commentsAfter -> 111 | Just 112 | { comments = exposingResult.comments |> Rope.prependTo commentsAfter 113 | , syntax = exposingResult.syntax 114 | } 115 | ) 116 | exposeDefinition 117 | Layout.optimisticLayout 118 | Nothing 119 | ) 120 | -------------------------------------------------------------------------------- /tests/Elm/Parser/NumbersTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.NumbersTests exposing (all) 2 | 3 | import Elm.Parser.TestUtil exposing (..) 4 | import Expect 5 | import ParserFast 6 | import Test exposing (..) 7 | 8 | 9 | all : Test 10 | all = 11 | describe "NumbersTests" 12 | [ describe "integerDecimalOrHexadecimalMapWithRange" 13 | [ test "hex" <| 14 | \() -> 15 | parseToResult "0x03FFFFFF" (ParserFast.integerDecimalOrHexadecimalMapWithRange (\_ _ -> -1) (\_ n -> n)) 16 | |> Expect.equal 17 | (Ok 67108863) 18 | , test "hex - 2" <| 19 | \() -> 20 | parseToResult "0xFF" (ParserFast.integerDecimalOrHexadecimalMapWithRange (\_ _ -> -1) (\_ n -> n)) 21 | |> Expect.equal 22 | (Ok 255) 23 | ] 24 | , describe "floatOrIntegerDecimalOrHexadecimalMapWithRange" 25 | [ test "hex" <| 26 | \() -> 27 | parseToResult "0x2A" 28 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 29 | (\_ _ -> -1) 30 | (\_ _ -> -1) 31 | (\_ n -> n) 32 | ) 33 | |> Expect.equal 34 | (Ok 42) 35 | , test "float" <| 36 | \() -> 37 | parseToResult "2.0" 38 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 39 | (\_ n -> n) 40 | (\_ _ -> -1) 41 | (\_ _ -> -1) 42 | ) 43 | |> Expect.equal 44 | (Ok 2.0) 45 | , test "integer with negative exponent" <| 46 | \() -> 47 | parseToResult "2e-2" 48 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 49 | (\_ n -> n) 50 | (\_ _ -> -1) 51 | (\_ _ -> -1) 52 | ) 53 | |> Expect.equal 54 | (Ok 2.0e-2) 55 | , test "integer with negative exponent (uppercase E)" <| 56 | \() -> 57 | parseToResult "2E-2" 58 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 59 | (\_ n -> n) 60 | (\_ _ -> -1) 61 | (\_ _ -> -1) 62 | ) 63 | |> Expect.equal 64 | (Ok 2.0e-2) 65 | , test "integer with positive exponent" <| 66 | \() -> 67 | parseToResult "2e+2" 68 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 69 | (\_ n -> n) 70 | (\_ _ -> -1) 71 | (\_ _ -> -1) 72 | ) 73 | |> Expect.equal 74 | (Ok 2.0e2) 75 | , test "float with negative exponent" <| 76 | \() -> 77 | parseToResult "2.0e-2" 78 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 79 | (\_ n -> n) 80 | (\_ _ -> -1) 81 | (\_ _ -> -1) 82 | ) 83 | |> Expect.equal 84 | (Ok 2.0e-2) 85 | , test "float with negative exponent (uppercase E)" <| 86 | \() -> 87 | parseToResult "2.0E-2" 88 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 89 | (\_ n -> n) 90 | (\_ _ -> -1) 91 | (\_ _ -> -1) 92 | ) 93 | |> Expect.equal 94 | (Ok 2.0e-2) 95 | , test "float with positive exponent" <| 96 | \() -> 97 | parseToResult "2.0e+2" 98 | (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 99 | (\_ n -> n) 100 | (\_ _ -> -1) 101 | (\_ _ -> -1) 102 | ) 103 | |> Expect.equal 104 | (Ok 2.0e2) 105 | 106 | -- TODO handling overflow like elm-format / the elm compiler 107 | -- would technically be a breaking change and maybe somewhat difficult to implement 108 | -- If there's a decision in issues like 109 | -- - https://github.com/stil4m/elm-syntax/issues/108 110 | -- - https://github.com/stil4m/elm-syntax/issues/255 111 | -- this should be considered. 112 | -- , test "overflow int" <| 113 | -- \() -> 114 | -- parseToResult "100000000000000000000000" 115 | -- (ParserFast.floatOrIntegerDecimalOrHexadecimalMapWithRange 116 | -- (\_ _ -> -1) 117 | -- (\_ n -> n) 118 | -- (\_ _ -> -1) 119 | -- ) 120 | -- |> Expect.equal 121 | -- (Ok 200376420520689660) 122 | ] 123 | ] 124 | -------------------------------------------------------------------------------- /tests/Elm/Parser/LambdaExpressionTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.LambdaExpressionTests exposing (all) 2 | 3 | import Elm.Parser.Expression exposing (expression) 4 | import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil 5 | import Elm.Syntax.Expression exposing (..) 6 | import Elm.Syntax.Infix exposing (InfixDirection(..)) 7 | import Elm.Syntax.Node exposing (Node(..)) 8 | import Elm.Syntax.Pattern exposing (..) 9 | import Expect 10 | import Test exposing (..) 11 | 12 | 13 | all : Test 14 | all = 15 | describe "LambdaExpressionTests" 16 | [ test "unit lambda" <| 17 | \() -> 18 | "\\() -> foo" 19 | |> expectAst 20 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 11 } } 21 | (LambdaExpression 22 | { args = [ Node { start = { row = 1, column = 2 }, end = { row = 1, column = 4 } } UnitPattern ] 23 | , expression = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 11 } } (FunctionOrValue [] "foo") 24 | } 25 | ) 26 | ) 27 | , test "record lambda" <| 28 | \() -> 29 | "\\{foo} -> foo" 30 | |> expectAst 31 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 14 } } 32 | (LambdaExpression 33 | { args = [ Node { start = { row = 1, column = 2 }, end = { row = 1, column = 7 } } (RecordPattern [ Node { start = { row = 1, column = 3 }, end = { row = 1, column = 6 } } "foo" ]) ] 34 | , expression = Node { start = { row = 1, column = 11 }, end = { row = 1, column = 14 } } (FunctionOrValue [] "foo") 35 | } 36 | ) 37 | ) 38 | , test "empty record lambda" <| 39 | \() -> 40 | "\\{} -> foo" 41 | |> expectAst 42 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 11 } } 43 | (LambdaExpression 44 | { args = [ Node { start = { row = 1, column = 2 }, end = { row = 1, column = 4 } } (RecordPattern []) ] 45 | , expression = Node { start = { row = 1, column = 8 }, end = { row = 1, column = 11 } } (FunctionOrValue [] "foo") 46 | } 47 | ) 48 | ) 49 | , test "args lambda" <| 50 | \() -> 51 | "\\a b -> a + b" 52 | |> expectAst 53 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 14 } } 54 | (LambdaExpression 55 | { args = 56 | [ Node { start = { row = 1, column = 2 }, end = { row = 1, column = 3 } } (VarPattern "a") 57 | , Node { start = { row = 1, column = 4 }, end = { row = 1, column = 5 } } (VarPattern "b") 58 | ] 59 | , expression = 60 | Node { start = { row = 1, column = 9 }, end = { row = 1, column = 14 } } 61 | (OperatorApplication "+" 62 | Left 63 | (Node { start = { row = 1, column = 9 }, end = { row = 1, column = 10 } } (FunctionOrValue [] "a")) 64 | (Node { start = { row = 1, column = 13 }, end = { row = 1, column = 14 } } (FunctionOrValue [] "b")) 65 | ) 66 | } 67 | ) 68 | ) 69 | , test "tuple lambda" <| 70 | \() -> 71 | "\\(a,b) -> a + b" 72 | |> expectAst 73 | (Node { start = { row = 1, column = 1 }, end = { row = 1, column = 16 } } 74 | (LambdaExpression 75 | { args = 76 | [ Node { start = { row = 1, column = 2 }, end = { row = 1, column = 7 } } 77 | (TuplePattern 78 | [ Node { start = { row = 1, column = 3 }, end = { row = 1, column = 4 } } (VarPattern "a") 79 | , Node { start = { row = 1, column = 5 }, end = { row = 1, column = 6 } } (VarPattern "b") 80 | ] 81 | ) 82 | ] 83 | , expression = 84 | Node { start = { row = 1, column = 11 }, end = { row = 1, column = 16 } } 85 | (OperatorApplication "+" 86 | Left 87 | (Node { start = { row = 1, column = 11 }, end = { row = 1, column = 12 } } (FunctionOrValue [] "a")) 88 | (Node { start = { row = 1, column = 15 }, end = { row = 1, column = 16 } } (FunctionOrValue [] "b")) 89 | ) 90 | } 91 | ) 92 | ) 93 | ] 94 | 95 | 96 | expectAst : Node Expression -> String -> Expect.Expectation 97 | expectAst = 98 | ParserWithCommentsUtil.expectAst expression 99 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: [push, pull_request] 6 | 7 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 8 | jobs: 9 | test: 10 | # The type of runner that the job will run on 11 | runs-on: ubuntu-latest 12 | 13 | # Steps represent a sequence of tasks that will be executed as part of the job 14 | steps: 15 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 16 | - uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js environment 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: lts/* 22 | 23 | # Re-use node_modules between runs until yarn.lock changes. 24 | - name: Cache node_modules 25 | id: internal-cache-node_modules 26 | uses: actions/cache@v3 27 | with: 28 | path: node_modules 29 | key: internal-node_modules-ubuntu-latest.x-${{ hashFiles('yarn.lock') }} 30 | 31 | # Re-use ~/.elm between runs until elm.json, elm-tooling.json or 32 | # review/elm.json changes. The Elm compiler saves downloaded Elm packages 33 | # to ~/.elm, and elm-tooling saves downloaded tool executables there. 34 | - name: Cache ~/.elm 35 | uses: actions/cache@v3 36 | with: 37 | path: ~/.elm 38 | key: elm-${{ hashFiles('elm.json', 'elm-tooling.json', 'review/elm.json') }} 39 | 40 | - name: Install npm dependencies 41 | if: steps.cache-node_modules.outputs.cache-hit != 'true' 42 | env: 43 | # If you have a `"postinstall": "elm-tooling install"` script in your 44 | # package.json, this turns it into a no-op. We’ll run it in the next 45 | # step because of the caching. If elm-tooling.json changes but 46 | # yarn.lock does not, the postinstall script needs running 47 | # but this step won’t. 48 | NO_ELM_TOOLING_INSTALL: 1 49 | run: yarn 50 | 51 | # Install tools from elm-tooling.json, unless we restored them from 52 | # cache. yarn.lock and elm-tooling.json can change independently, 53 | # so we need to install separately based on what was restored from cache. 54 | # This is run even if we restored ~/.elm from cache to be 100% sure 55 | # node_modules/.bin/ contains links to all your tools. `elm-tooling 56 | # install` runs very fast when there’s nothing new to download so 57 | # skipping the step doesn’t save much time. 58 | - name: elm-tooling install 59 | run: npx --no-install elm-tooling install 60 | 61 | - name: Run tests 62 | run: yarn test 63 | 64 | publish: 65 | needs: [test] # make sure all your other jobs succeed before trying to publish 66 | 67 | # The type of runner that the job will run on 68 | runs-on: ubuntu-latest 69 | 70 | # Steps represent a sequence of tasks that will be executed as part of the job 71 | steps: 72 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 73 | - uses: actions/checkout@v3 74 | 75 | - name: Setup Node.js environment 76 | uses: actions/setup-node@v3 77 | with: 78 | node-version: lts/* 79 | 80 | # Re-use node_modules between runs until yarn.lock changes. 81 | - name: Cache node_modules 82 | id: internal-cache-node_modules 83 | uses: actions/cache@v3 84 | with: 85 | path: node_modules 86 | key: internal-node_modules-ubuntu-latest.x-${{ hashFiles('yarn.lock') }} 87 | 88 | # Re-use ~/.elm between runs until elm.json, elm-tooling.json or 89 | # review/elm.json changes. The Elm compiler saves downloaded Elm packages 90 | # to ~/.elm, and elm-tooling saves downloaded tool executables there. 91 | - name: Cache ~/.elm 92 | uses: actions/cache@v3 93 | with: 94 | path: ~/.elm 95 | key: elm-${{ hashFiles('elm.json', 'elm-tooling.json', 'review/elm.json') }} 96 | 97 | - name: Install npm dependencies 98 | if: steps.cache-node_modules.outputs.cache-hit != 'true' 99 | env: 100 | # If you have a `"postinstall": "elm-tooling install"` script in your 101 | # package.json, this turns it into a no-op. We’ll run it in the next 102 | # step because of the caching. If elm-tooling.json changes but 103 | # yarn.lock does not, the postinstall script needs running 104 | # but this step won’t. 105 | NO_ELM_TOOLING_INSTALL: 1 106 | run: yarn 107 | 108 | # Install tools from elm-tooling.json, unless we restored them from 109 | # cache. yarn.lock and elm-tooling.json can change independently, 110 | # so we need to install separately based on what was restored from cache. 111 | # This is run even if we restored ~/.elm from cache to be 100% sure 112 | # node_modules/.bin/ contains links to all your tools. `elm-tooling 113 | # install` runs very fast when there’s nothing new to download so 114 | # skipping the step doesn’t save much time. 115 | - name: elm-tooling install 116 | run: npx --no-install elm-tooling install 117 | 118 | - name: Check if package needs to be published 119 | uses: dillonkearns/elm-publish-action@v1 120 | id: publish 121 | with: 122 | dry-run: true 123 | path-to-elm: ./node_modules/.bin/elm 124 | 125 | # Runs a single command using the runners shell 126 | - name: Elm Publish 127 | if: steps.publish.outputs.is-publishable == 'true' 128 | uses: dillonkearns/elm-publish-action@v1 129 | with: 130 | # Token provided by GitHub 131 | github-token: ${{ secrets.GITHUB_TOKEN }} 132 | path-to-elm: ./node_modules/.bin/elm 133 | -------------------------------------------------------------------------------- /src/Elm/Syntax/TypeAnnotation.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.TypeAnnotation exposing 2 | ( TypeAnnotation(..), RecordDefinition, RecordField 3 | , encode, decoder 4 | ) 5 | 6 | {-| This syntax represents the type annotation syntax. 7 | For example: 8 | 9 | Int -> String 10 | 11 | 12 | ## Types 13 | 14 | @docs TypeAnnotation, RecordDefinition, RecordField 15 | 16 | 17 | ## Serialization 18 | 19 | @docs encode, decoder 20 | 21 | -} 22 | 23 | import Elm.Json.Util exposing (decodeTyped, encodeTyped) 24 | import Elm.Syntax.ModuleName as ModuleName exposing (ModuleName) 25 | import Elm.Syntax.Node as Node exposing (Node) 26 | import Json.Decode as JD exposing (Decoder) 27 | import Json.Encode as JE exposing (Value) 28 | 29 | 30 | {-| Custom type for different type annotations. For example: 31 | 32 | - `GenericType`: `a` 33 | - `Typed`: `Maybe (Int -> String)` 34 | - `Unit`: `()` 35 | - `Tuples`: `(a, b, c)` 36 | - `Record`: `{ name : String}` 37 | - `GenericRecord`: `{ a | name : String}` 38 | - `FunctionTypeAnnotation`: `Int -> String` 39 | 40 | -} 41 | type TypeAnnotation 42 | = GenericType String 43 | | Typed (Node ( ModuleName, String )) (List (Node TypeAnnotation)) 44 | | Unit 45 | | Tupled (List (Node TypeAnnotation)) 46 | | Record RecordDefinition 47 | | GenericRecord (Node String) (Node RecordDefinition) 48 | | FunctionTypeAnnotation (Node TypeAnnotation) (Node TypeAnnotation) 49 | 50 | 51 | {-| A list of fields in-order of a record type annotation. 52 | -} 53 | type alias RecordDefinition = 54 | List (Node RecordField) 55 | 56 | 57 | {-| Single field of a record. A name and its type. 58 | -} 59 | type alias RecordField = 60 | ( Node String, Node TypeAnnotation ) 61 | 62 | 63 | 64 | -- Serialization 65 | 66 | 67 | {-| Encode a `TypeAnnotation` syntax element to JSON. 68 | -} 69 | encode : TypeAnnotation -> Value 70 | encode typeAnnotation = 71 | case typeAnnotation of 72 | GenericType name -> 73 | encodeTyped "generic" <| 74 | JE.object 75 | [ ( "value", JE.string name ) 76 | ] 77 | 78 | Typed moduleNameAndName args -> 79 | let 80 | inner : ( ModuleName, String ) -> Value 81 | inner ( mod, n ) = 82 | JE.object 83 | [ ( "moduleName", ModuleName.encode mod ) 84 | , ( "name", JE.string n ) 85 | ] 86 | in 87 | encodeTyped "typed" <| 88 | JE.object 89 | [ ( "moduleNameAndName", Node.encode inner moduleNameAndName ) 90 | , ( "args", JE.list (Node.encode encode) args ) 91 | ] 92 | 93 | Unit -> 94 | encodeTyped "unit" (JE.object []) 95 | 96 | Tupled t -> 97 | encodeTyped "tupled" <| 98 | JE.object 99 | [ ( "values", JE.list (Node.encode encode) t ) 100 | ] 101 | 102 | FunctionTypeAnnotation left right -> 103 | encodeTyped "function" <| 104 | JE.object 105 | [ ( "left", Node.encode encode left ) 106 | , ( "right", Node.encode encode right ) 107 | ] 108 | 109 | Record recordDefinition -> 110 | encodeTyped "record" <| 111 | JE.object 112 | [ ( "value", encodeRecordDefinition recordDefinition ) 113 | ] 114 | 115 | GenericRecord name recordDefinition -> 116 | encodeTyped "genericRecord" <| 117 | JE.object 118 | [ ( "name", Node.encode JE.string name ) 119 | , ( "values", Node.encode encodeRecordDefinition recordDefinition ) 120 | ] 121 | 122 | 123 | encodeRecordDefinition : RecordDefinition -> Value 124 | encodeRecordDefinition = 125 | JE.list (Node.encode encodeRecordField) 126 | 127 | 128 | encodeRecordField : RecordField -> Value 129 | encodeRecordField ( name, ref ) = 130 | JE.object 131 | [ ( "name", Node.encode JE.string name ) 132 | , ( "typeAnnotation", Node.encode encode ref ) 133 | ] 134 | 135 | 136 | decodeModuleNameAndName : Decoder ( ModuleName, String ) 137 | decodeModuleNameAndName = 138 | JD.map2 Tuple.pair 139 | (JD.field "moduleName" <| ModuleName.decoder) 140 | (JD.field "name" <| JD.string) 141 | 142 | 143 | {-| JSON decoder for a `TypeAnnotation` syntax element. 144 | -} 145 | decoder : Decoder TypeAnnotation 146 | decoder = 147 | JD.lazy 148 | (\() -> 149 | decodeTyped 150 | [ ( "generic", JD.map GenericType (JD.field "value" JD.string) ) 151 | , ( "typed" 152 | , JD.map2 Typed 153 | (JD.field "moduleNameAndName" <| Node.decoder decodeModuleNameAndName) 154 | (JD.field "args" (JD.list nestedDecoder)) 155 | ) 156 | , ( "unit", JD.succeed Unit ) 157 | , ( "tupled", JD.map Tupled (JD.field "values" (JD.list nestedDecoder)) ) 158 | , ( "function" 159 | , JD.map2 FunctionTypeAnnotation 160 | (JD.field "left" nestedDecoder) 161 | (JD.field "right" nestedDecoder) 162 | ) 163 | , ( "record", JD.map Record (JD.field "value" recordDefinitionDecoder) ) 164 | , ( "genericRecord" 165 | , JD.map2 GenericRecord 166 | (JD.field "name" <| Node.decoder JD.string) 167 | (JD.field "values" <| Node.decoder recordDefinitionDecoder) 168 | ) 169 | ] 170 | ) 171 | 172 | 173 | nestedDecoder : Decoder (Node TypeAnnotation) 174 | nestedDecoder = 175 | JD.lazy (\() -> Node.decoder decoder) 176 | 177 | 178 | recordDefinitionDecoder : Decoder RecordDefinition 179 | recordDefinitionDecoder = 180 | JD.lazy (\() -> JD.list <| Node.decoder recordFieldDecoder) 181 | 182 | 183 | recordFieldDecoder : Decoder RecordField 184 | recordFieldDecoder = 185 | JD.lazy 186 | (\() -> 187 | JD.map2 Tuple.pair 188 | (JD.field "name" <| Node.decoder JD.string) 189 | (JD.field "typeAnnotation" nestedDecoder) 190 | ) 191 | -------------------------------------------------------------------------------- /src/Elm/Parser/Modules.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.Modules exposing (moduleDefinition) 2 | 3 | import Elm.Parser.Base exposing (moduleName) 4 | import Elm.Parser.Expose exposing (exposeDefinition) 5 | import Elm.Parser.Layout as Layout 6 | import Elm.Parser.Tokens as Tokens 7 | import Elm.Syntax.Module exposing (Module(..)) 8 | import Elm.Syntax.Node exposing (Node(..)) 9 | import List.Extra 10 | import ParserFast exposing (Parser) 11 | import ParserWithComments exposing (WithComments) 12 | import Rope 13 | 14 | 15 | moduleDefinition : Parser (WithComments (Node Module)) 16 | moduleDefinition = 17 | ParserFast.oneOf3 18 | normalModuleDefinition 19 | portModuleDefinition 20 | effectModuleDefinition 21 | 22 | 23 | effectWhereClause : Parser (WithComments ( String, Node String )) 24 | effectWhereClause = 25 | ParserFast.map4 26 | (\fnName commentsAfterFnName commentsAfterEqual typeName_ -> 27 | { comments = commentsAfterFnName |> Rope.prependTo commentsAfterEqual 28 | , syntax = ( fnName, typeName_ ) 29 | } 30 | ) 31 | Tokens.functionName 32 | Layout.maybeLayout 33 | (ParserFast.symbolFollowedBy "=" Layout.maybeLayout) 34 | Tokens.typeNameNode 35 | 36 | 37 | whereBlock : Parser (WithComments { command : Maybe (Node String), subscription : Maybe (Node String) }) 38 | whereBlock = 39 | ParserFast.symbolFollowedBy "{" 40 | (ParserFast.map4 41 | (\commentsBeforeHead head commentsAfterHead tail -> 42 | let 43 | pairs : List ( String, Node String ) 44 | pairs = 45 | head.syntax :: tail.syntax 46 | in 47 | { comments = 48 | commentsBeforeHead 49 | |> Rope.prependTo head.comments 50 | |> Rope.prependTo commentsAfterHead 51 | |> Rope.prependTo tail.comments 52 | , syntax = 53 | { command = 54 | pairs 55 | |> List.Extra.find (\( fnName, _ ) -> fnName == "command") 56 | |> Maybe.map Tuple.second 57 | , subscription = 58 | pairs 59 | |> List.Extra.find (\( fnName, _ ) -> fnName == "subscription") 60 | |> Maybe.map Tuple.second 61 | } 62 | } 63 | ) 64 | Layout.maybeLayout 65 | effectWhereClause 66 | Layout.maybeLayout 67 | (ParserWithComments.many 68 | (ParserFast.symbolFollowedBy "," (Layout.maybeAroundBothSides effectWhereClause)) 69 | ) 70 | ) 71 | |> ParserFast.followedBySymbol "}" 72 | 73 | 74 | effectWhereClauses : Parser (WithComments { command : Maybe (Node String), subscription : Maybe (Node String) }) 75 | effectWhereClauses = 76 | ParserFast.map2 77 | (\commentsBefore whereResult -> 78 | { comments = commentsBefore |> Rope.prependTo whereResult.comments 79 | , syntax = whereResult.syntax 80 | } 81 | ) 82 | (ParserFast.keywordFollowedBy "where" Layout.maybeLayout) 83 | whereBlock 84 | 85 | 86 | effectModuleDefinition : Parser (WithComments (Node Module)) 87 | effectModuleDefinition = 88 | ParserFast.map7WithRange 89 | (\range commentsAfterEffect commentsAfterModule name commentsAfterName whereClauses commentsAfterWhereClauses exp -> 90 | { comments = 91 | commentsAfterEffect 92 | |> Rope.prependTo commentsAfterModule 93 | |> Rope.prependTo commentsAfterName 94 | |> Rope.prependTo whereClauses.comments 95 | |> Rope.prependTo commentsAfterWhereClauses 96 | |> Rope.prependTo exp.comments 97 | , syntax = 98 | Node range 99 | (EffectModule 100 | { moduleName = name 101 | , exposingList = exp.syntax 102 | , command = whereClauses.syntax.command 103 | , subscription = whereClauses.syntax.subscription 104 | } 105 | ) 106 | } 107 | ) 108 | (ParserFast.keywordFollowedBy "effect" Layout.maybeLayout) 109 | (ParserFast.keywordFollowedBy "module" Layout.maybeLayout) 110 | moduleName 111 | Layout.maybeLayout 112 | effectWhereClauses 113 | Layout.maybeLayout 114 | exposeDefinition 115 | 116 | 117 | normalModuleDefinition : Parser (WithComments (Node Module)) 118 | normalModuleDefinition = 119 | ParserFast.map4WithRange 120 | (\range commentsAfterModule moduleName commentsAfterModuleName exposingList -> 121 | { comments = 122 | commentsAfterModule 123 | |> Rope.prependTo commentsAfterModuleName 124 | |> Rope.prependTo exposingList.comments 125 | , syntax = 126 | Node range 127 | (NormalModule 128 | { moduleName = moduleName 129 | , exposingList = exposingList.syntax 130 | } 131 | ) 132 | } 133 | ) 134 | (ParserFast.keywordFollowedBy "module" Layout.maybeLayout) 135 | moduleName 136 | Layout.maybeLayout 137 | exposeDefinition 138 | 139 | 140 | portModuleDefinition : Parser (WithComments (Node Module)) 141 | portModuleDefinition = 142 | ParserFast.map5WithRange 143 | (\range commentsAfterPort commentsAfterModule moduleName commentsAfterModuleName exposingList -> 144 | { comments = 145 | commentsAfterPort 146 | |> Rope.prependTo commentsAfterModule 147 | |> Rope.prependTo commentsAfterModuleName 148 | |> Rope.prependTo exposingList.comments 149 | , syntax = 150 | Node range 151 | (PortModule { moduleName = moduleName, exposingList = exposingList.syntax }) 152 | } 153 | ) 154 | (ParserFast.keywordFollowedBy "port" Layout.maybeLayout) 155 | (ParserFast.keywordFollowedBy "module" Layout.maybeLayout) 156 | moduleName 157 | Layout.maybeLayout 158 | exposeDefinition 159 | -------------------------------------------------------------------------------- /src/Elm/Parser/Layout.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.Layout exposing 2 | ( layoutStrict 3 | , layoutStrictFollowedBy 4 | , layoutStrictFollowedByComments 5 | , layoutStrictFollowedByWithComments 6 | , maybeAroundBothSides 7 | , maybeLayout 8 | , moduleLevelIndentationFollowedBy 9 | , onTopIndentationFollowedBy 10 | , optimisticLayout 11 | , positivelyIndentedFollowedBy 12 | , positivelyIndentedPlusFollowedBy 13 | ) 14 | 15 | import Elm.Parser.Comments as Comments 16 | import ParserFast exposing (Parser) 17 | import ParserWithComments exposing (Comments, WithComments) 18 | import Rope 19 | 20 | 21 | whitespaceAndCommentsOrEmpty : Parser Comments 22 | whitespaceAndCommentsOrEmpty = 23 | ParserFast.skipWhileWhitespaceFollowedBy 24 | -- whitespace can't be followed by more whitespace 25 | -- 26 | -- since comments are comparatively rare 27 | -- but expensive to check for, we allow shortcutting 28 | (ParserFast.offsetSourceAndThenOrSucceed 29 | (\offset source -> 30 | case source |> String.slice offset (offset + 2) of 31 | "--" -> 32 | -- this will always succeed from here, so no need to fall back to Rope.empty 33 | Just fromSingleLineCommentNode 34 | 35 | "{-" -> 36 | Just fromMultilineCommentNodeOrEmptyOnProblem 37 | 38 | _ -> 39 | Nothing 40 | ) 41 | Rope.empty 42 | ) 43 | 44 | 45 | fromMultilineCommentNodeOrEmptyOnProblem : Parser Comments 46 | fromMultilineCommentNodeOrEmptyOnProblem = 47 | ParserFast.map2OrSucceed 48 | (\comment commentsAfter -> 49 | Rope.one comment |> Rope.filledPrependTo commentsAfter 50 | ) 51 | (Comments.multilineComment 52 | |> ParserFast.followedBySkipWhileWhitespace 53 | ) 54 | whitespaceAndCommentsOrEmptyLoop 55 | Rope.empty 56 | 57 | 58 | fromSingleLineCommentNode : Parser Comments 59 | fromSingleLineCommentNode = 60 | ParserFast.map2 61 | (\content commentsAfter -> 62 | Rope.one content |> Rope.filledPrependTo commentsAfter 63 | ) 64 | (Comments.singleLineComment 65 | |> ParserFast.followedBySkipWhileWhitespace 66 | ) 67 | whitespaceAndCommentsOrEmptyLoop 68 | 69 | 70 | whitespaceAndCommentsOrEmptyLoop : Parser Comments 71 | whitespaceAndCommentsOrEmptyLoop = 72 | ParserFast.loopWhileSucceeds 73 | (ParserFast.oneOf2 74 | Comments.singleLineComment 75 | Comments.multilineComment 76 | |> ParserFast.followedBySkipWhileWhitespace 77 | ) 78 | Rope.empty 79 | (\right soFar -> soFar |> Rope.prependToFilled (Rope.one right)) 80 | identity 81 | 82 | 83 | maybeLayout : Parser Comments 84 | maybeLayout = 85 | whitespaceAndCommentsOrEmpty |> endsPositivelyIndented 86 | 87 | 88 | endsPositivelyIndented : Parser a -> Parser a 89 | endsPositivelyIndented parser = 90 | ParserFast.validateEndColumnIndentation 91 | (\column indent -> column > indent) 92 | "must be positively indented" 93 | parser 94 | 95 | 96 | {-| Check that the indentation of an already parsed token 97 | would be valid after [`maybeLayout`](#maybeLayout) 98 | -} 99 | positivelyIndentedPlusFollowedBy : Int -> Parser a -> Parser a 100 | positivelyIndentedPlusFollowedBy extraIndent nextParser = 101 | ParserFast.columnIndentAndThen 102 | (\column indent -> 103 | if column > indent + extraIndent then 104 | nextParser 105 | 106 | else 107 | problemPositivelyIndented 108 | ) 109 | 110 | 111 | positivelyIndentedFollowedBy : Parser a -> Parser a 112 | positivelyIndentedFollowedBy nextParser = 113 | ParserFast.columnIndentAndThen 114 | (\column indent -> 115 | if column > indent then 116 | nextParser 117 | 118 | else 119 | problemPositivelyIndented 120 | ) 121 | 122 | 123 | problemPositivelyIndented : Parser a 124 | problemPositivelyIndented = 125 | ParserFast.problem "must be positively indented" 126 | 127 | 128 | optimisticLayout : Parser Comments 129 | optimisticLayout = 130 | whitespaceAndCommentsOrEmpty 131 | 132 | 133 | layoutStrictFollowedByComments : Parser Comments -> Parser Comments 134 | layoutStrictFollowedByComments nextParser = 135 | ParserFast.map2 136 | (\commentsBefore afterComments -> 137 | commentsBefore |> Rope.prependTo afterComments 138 | ) 139 | optimisticLayout 140 | (onTopIndentationFollowedBy nextParser) 141 | 142 | 143 | layoutStrictFollowedByWithComments : Parser (WithComments syntax) -> Parser (WithComments syntax) 144 | layoutStrictFollowedByWithComments nextParser = 145 | ParserFast.map2 146 | (\commentsBefore after -> 147 | { comments = commentsBefore |> Rope.prependTo after.comments 148 | , syntax = after.syntax 149 | } 150 | ) 151 | optimisticLayout 152 | (onTopIndentationFollowedBy nextParser) 153 | 154 | 155 | layoutStrictFollowedBy : Parser syntax -> Parser (WithComments syntax) 156 | layoutStrictFollowedBy nextParser = 157 | ParserFast.map2 158 | (\commentsBefore after -> 159 | { comments = commentsBefore, syntax = after } 160 | ) 161 | optimisticLayout 162 | (onTopIndentationFollowedBy nextParser) 163 | 164 | 165 | layoutStrict : Parser Comments 166 | layoutStrict = 167 | optimisticLayout |> endsTopIndented 168 | 169 | 170 | moduleLevelIndentationFollowedBy : Parser a -> Parser a 171 | moduleLevelIndentationFollowedBy nextParser = 172 | ParserFast.columnAndThen 173 | (\column -> 174 | if column == 1 then 175 | nextParser 176 | 177 | else 178 | problemModuleLevelIndentation 179 | ) 180 | 181 | 182 | problemModuleLevelIndentation : Parser a 183 | problemModuleLevelIndentation = 184 | ParserFast.problem "must be on module-level indentation" 185 | 186 | 187 | endsTopIndented : Parser a -> Parser a 188 | endsTopIndented parser = 189 | ParserFast.validateEndColumnIndentation 190 | (\column indent -> column - indent == 0) 191 | "must be on top indentation" 192 | parser 193 | 194 | 195 | onTopIndentationFollowedBy : Parser a -> Parser a 196 | onTopIndentationFollowedBy nextParser = 197 | ParserFast.columnIndentAndThen 198 | (\column indent -> 199 | if column - indent == 0 then 200 | nextParser 201 | 202 | else 203 | problemTopIndentation 204 | ) 205 | 206 | 207 | problemTopIndentation : Parser a 208 | problemTopIndentation = 209 | ParserFast.problem "must be on top indentation" 210 | 211 | 212 | maybeAroundBothSides : Parser (WithComments b) -> Parser (WithComments b) 213 | maybeAroundBothSides x = 214 | ParserFast.map3 215 | (\before v after -> 216 | { comments = 217 | before 218 | |> Rope.prependTo v.comments 219 | |> Rope.prependTo after 220 | , syntax = v.syntax 221 | } 222 | ) 223 | maybeLayout 224 | x 225 | maybeLayout 226 | -------------------------------------------------------------------------------- /tests/Elm/Parser/TokenTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.TokenTests exposing (all) 2 | 3 | import Elm.Parser.Declarations 4 | import Elm.Parser.TestUtil exposing (..) 5 | import Elm.Parser.Tokens as Parser 6 | import Expect 7 | import Test exposing (..) 8 | 9 | 10 | longString : String 11 | longString = 12 | "\"" ++ String.repeat (5 * 10 ^ 5) "a" ++ "\"" 13 | 14 | 15 | longMultiLineString : String 16 | longMultiLineString = 17 | "\"\"\"" ++ String.repeat (5 * 10 ^ 5) "a" ++ "\"\"\"" 18 | 19 | 20 | all : Test 21 | all = 22 | describe "TokenTests" 23 | [ test "functionName" <| 24 | \() -> 25 | parse "foo" Parser.functionName 26 | |> Expect.equal (Just "foo") 27 | , test "functionName may not be a keyword" <| 28 | \() -> 29 | parse "type" Parser.functionName 30 | |> Expect.equal Nothing 31 | , test "functionName may be a keyword suffixed with an underscore" <| 32 | \() -> 33 | parse "type_" Parser.functionName 34 | |> Expect.equal (Just "type_") 35 | , test "functionName not empty" <| 36 | \() -> 37 | parse "" Parser.functionName 38 | |> Expect.equal Nothing 39 | , test "functionName with number" <| 40 | \() -> 41 | parse "n1" Parser.functionName 42 | |> Expect.equal (Just "n1") 43 | , test "alias can be a functionName (it is not reserved)" <| 44 | \() -> 45 | parse "alias" Parser.functionName 46 | |> Expect.equal (Just "alias") 47 | , test "infix can be a functionName (it is not reserved)" <| 48 | \() -> 49 | parse "infix" Parser.functionName 50 | |> Expect.equal (Just "infix") 51 | , test "functionName is not matched with 'if'" <| 52 | \() -> 53 | parse "if" Parser.functionName 54 | |> Expect.equal Nothing 55 | , test "functionName with _" <| 56 | \() -> 57 | parse "foo_" Parser.functionName 58 | |> Expect.equal (Just "foo_") 59 | , test "typeName" <| 60 | \() -> 61 | parse "MyCmd" Parser.typeName 62 | |> Expect.equal (Just "MyCmd") 63 | , test "typeName not empty" <| 64 | \() -> 65 | parse "" Parser.typeName 66 | |> Expect.equal Nothing 67 | , test "typeName with number" <| 68 | \() -> 69 | parse "T1" Parser.typeName 70 | |> Expect.equal (Just "T1") 71 | , test "operatorToken 11 -- is not an operator" <| 72 | \() -> 73 | parse "a = (--)" Elm.Parser.Declarations.declaration 74 | |> Expect.equal Nothing 75 | , test "operatorToken 14" <| 76 | \() -> 77 | parse "a = (=)" Elm.Parser.Declarations.declaration 78 | |> Expect.equal Nothing 79 | , test "operatorToken 15" <| 80 | \() -> 81 | parse "a = (?)" Elm.Parser.Declarations.declaration 82 | |> Expect.equal Nothing 83 | , test "multiline string" <| 84 | \() -> 85 | parse "\"\"\"Bar foo \n a\"\"\"" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) 86 | |> Expect.equal (Just "Bar foo \n a") 87 | , test "multiline string escape" <| 88 | \() -> 89 | parse """\"\"\" \\\"\"\" \"\"\"""" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) 90 | |> Expect.equal (Just """ \"\"\" """) 91 | , test "character escaped" <| 92 | \() -> 93 | parse "'\\''" (Parser.characterLiteralMapWithRange (\_ c -> c)) 94 | |> Expect.equal (Just '\'') 95 | , test "character escaped - 2" <| 96 | \() -> 97 | parse "'\\r'" (Parser.characterLiteralMapWithRange (\_ c -> c)) 98 | |> Expect.equal (Just '\u{000D}') 99 | , test "unicode char" <| 100 | \() -> 101 | parse "'\\u{000D}'" (Parser.characterLiteralMapWithRange (\_ c -> c)) 102 | |> Expect.equal (Just '\u{000D}') 103 | , test "unicode char with lowercase hex" <| 104 | \() -> 105 | parse "'\\u{000d}'" (Parser.characterLiteralMapWithRange (\_ c -> c)) 106 | |> Expect.equal (Just '\u{000D}') 107 | , test "string escaped 3" <| 108 | \() -> 109 | parse "\"\\\"\"" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) 110 | |> Expect.equal (Just "\"") 111 | , test "string escaped" <| 112 | \() -> 113 | parse "\"foo\\\\\"" (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) 114 | |> Expect.equal (Just "foo\\") 115 | , test "character escaped 3" <| 116 | \() -> 117 | parse "'\\n'" (Parser.characterLiteralMapWithRange (\_ c -> c)) 118 | |> Expect.equal (Just '\n') 119 | , test "long string" <| 120 | \() -> 121 | parse longString (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) 122 | |> Expect.notEqual Nothing 123 | , test "long multi line string" <| 124 | \() -> 125 | parse longMultiLineString (Parser.singleOrTripleQuotedStringLiteralMapWithRange (\_ s -> s)) 126 | |> Expect.notEqual Nothing 127 | , test "ρ function" <| 128 | \() -> 129 | parse "ρ" Parser.functionName 130 | |> Expect.notEqual Nothing 131 | , test "ε2 function" <| 132 | \() -> 133 | parse "ε2" Parser.functionName 134 | |> Expect.notEqual Nothing 135 | , test "εε function" <| 136 | \() -> 137 | parse "εε" Parser.functionName 138 | |> Expect.notEqual Nothing 139 | , test "ρ uppercase function" <| 140 | \() -> 141 | parse (String.toUpper "ρ") Parser.functionName 142 | |> Expect.equal Nothing 143 | , test "ε uppercase function" <| 144 | \() -> 145 | parse (String.toUpper "ε") Parser.functionName 146 | |> Expect.equal Nothing 147 | , test "ρ type name" <| 148 | \() -> 149 | parse "ρ" Parser.typeName 150 | |> Expect.equal Nothing 151 | , test "ε2 type name" <| 152 | \() -> 153 | parse "ε2" Parser.typeName 154 | |> Expect.equal Nothing 155 | , test "εε type name" <| 156 | \() -> 157 | parse "εε" Parser.typeName 158 | |> Expect.equal Nothing 159 | , test "ρ uppercase type name" <| 160 | \() -> 161 | parse (String.toUpper "ρ") Parser.typeName 162 | |> Expect.notEqual Nothing 163 | , test "ε uppercase type name" <| 164 | \() -> 165 | parse (String.toUpper "ε") Parser.typeName 166 | |> Expect.notEqual Nothing 167 | ] 168 | -------------------------------------------------------------------------------- /tests/Elm/Parser/ExposeTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.ExposeTests exposing (all) 2 | 3 | import Elm.Parser.Expose exposing (exposeDefinition) 4 | import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil 5 | import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) 6 | import Elm.Syntax.Node as Node exposing (Node(..)) 7 | import Expect 8 | import ParserFast 9 | import Test exposing (Test, describe, test) 10 | 11 | 12 | all : Test 13 | all = 14 | describe "ExposeTests" 15 | [ test "Exposing all" <| 16 | \() -> 17 | "exposing (..)" 18 | |> expectAst (All { start = { row = 1, column = 11 }, end = { row = 1, column = 13 } }) 19 | , test "Exposing all with spacing and comment" <| 20 | \() -> 21 | """exposing ( 22 | .. -- foo 23 | )""" 24 | |> expectAstWithComments 25 | { ast = All { start = { row = 2, column = 3 }, end = { row = 3, column = 3 } } 26 | , comments = [ Node { start = { row = 2, column = 6 }, end = { row = 2, column = 12 } } "-- foo" ] 27 | } 28 | , test "should fail to parse multi-line exposing all when closing parens is at the end of a line" <| 29 | \() -> 30 | """exposing ( 31 | .. 32 | )""" 33 | |> expectInvalidWithIndent1 34 | , test "should fail to parse empty with just 1 `.`" <| 35 | \() -> 36 | "exposing ( . )" 37 | |> expectInvalid 38 | , test "should fail to parse empty with just 3 `...`" <| 39 | \() -> 40 | "exposing ( ... )" 41 | |> expectInvalid 42 | , test "should fail to parse empty with 2 spaced `.`" <| 43 | \() -> 44 | "exposing (. .)" 45 | |> expectInvalid 46 | , test "should fail to parse empty exposing list" <| 47 | \() -> 48 | "exposing ()" 49 | |> expectInvalid 50 | , test "Explicit exposing list" <| 51 | \() -> 52 | "exposing (Model,Msg(..),Info(..),init,(::))" 53 | |> expectAst 54 | (Explicit 55 | [ Node { start = { row = 1, column = 11 }, end = { row = 1, column = 16 } } (TypeOrAliasExpose "Model") 56 | , Node { start = { row = 1, column = 17 }, end = { row = 1, column = 24 } } 57 | (TypeExpose 58 | { name = "Msg" 59 | , open = Just { start = { row = 1, column = 20 }, end = { row = 1, column = 24 } } 60 | } 61 | ) 62 | , Node { start = { row = 1, column = 25 }, end = { row = 1, column = 33 } } 63 | (TypeExpose 64 | { name = "Info" 65 | , open = Just { start = { row = 1, column = 29 }, end = { row = 1, column = 33 } } 66 | } 67 | ) 68 | , Node { start = { row = 1, column = 34 }, end = { row = 1, column = 38 } } (FunctionExpose "init") 69 | , Node { start = { row = 1, column = 39 }, end = { row = 1, column = 43 } } (InfixExpose "::") 70 | ] 71 | ) 72 | , test "exposingList with spacing on one line" <| 73 | \() -> 74 | "exposing (Model, Msg, Info (..) ,init,(::) )" 75 | |> expectAst 76 | (Explicit 77 | [ Node { start = { row = 1, column = 11 }, end = { row = 1, column = 16 } } (TypeOrAliasExpose "Model") 78 | , Node { start = { row = 1, column = 18 }, end = { row = 1, column = 21 } } (TypeOrAliasExpose "Msg") 79 | , Node { start = { row = 1, column = 23 }, end = { row = 1, column = 34 } } 80 | (TypeExpose 81 | { name = "Info" 82 | , open = Just { start = { row = 1, column = 30 }, end = { row = 1, column = 34 } } 83 | } 84 | ) 85 | , Node { start = { row = 1, column = 38 }, end = { row = 1, column = 42 } } (FunctionExpose "init") 86 | , Node { start = { row = 1, column = 43 }, end = { row = 1, column = 47 } } (InfixExpose "::") 87 | ] 88 | ) 89 | , test "Explicit exposing list with spaces and newlines" <| 90 | \() -> 91 | """exposing 92 | ( A 93 | , B(..) 94 | , Info (..) 95 | , init , 96 | (::) 97 | )""" 98 | |> expectAst 99 | (Explicit 100 | [ Node { start = { row = 2, column = 7 }, end = { row = 2, column = 8 } } (TypeOrAliasExpose "A") 101 | , Node { start = { row = 3, column = 7 }, end = { row = 3, column = 12 } } 102 | (TypeExpose 103 | { name = "B" 104 | , open = Just { start = { row = 3, column = 8 }, end = { row = 3, column = 12 } } 105 | } 106 | ) 107 | , Node { start = { row = 4, column = 7 }, end = { row = 4, column = 16 } } 108 | (TypeExpose 109 | { name = "Info" 110 | , open = Just { start = { row = 4, column = 12 }, end = { row = 4, column = 16 } } 111 | } 112 | ) 113 | , Node { start = { row = 5, column = 12 }, end = { row = 5, column = 16 } } (FunctionExpose "init") 114 | , Node { start = { row = 6, column = 2 }, end = { row = 6, column = 6 } } (InfixExpose "::") 115 | ] 116 | ) 117 | , test "Comments inside the exposing clause" <| 118 | \() -> 119 | "exposing (foo\n --bar\n )" 120 | |> expectAstWithComments 121 | { ast = 122 | Explicit 123 | [ Node { start = { row = 1, column = 11 }, end = { row = 1, column = 14 } } 124 | (FunctionExpose "foo") 125 | ] 126 | , comments = [ Node { start = { row = 2, column = 2 }, end = { row = 2, column = 7 } } "--bar" ] 127 | } 128 | ] 129 | 130 | 131 | expectAst : Exposing -> String -> Expect.Expectation 132 | expectAst = 133 | ParserWithCommentsUtil.expectAst 134 | (ParserFast.map (\expose -> { comments = expose.comments, syntax = Node.value expose.syntax }) 135 | exposeDefinition 136 | ) 137 | 138 | 139 | expectAstWithComments : { ast : Exposing, comments : List (Node String) } -> String -> Expect.Expectation 140 | expectAstWithComments = 141 | ParserWithCommentsUtil.expectAstWithComments 142 | (ParserFast.map (\expose -> { comments = expose.comments, syntax = Node.value expose.syntax }) 143 | exposeDefinition 144 | ) 145 | 146 | 147 | expectInvalid : String -> Expect.Expectation 148 | expectInvalid = 149 | ParserWithCommentsUtil.expectInvalid exposeDefinition 150 | 151 | 152 | expectInvalidWithIndent1 : String -> Expect.Expectation 153 | expectInvalidWithIndent1 = 154 | ParserWithCommentsUtil.expectInvalid exposeDefinition 155 | -------------------------------------------------------------------------------- /src/Elm/Interface.elm: -------------------------------------------------------------------------------- 1 | module Elm.Interface exposing 2 | ( Interface, Exposed(..) 3 | , build, exposesAlias, exposesFunction, operators 4 | ) 5 | 6 | {-| A type that represents the interface for an Elm module. 7 | You can see this as a trimmed down version of a file that only contains the header (`module X exposing (..)`) and some small set of additional data. 8 | 9 | 10 | ## Types 11 | 12 | @docs Interface, Exposed 13 | 14 | 15 | ## Functions 16 | 17 | @docs build, exposesAlias, exposesFunction, operators 18 | 19 | -} 20 | 21 | import Dict exposing (Dict) 22 | import Elm.Internal.RawFile as InternalRawFile 23 | import Elm.RawFile as RawFile 24 | import Elm.Syntax.Declaration exposing (Declaration(..)) 25 | import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) 26 | import Elm.Syntax.Expression exposing (FunctionImplementation) 27 | import Elm.Syntax.File exposing (File) 28 | import Elm.Syntax.Infix exposing (Infix, InfixDirection(..)) 29 | import Elm.Syntax.Module as Module 30 | import Elm.Syntax.Node as Node exposing (Node(..)) 31 | 32 | 33 | {-| An interface is just a list of 'things' that are exposed by a module. 34 | 35 | [ Type "Color" [ "Red", "Blue" ], Function "asRgb" ] 36 | 37 | -} 38 | type alias Interface = 39 | List Exposed 40 | 41 | 42 | {-| Union type for the things that a module can expose. These are `Function`s, `CustomType`s, and `Alias`es. 43 | 44 | Elm core packages can also define `Operator`s, and thus we take that into account as well. 45 | The `Infix` type alias will contain all the information regarding the operator 46 | 47 | -} 48 | type Exposed 49 | = Function String 50 | | CustomType ( String, List String ) 51 | | Alias String 52 | | Operator Infix 53 | 54 | 55 | {-| A function to check whether an `Interface` exposes an certain type alias. 56 | -} 57 | exposesAlias : String -> Interface -> Bool 58 | exposesAlias k interface = 59 | List.any 60 | (\x -> 61 | case x of 62 | Alias l -> 63 | k == l 64 | 65 | _ -> 66 | False 67 | ) 68 | interface 69 | 70 | 71 | {-| Check whether an `Interface` exposes an function. 72 | 73 | exposesFunction "A" [ Function "A", CustomType "B", [ "C" ] ] == True 74 | exposesFunction "B" [ Function "A", CustomType "B", [ "C" ] ] == False 75 | exposesFunction "<" [ Infix { operator = "<" , ... } ] == True 76 | exposesFunction "A" [ Alias "A" ] == False 77 | 78 | -} 79 | exposesFunction : String -> Interface -> Bool 80 | exposesFunction k interface = 81 | List.any 82 | (\x -> 83 | case x of 84 | Function l -> 85 | k == l 86 | 87 | CustomType ( _, constructors ) -> 88 | List.member k constructors 89 | 90 | Operator inf -> 91 | Node.value inf.operator == k 92 | 93 | Alias _ -> 94 | False 95 | ) 96 | interface 97 | 98 | 99 | {-| Retrieve all operators exposed by the `Interface` 100 | -} 101 | operators : Interface -> List Infix 102 | operators = 103 | List.filterMap 104 | (\interface -> 105 | case interface of 106 | Operator operator -> 107 | Just operator 108 | 109 | _ -> 110 | Nothing 111 | ) 112 | 113 | 114 | {-| Build an interface from a file 115 | -} 116 | build : RawFile.RawFile -> Interface 117 | build (InternalRawFile.Raw file) = 118 | let 119 | fileDefinitionList : List ( String, Exposed ) 120 | fileDefinitionList = 121 | fileToDefinitions file 122 | in 123 | case Module.exposingList (Node.value file.moduleDefinition) of 124 | Explicit x -> 125 | buildInterfaceFromExplicit x (Dict.fromList fileDefinitionList) 126 | 127 | All _ -> 128 | List.map Tuple.second fileDefinitionList 129 | 130 | 131 | buildInterfaceFromExplicit : List (Node TopLevelExpose) -> Dict String Exposed -> Interface 132 | buildInterfaceFromExplicit x exposedDict = 133 | List.filterMap 134 | (\(Node _ expose) -> 135 | case expose of 136 | InfixExpose k -> 137 | Dict.get k exposedDict 138 | 139 | TypeOrAliasExpose s -> 140 | Dict.get s exposedDict 141 | |> Maybe.map (ifCustomType (\( name, _ ) -> CustomType ( name, [] ))) 142 | 143 | FunctionExpose s -> 144 | Just <| Function s 145 | 146 | TypeExpose exposedType -> 147 | case exposedType.open of 148 | Nothing -> 149 | Just <| CustomType ( exposedType.name, [] ) 150 | 151 | Just _ -> 152 | Dict.get exposedType.name exposedDict 153 | ) 154 | x 155 | 156 | 157 | ifCustomType : (( String, List String ) -> Exposed) -> Exposed -> Exposed 158 | ifCustomType f i = 159 | case i of 160 | CustomType t -> 161 | f t 162 | 163 | _ -> 164 | i 165 | 166 | 167 | fileToDefinitions : File -> List ( String, Exposed ) 168 | fileToDefinitions file = 169 | let 170 | allDeclarations : List ( String, Exposed ) 171 | allDeclarations = 172 | List.filterMap 173 | (\(Node _ decl) -> 174 | case decl of 175 | CustomTypeDeclaration t -> 176 | Just ( Node.value t.name, CustomType ( Node.value t.name, t.constructors |> List.map (Node.value >> .name >> Node.value) ) ) 177 | 178 | AliasDeclaration a -> 179 | Just ( Node.value a.name, Alias <| Node.value a.name ) 180 | 181 | PortDeclaration p -> 182 | Just ( Node.value p.name, Function (Node.value p.name) ) 183 | 184 | FunctionDeclaration f -> 185 | let 186 | declaration : FunctionImplementation 187 | declaration = 188 | Node.value f.declaration 189 | 190 | name : String 191 | name = 192 | Node.value declaration.name 193 | in 194 | Just ( name, Function <| name ) 195 | 196 | InfixDeclaration i -> 197 | Just ( Node.value i.operator, Operator i ) 198 | 199 | Destructuring _ _ -> 200 | Nothing 201 | ) 202 | file.declarations 203 | in 204 | -- I really don't understand what this part of the function does. 205 | allDeclarations 206 | |> List.map 207 | (\( name, _ ) -> 208 | -- AFAIK, it's not possible to have two declarations with the same name 209 | -- in the same file, so this seems pointless. 210 | allDeclarations 211 | |> List.filter (\( otherDeclarationName, _ ) -> otherDeclarationName == name) 212 | ) 213 | |> List.filterMap resolveGroup 214 | 215 | 216 | resolveGroup : List ( String, Exposed ) -> Maybe ( String, Exposed ) 217 | resolveGroup g = 218 | case g of 219 | [] -> 220 | Nothing 221 | 222 | [ x ] -> 223 | Just x 224 | 225 | [ ( n1, t1 ), ( _, t2 ) ] -> 226 | getValidOperatorInterface t1 t2 227 | |> Maybe.map (\a -> ( n1, a )) 228 | 229 | _ -> 230 | Nothing 231 | 232 | 233 | getValidOperatorInterface : Exposed -> Exposed -> Maybe Exposed 234 | getValidOperatorInterface t1 t2 = 235 | case ( t1, t2 ) of 236 | ( Operator x, Operator y ) -> 237 | if Node.value x.precedence == 5 && Node.value x.direction == Left then 238 | Just <| Operator y 239 | 240 | else 241 | Just <| Operator x 242 | 243 | _ -> 244 | Nothing 245 | -------------------------------------------------------------------------------- /src/Elm/Syntax/Pattern.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.Pattern exposing 2 | ( Pattern(..), QualifiedNameRef 3 | , moduleNames 4 | , encode, decoder 5 | ) 6 | 7 | {-| This syntax represents the patterns. 8 | For example: 9 | 10 | Just x as someMaybe 11 | {name, age} 12 | 13 | 14 | ## Types 15 | 16 | @docs Pattern, QualifiedNameRef 17 | 18 | 19 | ## Functions 20 | 21 | @docs moduleNames 22 | 23 | 24 | ## Serialization 25 | 26 | @docs encode, decoder 27 | 28 | -} 29 | 30 | import Elm.Json.Util exposing (decodeTyped, encodeTyped) 31 | import Elm.Syntax.ModuleName as ModuleName exposing (ModuleName) 32 | import Elm.Syntax.Node as Node exposing (Node(..)) 33 | import Json.Decode as JD exposing (Decoder) 34 | import Json.Encode as JE exposing (Value) 35 | 36 | 37 | {-| Custom type for all patterns such as: 38 | 39 | - `AllPattern`: `_` 40 | - `UnitPattern`: `()` 41 | - `CharPattern`: `'c'` 42 | - `StringPattern`: `"hello"` 43 | - `IntPattern`: `42` 44 | - `HexPattern`: `0x11` 45 | - `FloatPattern`: `42.0` 46 | - `TuplePattern`: `(a, b)` 47 | - `RecordPattern`: `{name, age}` 48 | - `UnConsPattern`: `x :: xs` 49 | - `ListPattern`: `[ x, y ]` 50 | - `VarPattern`: `x` 51 | - `NamedPattern`: `Just _` 52 | - `AsPattern`: `_ as x` 53 | - `ParenthesizedPattern`: `( _ )` 54 | 55 | -} 56 | type Pattern 57 | = AllPattern 58 | | UnitPattern 59 | | CharPattern Char 60 | | StringPattern String 61 | | IntPattern Int 62 | | HexPattern Int 63 | | FloatPattern Float 64 | | TuplePattern (List (Node Pattern)) 65 | | RecordPattern (List (Node String)) 66 | | UnConsPattern (Node Pattern) (Node Pattern) 67 | | ListPattern (List (Node Pattern)) 68 | | VarPattern String 69 | | NamedPattern QualifiedNameRef (List (Node Pattern)) 70 | | AsPattern (Node Pattern) (Node String) 71 | | ParenthesizedPattern (Node Pattern) 72 | 73 | 74 | {-| Qualified name reference such as `Maybe.Just`. 75 | -} 76 | type alias QualifiedNameRef = 77 | { moduleName : List String 78 | , name : String 79 | } 80 | 81 | 82 | {-| Get all the modules names that are used in the pattern (and its nested patterns). 83 | Use this to collect qualified patterns, such as `Maybe.Just x`. 84 | -} 85 | moduleNames : Pattern -> List ModuleName 86 | moduleNames p = 87 | -- TODO Remove this function or make it take a `Node Pattern` 88 | -- Should it return a `Set`? 89 | case p of 90 | TuplePattern xs -> 91 | List.concatMap (\(Node _ x) -> moduleNames x) xs 92 | 93 | RecordPattern _ -> 94 | [] 95 | 96 | UnConsPattern (Node _ left) (Node _ right) -> 97 | moduleNames left ++ moduleNames right 98 | 99 | ListPattern xs -> 100 | List.concatMap (\(Node _ x) -> moduleNames x) xs 101 | 102 | NamedPattern qualifiedNameRef subPatterns -> 103 | qualifiedNameRef.moduleName :: List.concatMap (\(Node _ x) -> moduleNames x) subPatterns 104 | 105 | AsPattern (Node _ inner) _ -> 106 | moduleNames inner 107 | 108 | ParenthesizedPattern (Node _ inner) -> 109 | moduleNames inner 110 | 111 | _ -> 112 | [] 113 | 114 | 115 | 116 | -- Serialization 117 | 118 | 119 | {-| Encode a `Pattern` syntax element to JSON. 120 | -} 121 | encode : Pattern -> Value 122 | encode pattern = 123 | case pattern of 124 | AllPattern -> 125 | encodeTyped "all" (JE.object []) 126 | 127 | UnitPattern -> 128 | encodeTyped "unit" (JE.object []) 129 | 130 | CharPattern c -> 131 | encodeTyped "char" 132 | (JE.object 133 | [ ( "value", JE.string <| String.fromChar c ) 134 | ] 135 | ) 136 | 137 | StringPattern v -> 138 | encodeTyped "string" 139 | (JE.object 140 | [ ( "value", JE.string v ) 141 | ] 142 | ) 143 | 144 | HexPattern h -> 145 | encodeTyped "hex" 146 | (JE.object 147 | [ ( "value", JE.int h ) 148 | ] 149 | ) 150 | 151 | IntPattern i -> 152 | encodeTyped "int" 153 | (JE.object 154 | [ ( "value", JE.int i ) 155 | ] 156 | ) 157 | 158 | FloatPattern f -> 159 | encodeTyped "float" 160 | (JE.object 161 | [ ( "value", JE.float f ) 162 | ] 163 | ) 164 | 165 | TuplePattern patterns -> 166 | encodeTyped "tuple" 167 | (JE.object 168 | [ ( "value", JE.list (Node.encode encode) patterns ) 169 | ] 170 | ) 171 | 172 | RecordPattern pointers -> 173 | encodeTyped "record" 174 | (JE.object 175 | [ ( "value", JE.list (Node.encode JE.string) pointers ) 176 | ] 177 | ) 178 | 179 | UnConsPattern p1 p2 -> 180 | encodeTyped "uncons" 181 | (JE.object 182 | [ ( "left", Node.encode encode p1 ) 183 | , ( "right", Node.encode encode p2 ) 184 | ] 185 | ) 186 | 187 | ListPattern patterns -> 188 | encodeTyped "list" 189 | (JE.object 190 | [ ( "value", JE.list (Node.encode encode) patterns ) 191 | ] 192 | ) 193 | 194 | VarPattern name -> 195 | encodeTyped "var" 196 | (JE.object 197 | [ ( "value", JE.string name ) 198 | ] 199 | ) 200 | 201 | NamedPattern qualifiedNameRef patterns -> 202 | encodeTyped "named" <| 203 | JE.object 204 | [ ( "qualified" 205 | , JE.object 206 | [ ( "moduleName", ModuleName.encode qualifiedNameRef.moduleName ) 207 | , ( "name", JE.string qualifiedNameRef.name ) 208 | ] 209 | ) 210 | , ( "patterns", JE.list (Node.encode encode) patterns ) 211 | ] 212 | 213 | AsPattern destructured name -> 214 | encodeTyped "as" <| 215 | JE.object 216 | [ ( "name", Node.encode JE.string name ) 217 | , ( "pattern", Node.encode encode destructured ) 218 | ] 219 | 220 | ParenthesizedPattern p1 -> 221 | encodeTyped "parentisized" 222 | (JE.object 223 | [ ( "value", Node.encode encode p1 ) 224 | ] 225 | ) 226 | 227 | 228 | {-| JSON decoder for a `Pattern` syntax element. 229 | -} 230 | decoder : Decoder Pattern 231 | decoder = 232 | JD.lazy 233 | (\() -> 234 | decodeTyped 235 | [ ( "all", JD.succeed AllPattern ) 236 | , ( "unit", JD.succeed UnitPattern ) 237 | , ( "char", JD.field "value" decodeChar |> JD.map CharPattern ) 238 | , ( "string", JD.field "value" JD.string |> JD.map StringPattern ) 239 | , ( "hex", JD.int |> JD.map HexPattern ) 240 | , ( "int", JD.field "value" JD.int |> JD.map IntPattern ) 241 | , ( "float", JD.field "value" JD.float |> JD.map FloatPattern ) 242 | , ( "tuple", JD.field "value" (JD.list (Node.decoder decoder)) |> JD.map TuplePattern ) 243 | , ( "record", JD.field "value" (JD.list (Node.decoder JD.string)) |> JD.map RecordPattern ) 244 | , ( "uncons", JD.map2 UnConsPattern (JD.field "left" (Node.decoder decoder)) (JD.field "right" (Node.decoder decoder)) ) 245 | , ( "list", JD.field "value" (JD.list (Node.decoder decoder)) |> JD.map ListPattern ) 246 | , ( "var", JD.field "value" JD.string |> JD.map VarPattern ) 247 | , ( "named", JD.map2 NamedPattern (JD.field "qualified" decodeQualifiedNameRef) (JD.field "patterns" (JD.list (Node.decoder decoder))) ) 248 | , ( "as", JD.map2 AsPattern (JD.field "pattern" (Node.decoder decoder)) (JD.field "name" (Node.decoder JD.string)) ) 249 | , ( "parentisized", JD.map ParenthesizedPattern (JD.field "value" (Node.decoder decoder)) ) 250 | ] 251 | ) 252 | 253 | 254 | decodeQualifiedNameRef : Decoder QualifiedNameRef 255 | decodeQualifiedNameRef = 256 | JD.map2 QualifiedNameRef 257 | (JD.field "moduleName" ModuleName.decoder) 258 | (JD.field "name" JD.string) 259 | 260 | 261 | decodeChar : Decoder Char 262 | decodeChar = 263 | JD.string 264 | |> JD.andThen 265 | (\s -> 266 | case String.uncons s of 267 | Just ( c, _ ) -> 268 | JD.succeed c 269 | 270 | Nothing -> 271 | JD.fail "Not a char" 272 | ) 273 | -------------------------------------------------------------------------------- /tests/Elm/Syntax/RangeTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Syntax.RangeTests exposing (suite) 2 | 3 | import Elm.Syntax.Range as Range exposing (Range) 4 | import Expect 5 | import Test exposing (Test, describe, test) 6 | 7 | 8 | suite : Test 9 | suite = 10 | describe "Elm.Syntax.Range" 11 | [ describe "combine" combineTests 12 | , describe "compare" compareTests 13 | , describe "compareLocations" compareLocationsTests 14 | ] 15 | 16 | 17 | combineTests : List Test 18 | combineTests = 19 | [ test "given an empty list is an empty range" <| 20 | \() -> 21 | Range.combine [] 22 | |> Expect.equal Range.empty 23 | , test "given a singleton list is the same range" <| 24 | \() -> 25 | let 26 | range : Range 27 | range = 28 | { start = { row = 1, column = 1 } 29 | , end = { row = 1, column = 9 } 30 | } 31 | in 32 | Range.combine [ range ] 33 | |> Expect.equal range 34 | , test "given a list of ranges, where one covers all the others, gives that range" <| 35 | \() -> 36 | let 37 | outerRange : Range 38 | outerRange = 39 | { start = { row = 1, column = 1 } 40 | , end = { row = 5, column = 100 } 41 | } 42 | 43 | ranges : List Range 44 | ranges = 45 | [ outerRange 46 | , { start = { row = 2, column = 1 } 47 | , end = { row = 2, column = 9 } 48 | } 49 | , { start = { row = 3, column = 5 } 50 | , end = { row = 4, column = 15 } 51 | } 52 | ] 53 | in 54 | Range.combine ranges 55 | |> Expect.equal outerRange 56 | , test "given a list of distinct ranges where none overlap, gives a range over all" <| 57 | \() -> 58 | let 59 | outerRange : Range 60 | outerRange = 61 | { start = { row = 2, column = 1 } 62 | , end = { row = 10, column = 69 } 63 | } 64 | 65 | ranges : List Range 66 | ranges = 67 | [ { start = outerRange.start 68 | , end = { row = 2, column = 21 } 69 | } 70 | , { start = { row = 4, column = 6 } 71 | , end = { row = 4, column = 100 } 72 | } 73 | , { start = { row = 9, column = 5 } 74 | , end = outerRange.end 75 | } 76 | ] 77 | in 78 | Range.combine ranges 79 | |> Expect.equal outerRange 80 | , test "given overlapping ranges, gives a range over both" <| 81 | \() -> 82 | let 83 | outerRange : Range 84 | outerRange = 85 | { start = { row = 1, column = 23 } 86 | , end = { row = 32, column = 5 } 87 | } 88 | 89 | ranges : List Range 90 | ranges = 91 | [ { start = { row = 15, column = 1 } 92 | , end = outerRange.end 93 | } 94 | , { start = outerRange.start 95 | , end = { row = 16, column = 27 } 96 | } 97 | ] 98 | in 99 | Range.combine ranges 100 | |> Expect.equal outerRange 101 | ] 102 | 103 | 104 | compareLocationsTests : List Test 105 | compareLocationsTests = 106 | [ describe "EQ" 107 | [ test "when locations are equal" <| 108 | \() -> 109 | let 110 | location : Range.Location 111 | location = 112 | { row = 1, column = 3 } 113 | in 114 | Range.compareLocations location location 115 | |> Expect.equal EQ 116 | ] 117 | , describe "LT" 118 | [ test "when left row < right row" <| 119 | \() -> 120 | let 121 | left : Range.Location 122 | left = 123 | { row = 1, column = 1 } 124 | 125 | right : Range.Location 126 | right = 127 | { left | row = 3 } 128 | in 129 | Range.compareLocations left right 130 | |> Expect.equal LT 131 | , test "when left row == right row and left column < right column" <| 132 | \() -> 133 | let 134 | left : Range.Location 135 | left = 136 | { row = 1, column = 1 } 137 | 138 | right : Range.Location 139 | right = 140 | { left | column = 3 } 141 | in 142 | Range.compareLocations left right 143 | |> Expect.equal LT 144 | ] 145 | , describe "GT" 146 | [ test "when left row > right row" <| 147 | \() -> 148 | let 149 | left : Range.Location 150 | left = 151 | { row = 3, column = 4 } 152 | 153 | right : Range.Location 154 | right = 155 | { left | row = 2 } 156 | in 157 | Range.compareLocations left right 158 | |> Expect.equal GT 159 | , test "when left row == right row and left column > right column" <| 160 | \() -> 161 | let 162 | left : Range.Location 163 | left = 164 | { row = 2, column = 6 } 165 | 166 | right : Range.Location 167 | right = 168 | { left | column = 3 } 169 | in 170 | Range.compareLocations left right 171 | |> Expect.equal GT 172 | ] 173 | ] 174 | 175 | 176 | compareTests : List Test 177 | compareTests = 178 | [ describe "EQ" 179 | [ test "when ranges are equal" <| 180 | \() -> 181 | let 182 | range : Range 183 | range = 184 | { start = { row = 1, column = 2 } 185 | , end = { row = 3, column = 4 } 186 | } 187 | in 188 | Range.compare range range 189 | |> Expect.equal EQ 190 | ] 191 | , describe "LT" 192 | [ test "when left start < right start" <| 193 | \() -> 194 | let 195 | left : Range 196 | left = 197 | { start = { row = 1, column = 1 } 198 | , end = { row = 1, column = 2 } 199 | } 200 | 201 | right : Range 202 | right = 203 | { left | end = { row = 2, column = 2 } } 204 | in 205 | Range.compare left right 206 | |> Expect.equal LT 207 | , test "when left start == right start and left end < right end" <| 208 | \() -> 209 | let 210 | left : Range 211 | left = 212 | { start = { row = 2, column = 1 } 213 | , end = { row = 2, column = 3 } 214 | } 215 | 216 | right : Range 217 | right = 218 | { left | end = { row = 2, column = 9 } } 219 | in 220 | Range.compare left right 221 | |> Expect.equal LT 222 | ] 223 | , describe "GT" 224 | [ test "when left start > right start" <| 225 | \() -> 226 | let 227 | left : Range 228 | left = 229 | { start = { row = 5, column = 6 } 230 | , end = { row = 5, column = 7 } 231 | } 232 | 233 | right : Range 234 | right = 235 | { left | start = { row = 5, column = 2 } } 236 | in 237 | Range.compare left right 238 | |> Expect.equal GT 239 | , test "when left start == right start and left end > right end" <| 240 | \() -> 241 | let 242 | left : Range 243 | left = 244 | { start = { row = 5, column = 2 } 245 | , end = { row = 5, column = 9 } 246 | } 247 | 248 | right : Range 249 | right = 250 | { left | end = { row = 5, column = 4 } } 251 | in 252 | Range.compare left right 253 | |> Expect.equal GT 254 | ] 255 | ] 256 | -------------------------------------------------------------------------------- /tests/Elm/Parser/CaseExpressionTests.elm: -------------------------------------------------------------------------------- 1 | module Elm.Parser.CaseExpressionTests exposing (all) 2 | 3 | import Elm.Parser.Expression exposing (expression) 4 | import Elm.Parser.ParserWithCommentsTestUtil as ParserWithCommentsUtil 5 | import Elm.Syntax.Expression exposing (..) 6 | import Elm.Syntax.Node exposing (Node(..)) 7 | import Elm.Syntax.Pattern exposing (..) 8 | import Expect 9 | import Test exposing (..) 10 | 11 | 12 | all : Test 13 | all = 14 | describe "CaseExpressionTests" 15 | [ test "should fail to parse when the matched expression has the wrong indentation" <| 16 | \() -> 17 | """case 18 | True 19 | of 20 | A -> 1""" 21 | |> expectInvalid 22 | , test "should fail to parse when the `of` keyword has the wrong indentation" <| 23 | \() -> 24 | """case True 25 | of 26 | A -> 1""" 27 | |> expectInvalid 28 | , test "should fail to parse a branch at the start of a line" <| 29 | \() -> 30 | """case True of 31 | True -> 1""" 32 | |> expectInvalid 33 | , test "should fail to parse when branch body starts at the start of a line" <| 34 | \() -> 35 | """case f of 36 | True -> 37 | 1""" 38 | |> expectInvalid 39 | , test "case expression" <| 40 | \() -> 41 | """case f of 42 | True -> 1 43 | False -> 2""" 44 | |> expectAst 45 | (Node { start = { row = 1, column = 1 }, end = { row = 3, column = 13 } } 46 | (CaseExpression 47 | { expression = 48 | Node { start = { row = 1, column = 6 }, end = { row = 1, column = 7 } } <| 49 | FunctionOrValue [] "f" 50 | , cases = 51 | [ ( Node { start = { row = 2, column = 3 }, end = { row = 2, column = 7 } } <| 52 | NamedPattern (QualifiedNameRef [] "True") [] 53 | , Node { start = { row = 2, column = 11 }, end = { row = 2, column = 12 } } <| 54 | Integer 1 55 | ) 56 | , ( Node { start = { row = 3, column = 3 }, end = { row = 3, column = 8 } } <| 57 | NamedPattern (QualifiedNameRef [] "False") [] 58 | , Node { start = { row = 3, column = 12 }, end = { row = 3, column = 13 } } <| 59 | Integer 2 60 | ) 61 | ] 62 | } 63 | ) 64 | ) 65 | , test "case expression with qualified imports" <| 66 | \() -> 67 | """case f of 68 | Foo.Bar -> 1""" 69 | |> expectAst 70 | (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 15 } } 71 | (CaseExpression 72 | { expression = 73 | Node { start = { row = 1, column = 6 }, end = { row = 1, column = 7 } } <| 74 | FunctionOrValue [] "f" 75 | , cases = 76 | [ ( Node { start = { row = 2, column = 3 }, end = { row = 2, column = 10 } } <| 77 | NamedPattern (QualifiedNameRef [ "Foo" ] "Bar") [] 78 | , Node { start = { row = 2, column = 14 }, end = { row = 2, column = 15 } } <| 79 | Integer 1 80 | ) 81 | ] 82 | } 83 | ) 84 | ) 85 | , test "case expression with no space between pattern and value" <| 86 | \() -> 87 | """case f of 88 | x->1""" 89 | |> expectAst 90 | (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 7 } } 91 | (CaseExpression 92 | { expression = 93 | Node { start = { row = 1, column = 6 }, end = { row = 1, column = 7 } } <| 94 | FunctionOrValue [] "f" 95 | , cases = 96 | [ ( Node { start = { row = 2, column = 3 }, end = { row = 2, column = 4 } } <| 97 | VarPattern "x" 98 | , Node { start = { row = 2, column = 6 }, end = { row = 2, column = 7 } } <| 99 | Integer 1 100 | ) 101 | ] 102 | } 103 | ) 104 | ) 105 | , test "should parse case expression with first branch on the same line as case of" <| 106 | \() -> 107 | """case x of True -> 1 108 | False -> 2""" 109 | |> expectAst 110 | (Node { start = { row = 1, column = 1 }, end = { row = 2, column = 21 } } 111 | (CaseExpression 112 | { expression = Node { start = { row = 1, column = 6 }, end = { row = 1, column = 7 } } (FunctionOrValue [] "x") 113 | , cases = 114 | [ ( Node { start = { row = 1, column = 11 }, end = { row = 1, column = 15 } } (NamedPattern { moduleName = [], name = "True" } []) 115 | , Node { start = { row = 1, column = 19 }, end = { row = 1, column = 20 } } (Integer 1) 116 | ) 117 | , ( Node { start = { row = 2, column = 11 }, end = { row = 2, column = 16 } } (NamedPattern { moduleName = [], name = "False" } []) 118 | , Node { start = { row = 2, column = 20 }, end = { row = 2, column = 21 } } (Integer 2) 119 | ) 120 | ] 121 | } 122 | ) 123 | ) 124 | , test "should parse case expression with a multiline pattern" <| 125 | \() -> 126 | """case x of 127 | \"\"\"single line triple quote\"\"\" -> 128 | 1 129 | \"\"\"multi line 130 | triple quote\"\"\" -> 131 | 2 132 | _ -> 3""" 133 | |> expectAst 134 | (Node { start = { row = 1, column = 1 }, end = { row = 7, column = 15 } } 135 | (CaseExpression 136 | { expression = 137 | Node 138 | { start = { row = 1, column = 6 } 139 | , end = { row = 1, column = 7 } 140 | } 141 | (FunctionOrValue [] "x") 142 | , cases = 143 | [ ( Node { start = { row = 2, column = 9 }, end = { row = 2, column = 39 } } (StringPattern "single line triple quote") 144 | , Node { start = { row = 3, column = 13 }, end = { row = 3, column = 14 } } (Integer 1) 145 | ) 146 | , ( Node { start = { row = 4, column = 9 }, end = { row = 5, column = 28 } } (StringPattern "multi line\n triple quote") 147 | , Node { start = { row = 6, column = 13 }, end = { row = 6, column = 14 } } (Integer 2) 148 | ) 149 | , ( Node { start = { row = 7, column = 9 }, end = { row = 7, column = 10 } } AllPattern 150 | , Node { start = { row = 7, column = 14 }, end = { row = 7, column = 15 } } (Integer 3) 151 | ) 152 | ] 153 | } 154 | ) 155 | ) 156 | , test "should fail to parse case expression with second branch indented differently than the first line (before)" <| 157 | \() -> 158 | expectInvalid """case f of 159 | True -> 1 160 | False -> 2""" 161 | , test "should fail to parse case expression with second branch indented differently than the first line (after)" <| 162 | \() -> 163 | """case f of 164 | True -> 1 165 | False -> 2 166 | """ 167 | |> expectInvalid 168 | , test "should parse case expression when " <| 169 | \() -> 170 | """case msg of 171 | Increment -> 172 | 1 173 | 174 | Decrement -> 175 | 2""" 176 | |> expectAst 177 | (Node { start = { row = 1, column = 1 }, end = { row = 6, column = 6 } } 178 | (CaseExpression 179 | { expression = Node { start = { row = 1, column = 6 }, end = { row = 1, column = 9 } } (FunctionOrValue [] "msg") 180 | , cases = 181 | [ ( Node { start = { row = 2, column = 3 }, end = { row = 2, column = 12 } } (NamedPattern { moduleName = [], name = "Increment" } []) 182 | , Node { start = { row = 3, column = 5 }, end = { row = 3, column = 6 } } (Integer 1) 183 | ) 184 | , ( Node { start = { row = 5, column = 3 }, end = { row = 5, column = 12 } } (NamedPattern { moduleName = [], name = "Decrement" } []) 185 | , Node { start = { row = 6, column = 5 }, end = { row = 6, column = 6 } } (Integer 2) 186 | ) 187 | ] 188 | } 189 | ) 190 | ) 191 | ] 192 | 193 | 194 | expectAst : Node Expression -> String -> Expect.Expectation 195 | expectAst = 196 | ParserWithCommentsUtil.expectAst expression 197 | 198 | 199 | expectInvalid : String -> Expect.Expectation 200 | expectInvalid = 201 | ParserWithCommentsUtil.expectInvalid expression 202 | --------------------------------------------------------------------------------