diff --git a/src/Elm/AST/Frontend.elm b/src/Elm/AST/Frontend.elm index 289a8ebf..c6b6cbfd 100644 --- a/src/Elm/AST/Frontend.elm +++ b/src/Elm/AST/Frontend.elm @@ -16,6 +16,7 @@ still there. import Dict exposing (Dict) import Elm.AST.Frontend.Unwrapped as Unwrapped import Elm.Data.Binding as Binding exposing (Binding) +import Elm.Data.Comment exposing (Comment) import Elm.Data.Located as Located exposing (Located) import Elm.Data.Module exposing (Module) import Elm.Data.ModuleName exposing (ModuleName) @@ -51,26 +52,121 @@ type alias LocatedExpr = {-| -} type Expr - = Int Int + = Unit + | Bool Bool + | Int Int | Float Float | Char Char | String String - | Bool Bool | Var { qualifiedness : PossiblyQualified, name : VarName } | Argument VarName - | Plus LocatedExpr LocatedExpr - | Cons LocatedExpr LocatedExpr - | ListConcat LocatedExpr LocatedExpr - | Lambda { arguments : List VarName, body : LocatedExpr } - | Call { fn : LocatedExpr, argument : LocatedExpr } - | If { test : LocatedExpr, then_ : LocatedExpr, else_ : LocatedExpr } - | Let { bindings : List (Binding LocatedExpr), body : LocatedExpr } - | List (List LocatedExpr) - | Unit - | Tuple LocatedExpr LocatedExpr - | Tuple3 LocatedExpr LocatedExpr LocatedExpr - | Record (List (Binding LocatedExpr)) - | Case LocatedExpr (List { pattern : LocatedPattern, body : LocatedExpr }) + | Parenthesized + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + | Tuple + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + | Tuple3 + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + | Record + (List + { commentsBefore : List Comment + , binding : Binding.Commented LocatedExpr + , commentsAfter : List Comment + } + ) + | List + (List + { commentsBefore : List Comment + , expr : LocatedExpr + , commentsAfter : List Comment + } + ) + | Call + { fn : LocatedExpr + , comments : List Comment + , argument : LocatedExpr + } + | Lambda + { arguments : + List + { commentsBefore : List Comment + , argument : VarName + } + , commentsAfterArguments : List Comment + , commentsBeforeBody : List Comment + , body : LocatedExpr + } + | Plus + { left : LocatedExpr + , commentsAfterLeft : List Comment + , commentsBeforeRight : List Comment + , right : LocatedExpr + } + | Cons + { left : LocatedExpr + , commentsAfterLeft : List Comment + , commentsBeforeRight : List Comment + , right : LocatedExpr + } + | ListConcat + { left : LocatedExpr + , commentsAfterLeft : List Comment + , commentsBeforeRight : List Comment + , right : LocatedExpr + } + | Let + { bindings : + List + { commentsBefore : List Comment + , binding : Binding.Commented LocatedExpr + } + , commentsAfterBindings : List Comment + , commentsBeforeBody : List Comment + , body : LocatedExpr + } + | If + { commentsBeforeTest : List Comment + , test : LocatedExpr + , commentsAfterTest : List Comment + , commentsBeforeThen : List Comment + , then_ : LocatedExpr + , commentsAfterThen : List Comment + , commentsBeforeElse : List Comment + , else_ : LocatedExpr + } + | Case + { commentsBeforeTest : List Comment + , test : LocatedExpr + , commentsAfterTest : List Comment + , branches : + List + { commentsBeforePattern : List Comment + , pattern : LocatedPattern + , commentsAfterPattern : List Comment + , commentsBeforeBody : List Comment + , body : LocatedExpr + } + } type alias LocatedPattern = @@ -79,19 +175,66 @@ type alias LocatedPattern = type Pattern = PAnything - | PVar VarName - | PRecord (List VarName) - | PAlias LocatedPattern VarName | PUnit - | PTuple LocatedPattern LocatedPattern - | PTuple3 LocatedPattern LocatedPattern LocatedPattern - | PList (List LocatedPattern) - | PCons LocatedPattern LocatedPattern | PBool Bool - | PChar Char - | PString String | PInt Int | PFloat Float + | PChar Char + | PString String + | PVar VarName + | PParenthesized + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + | PTuple + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + | PTuple3 + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + | PRecord + (List + { commentsBefore : List Comment + , varName : VarName + , commentsAfter : List Comment + } + ) + | PList + (List + { commentsBefore : List Comment + , pattern : LocatedPattern + , commentsAfter : List Comment + } + ) + | PAlias + { pattern : LocatedPattern + , commentsAfterPattern : List Comment + , commentsBeforeAlias : List Comment + , alias : VarName + } + | PCons + { left : LocatedPattern + , commentsAfterLeft : List Comment + , commentsBeforeRight : List Comment + , right : LocatedPattern + } {-| A helper for the [Transform](/packages/Janiczek/transform/latest/) library. @@ -104,6 +247,12 @@ recurse f expr = Located.map f in case expr of + Unit -> + expr + + Bool _ -> + expr + Int _ -> expr @@ -116,74 +265,103 @@ recurse f expr = String _ -> expr - Bool _ -> - expr - Var _ -> expr Argument _ -> expr - Plus e1 e2 -> - Plus - (f_ e1) - (f_ e2) + Parenthesized item -> + Parenthesized + { item | expr = f_ item.expr } - Cons e1 e2 -> - Cons - (f_ e1) - (f_ e2) + Tuple item1 item2 -> + Tuple + { item1 | expr = f_ item1.expr } + { item2 | expr = f_ item2.expr } - ListConcat e1 e2 -> - ListConcat (f_ e1) (f_ e2) + Tuple3 item1 item2 item3 -> + Tuple3 + { item1 | expr = f_ item1.expr } + { item2 | expr = f_ item2.expr } + { item3 | expr = f_ item3.expr } - Lambda ({ body } as lambda_) -> - Lambda { lambda_ | body = f_ body } + Record bindings -> + Record <| + List.map + (\({ binding } as binding_) -> + { binding_ | binding = Binding.mapCommented f_ binding } + ) + bindings - Call { fn, argument } -> + List items -> + List <| + List.map + (\item_ -> { item_ | expr = f_ item_.expr }) + items + + Call ({ fn, argument } as call) -> Call - { fn = f_ fn - , argument = f_ argument + { call + | fn = f_ fn + , argument = f_ argument } - If { test, then_, else_ } -> - If - { test = f_ test - , then_ = f_ then_ - , else_ = f_ else_ - } + Lambda ({ body } as lambda_) -> + Lambda { lambda_ | body = f_ body } - Let { bindings, body } -> - Let - { bindings = List.map (Binding.map f_) bindings - , body = f_ body + Plus ({ left, right } as plus) -> + Plus + { plus + | left = f_ left + , right = f_ right } - List items -> - List (List.map f_ items) - - Unit -> - expr + Cons ({ left, right } as cons) -> + Cons + { cons + | left = f_ left + , right = f_ right + } - Tuple e1 e2 -> - Tuple (f_ e1) (f_ e2) + ListConcat ({ left, right } as listConcat) -> + ListConcat + { listConcat + | left = f_ left + , right = f_ right + } - Tuple3 e1 e2 e3 -> - Tuple3 (f_ e1) (f_ e2) (f_ e3) + Let ({ bindings, body } as let_) -> + Let + { let_ + | bindings = + List.map + (\({ binding } as binding_) -> + { binding_ | binding = Binding.mapCommented f_ binding } + ) + bindings + , body = f_ body + } - Record bindings -> - Record <| List.map (Binding.map f_) bindings + If ({ test, then_, else_ } as if_) -> + If + { if_ + | test = f_ test + , then_ = f_ then_ + , else_ = f_ else_ + } - Case test branches -> - Case (f_ test) <| - List.map - (\{ pattern, body } -> - { pattern = pattern - , body = f_ body - } - ) - branches + Case ({ test, branches } as case_) -> + Case + { case_ + | test = f_ test + , branches = + List.map + (\({ body } as branch) -> + { branch | body = f_ body } + ) + branches + } {-| [Transform](/packages/Janiczek/transform/latest/Transform#transformAll) @@ -209,6 +387,12 @@ transform pass expr = unwrap : LocatedExpr -> Unwrapped.Expr unwrap expr = case Located.unwrap expr of + Unit -> + Unwrapped.Unit + + Bool bool -> + Unwrapped.Bool bool + Int int -> Unwrapped.Int int @@ -221,40 +405,70 @@ unwrap expr = String string -> Unwrapped.String string - Bool bool -> - Unwrapped.Bool bool - Var var_ -> Unwrapped.Var var_ Argument name -> Unwrapped.Argument name - Plus e1 e2 -> - Unwrapped.Plus - (unwrap e1) - (unwrap e2) + Parenthesized item -> + unwrap item.expr + + Tuple item1 item2 -> + Unwrapped.Tuple + (unwrap item1.expr) + (unwrap item2.expr) - Cons e1 e2 -> - Unwrapped.Cons - (unwrap e1) - (unwrap e2) + Tuple3 item1 item2 item3 -> + Unwrapped.Tuple3 + (unwrap item1.expr) + (unwrap item2.expr) + (unwrap item3.expr) - ListConcat e1 e2 -> - Unwrapped.ListConcat - (unwrap e1) - (unwrap e2) + Record bindings -> + Unwrapped.Record <| + List.map + (.binding + >> Binding.fromCommented + >> Binding.map unwrap + ) + bindings + + List list -> + Unwrapped.List <| + List.map (.expr >> unwrap) list + + Call { fn, comments, argument } -> + Unwrapped.Call + { fn = unwrap fn + , argument = unwrap argument + } Lambda { arguments, body } -> Unwrapped.Lambda - { arguments = arguments + { arguments = List.map .argument arguments , body = unwrap body } - Call { fn, argument } -> - Unwrapped.Call - { fn = unwrap fn - , argument = unwrap argument + Plus { left, right } -> + Unwrapped.Plus (unwrap left) (unwrap right) + + Cons { left, right } -> + Unwrapped.Cons (unwrap left) (unwrap right) + + ListConcat { left, right } -> + Unwrapped.ListConcat (unwrap left) (unwrap right) + + Let { bindings, body } -> + Unwrapped.Let + { bindings = + List.map + (.binding + >> Binding.fromCommented + >> Binding.map unwrap + ) + bindings + , body = unwrap body } If { test, then_, else_ } -> @@ -264,46 +478,21 @@ unwrap expr = , else_ = unwrap else_ } - Let { bindings, body } -> - Unwrapped.Let - { bindings = List.map (Binding.map unwrap) bindings - , body = unwrap body + Case { test, branches } -> + Unwrapped.Case + { test = unwrap test + , branches = + List.map + (\{ pattern, body } -> + { pattern = unwrapPattern pattern + , body = unwrap body + } + ) + branches } - List list -> - Unwrapped.List - (List.map unwrap list) - - Unit -> - Unwrapped.Unit - - Tuple e1 e2 -> - Unwrapped.Tuple - (unwrap e1) - (unwrap e2) - - Tuple3 e1 e2 e3 -> - Unwrapped.Tuple3 - (unwrap e1) - (unwrap e2) - (unwrap e3) - - Record bindings -> - Unwrapped.Record <| - List.map (Binding.map unwrap) bindings - - Case e branches -> - Unwrapped.Case (unwrap e) <| - List.map - (\branch -> - { pattern = unwrapPattern branch.pattern - , body = unwrap branch.body - } - ) - branches - -{-| Discard the [location metadata](Elm.Data.Located#Located). +{-| Discard the pattern [location metadata](Elm.Data.Located#Located). -} unwrapPattern : LocatedPattern -> Unwrapped.Pattern unwrapPattern expr = @@ -311,44 +500,54 @@ unwrapPattern expr = PAnything -> Unwrapped.PAnything - PVar varName -> - Unwrapped.PVar varName - - PRecord varNames -> - Unwrapped.PRecord varNames - - PAlias p varName -> - Unwrapped.PAlias (unwrapPattern p) varName - PUnit -> Unwrapped.PUnit - PTuple p1 p2 -> - Unwrapped.PTuple (unwrapPattern p1) (unwrapPattern p2) - - PTuple3 p1 p2 p3 -> - Unwrapped.PTuple3 - (unwrapPattern p1) - (unwrapPattern p2) - (unwrapPattern p3) - - PList ps -> - Unwrapped.PList (List.map unwrapPattern ps) - - PCons p1 p2 -> - Unwrapped.PCons (unwrapPattern p1) (unwrapPattern p2) - PBool bool -> Unwrapped.PBool bool + PInt int -> + Unwrapped.PInt int + + PFloat float -> + Unwrapped.PFloat float + PChar char -> Unwrapped.PChar char PString string -> Unwrapped.PString string - PInt int -> - Unwrapped.PInt int + PVar varName -> + Unwrapped.PVar varName - PFloat float -> - Unwrapped.PFloat float + PParenthesized item -> + unwrapPattern item.pattern + + PTuple item1 item2 -> + Unwrapped.PTuple + (unwrapPattern item1.pattern) + (unwrapPattern item2.pattern) + + PTuple3 item1 item2 item3 -> + Unwrapped.PTuple3 + (unwrapPattern item1.pattern) + (unwrapPattern item2.pattern) + (unwrapPattern item3.pattern) + + PRecord varNames -> + Unwrapped.PRecord (List.map .varName varNames) + + PList items -> + Unwrapped.PList <| + List.map + (\{ pattern } -> unwrapPattern pattern) + items + + PAlias { pattern, alias } -> + Unwrapped.PAlias (unwrapPattern pattern) alias + + PCons { left, right } -> + Unwrapped.PCons + (unwrapPattern left) + (unwrapPattern right) diff --git a/src/Elm/AST/Frontend/Unwrapped.elm b/src/Elm/AST/Frontend/Unwrapped.elm index fd0146e0..daef7c2b 100644 --- a/src/Elm/AST/Frontend/Unwrapped.elm +++ b/src/Elm/AST/Frontend/Unwrapped.elm @@ -3,9 +3,9 @@ module Elm.AST.Frontend.Unwrapped exposing , Pattern(..) ) -{-| Version of [Frontend AST](Elm.AST.Frontend) without the location info. +{-| Version of [Frontend AST](Elm.AST.Frontend) without the location info or comments. -Handy for parser tests, or when you don't need the location info. +Handy for parser tests, or when you don't need the location info and the comments. Convert to it using the [`Elm.AST.Frontend.unwrap`](Elm.AST.Frontend#unwrap). @@ -13,47 +13,68 @@ Convert to it using the [`Elm.AST.Frontend.unwrap`](Elm.AST.Frontend#unwrap). -} -import Elm.Data.Binding exposing (Binding) +import Elm.Data.Binding as Binding exposing (Binding) +import Elm.Data.Comment exposing (Comment) import Elm.Data.Qualifiedness exposing (PossiblyQualified) import Elm.Data.VarName exposing (VarName) {-| -} type Expr - = Int Int + = Unit + | Bool Bool + | Int Int | Float Float | Char Char | String String - | Bool Bool | Var { qualifiedness : PossiblyQualified, name : VarName } | Argument VarName - | Plus Expr Expr - | Cons Expr Expr - | ListConcat Expr Expr - | Lambda { arguments : List VarName, body : Expr } - | Call { fn : Expr, argument : Expr } - | If { test : Expr, then_ : Expr, else_ : Expr } - | Let { bindings : List (Binding Expr), body : Expr } - | List (List Expr) - | Unit | Tuple Expr Expr | Tuple3 Expr Expr Expr | Record (List (Binding Expr)) - | Case Expr (List { pattern : Pattern, body : Expr }) + | List (List Expr) + | Call + { fn : Expr + , argument : Expr + } + | Lambda + { arguments : List VarName + , body : Expr + } + | Plus Expr Expr + | Cons Expr Expr + | ListConcat Expr Expr + | Let + { bindings : List (Binding Expr) + , body : Expr + } + | If + { test : Expr + , then_ : Expr + , else_ : Expr + } + | Case + { test : Expr + , branches : + List + { pattern : Pattern + , body : Expr + } + } type Pattern = PAnything - | PVar VarName - | PRecord (List VarName) - | PAlias Pattern VarName | PUnit + | PBool Bool + | PInt Int + | PFloat Float + | PChar Char + | PString String + | PVar VarName | PTuple Pattern Pattern | PTuple3 Pattern Pattern Pattern + | PRecord (List VarName) | PList (List Pattern) + | PAlias Pattern VarName | PCons Pattern Pattern - | PBool Bool - | PChar Char - | PString String - | PInt Int - | PFloat Float diff --git a/src/Elm/Compiler.elm b/src/Elm/Compiler.elm index e4140401..080bf616 100644 --- a/src/Elm/Compiler.elm +++ b/src/Elm/Compiler.elm @@ -169,9 +169,9 @@ type alias Parser a = -} parse : Parser a -> FileContents -> Result Error a parse parser sourceCode = - Result.mapError - (\errorList -> ParseError (ParseProblem ( errorList, sourceCode ))) - (P.run parser sourceCode) + P.run parser sourceCode + |> Result.mapError + (\errorList -> ParseError (ParseProblem ( errorList, sourceCode ))) {-| Parse a single expression like @@ -198,6 +198,7 @@ use [`Elm.AST.Frontend.unwrap`](Elm.AST.Frontend#unwrap) to get something like parseExpr : FileContents -> Result Error Frontend.LocatedExpr parseExpr sourceCode = parse Stage.Parse.Parser.expr sourceCode + |> Result.map Tuple.first {-| Parse a module (one `*.elm` file). Get a [`Module`](Elm.Data.Module#Module) datastructure back, holding @@ -288,6 +289,7 @@ into parseImport : FileContents -> Result Error Import parseImport sourceCode = parse Stage.Parse.Parser.import_ sourceCode + |> Result.map (\import_ -> Tuple.first (import_ [])) {-| Parse a single declaration, like @@ -308,7 +310,7 @@ parseDeclaration : -> Result Error (Declaration Frontend.LocatedExpr TypeAnnotation PossiblyQualified) parseDeclaration { moduleName, declaration } = parse Stage.Parse.Parser.declaration declaration - |> Result.map (\toDeclaration -> toDeclaration moduleName) + |> Result.map (\toDeclaration -> Tuple.first (toDeclaration []) moduleName) diff --git a/src/Elm/Compiler/Error.elm b/src/Elm/Compiler/Error.elm index 9f51b93d..7e65793a 100644 --- a/src/Elm/Compiler/Error.elm +++ b/src/Elm/Compiler/Error.elm @@ -74,6 +74,7 @@ type ParseContext | InTuple | InTuple3 | InRecord + | InRecordBinding | InFile FilePath | InCase | InPattern @@ -96,13 +97,15 @@ type ParseContext | InTypeBinding | InPatternVar | InPatternRecord + | InImport {-| The specific problem the parser encountered. Together with [`ParseContext`](#ParseContext) and the [location info](Elm.Data.Located) this should give you enough info about what's wrong. -} type ParseProblem - = ExpectingPortKeyword -- `>port< module ...` + = TooMuchIndentation String + | ExpectingPortKeyword -- `>port< module ...` | ExpectingEffectKeyword -- `>effect< module ...` | ExpectingModuleKeyword -- `>module< Foo.Bar exposing (..)` | ExpectingModuleName -- `module >Foo.Bar< exposing (..)` @@ -151,6 +154,8 @@ type ParseProblem | ExpectingTrue | ExpectingFalse | ExpectingLet + | ExpectingLetIndentation + | ExpectingLetBindingIndentation | ExpectingIn | ExpectingUnit | ExpectingColon @@ -168,9 +173,14 @@ type ParseProblem | ExpectingIndentation | ExpectingPatternAnything -- `>_< ->` | ExpectingMaxThreeTuple + | ExpectingExpression | ExpectingTypeName | ExpectingNewlineAfterTypeAnnotation + | ExpectingTypeAnnotationDefinition | ExpectingNonSpaceAfterTypeAnnotationNewlines + | ExpectingSingleLineCommentStart -- -- + | ExpectingMultiLineCommentStart -- {- + | ExpectingMultiLineCommentEnd -- -} | InvalidTab | InvalidNumber | TriedToParseCharacterStoppingDelimiter @@ -404,6 +414,18 @@ fullVarName { qualifiedness, name } = parseProblemToString : ParseProblem -> String parseProblemToString problem = case problem of + TooMuchIndentation str -> + {- TODO + Too Much Indentation + Line 1, Column 2 + This `module` should not have any spaces before it: + + 1| module Main + ^ + Delete the spaces before `module` until there are none left! + -} + "TooMuchIndentation " ++ str + ExpectingPortKeyword -> "ExpectingPortKeyword" @@ -551,6 +573,51 @@ parseProblemToString problem = ExpectingLet -> "ExpectingLet" + ExpectingLetIndentation -> + {- TODO + Unfinished Let + Line 20, Column 6 + I was partway through parsing a `let` expression, but I got stuck here: + + 20| let + ^ + I was expecting a value to be defined here. + + Note: Here is an example with a valid `let` expression for reference: + + viewPerson person = + let + fullName = + person.firstName ++ " " ++ person.lastName + in + div [] [ text fullName ] + + Here we defined a `viewPerson` function that turns a person into some HTML. We + use a `let` expression to define the `fullName` we want to show. Notice the + indentation! The `fullName` is indented more than the `let` keyword, and the + actual value of `fullName` is indented a bit more than that. That is important! + -} + "ExpectingLetIndentation" + + ExpectingLetBindingIndentation -> + {- TODO + ERRORS + Unexpected Equals + Line 22, Column 7 + I was not expecting to see this equals sign: + + 22| y = 2 in { count = 0 } + ^ + Maybe you want == instead? To check if two values are equal? + + Note: I may be getting confused by your indentation. I think I am still parsing + the `x` definition. Is this supposed to be part of a definition after that? If + so, the problem may be a bit before the equals sign. I need all definitions to + be indented exactly the same amount, so the problem may be that this new + definition has too many spaces in front of it. + -} + "ExpectingLetBindingIndentation" + ExpectingIn -> "ExpectingIn" @@ -600,17 +667,55 @@ parseProblemToString problem = "ExpectingPatternAnything" ExpectingMaxThreeTuple -> + {- TODO + I only accept tuples with two or three items. This has too many: + + 39| x = (1, 2, 3, 4) + ^^^^^^^^^^^^ + I recommend switching to records. Each item will be named, and you can use the + `point.x` syntax to access them. + + Note: Read for more comprehensive advice on + working with large chunks of data in Elm. + -} "ExpectingMaxThreeTuple" + ExpectingExpression -> + {- TODO + I am partway through parsing some parentheses, but I got stuck here: + + 39| x = ( ) + ^ + I was expecting to see an expression like 42 or "hello". Once there is something + there, I can probably give a more specific hint! + + Note: This can also happen if run into reserved words like `let` or `as` + unexpectedly. Or if I run into operators in unexpected spots. Point is, there + are a couple ways I can get confused and give sort of weird advice! + -} + "ExpectingExpression" + ExpectingTypeName -> "ExpectingTypeName" ExpectingNewlineAfterTypeAnnotation -> "ExpectingNewlineAfterTypeAnnotation" + ExpectingTypeAnnotationDefinition -> + "ExpectingTypeAnnotationDefinition" + ExpectingNonSpaceAfterTypeAnnotationNewlines -> "ExpectingNonSpaceAfterTypeAnnotationNewlines" + ExpectingSingleLineCommentStart -> + "ExpectingSingleLineCommentStart" + + ExpectingMultiLineCommentStart -> + "ExpectingMultiLineCommentStart" + + ExpectingMultiLineCommentEnd -> + "ExpectingMultiLineCommentEnd" + InvalidTab -> "InvalidTab" diff --git a/src/Elm/Data/Binding.elm b/src/Elm/Data/Binding.elm index caee04dc..80d2c8a4 100644 --- a/src/Elm/Data/Binding.elm +++ b/src/Elm/Data/Binding.elm @@ -1,4 +1,7 @@ -module Elm.Data.Binding exposing (Binding, combine, map) +module Elm.Data.Binding exposing + ( Binding, combine, map + , Commented, fromCommented, mapCommented + ) {-| Binding in the `let...in` expression. @@ -14,9 +17,12 @@ module Elm.Data.Binding exposing (Binding, combine, map) contains two bindings: `myNumber` and `answer`. @docs Binding, combine, map +@docs Commented, fromCommented, mapCommented -} +import Elm.Data.Comment exposing (Comment) + {-| -} type alias Binding expr = @@ -51,3 +57,37 @@ combine { name, body } = } ) body + + +{-| Binding with comments: + + x {- commentsAfterName -} = {- commentsBeforeBody -} 2 + +-} +type alias Commented expr = + -- TODO type annotation for the let...in binding + { name : String + , commentsAfterName : List Comment + , commentsBeforeBody : List Comment + , body : expr + } + + +{-| Create a [Binding](#Binding) from a [Commented](#Commented). +-} +fromCommented : Commented e -> Binding e +fromCommented { name, body } = + { name = name + , body = body + } + + +{-| Apply a function to the expression inside the commented binding. +-} +mapCommented : (e1 -> e2) -> Commented e1 -> Commented e2 +mapCommented fn { name, commentsAfterName, commentsBeforeBody, body } = + { name = name + , commentsAfterName = commentsAfterName + , commentsBeforeBody = commentsBeforeBody + , body = fn body + } diff --git a/src/Elm/Data/Comment.elm b/src/Elm/Data/Comment.elm new file mode 100644 index 00000000..9d7410d5 --- /dev/null +++ b/src/Elm/Data/Comment.elm @@ -0,0 +1,19 @@ +module Elm.Data.Comment exposing (Comment, CommentType(..)) + +import Elm.Data.Located exposing (Located) + + +{-| Comment information + +@docs Comment, CommentType + +-} +type alias Comment = + { content : Located String + , type_ : CommentType + } + + +type CommentType + = SingleLine + | MultiLine diff --git a/src/Elm/Data/Declaration.elm b/src/Elm/Data/Declaration.elm index bf845250..0b2f61f6 100644 --- a/src/Elm/Data/Declaration.elm +++ b/src/Elm/Data/Declaration.elm @@ -14,6 +14,7 @@ module Elm.Data.Declaration exposing -} +import Elm.Data.Comment exposing (Comment) import Elm.Data.ModuleName exposing (ModuleName) import Elm.Data.Type.Concrete as ConcreteType exposing (ConcreteType) import Elm.Data.VarName exposing (VarName) @@ -27,6 +28,7 @@ import Stage.InferTypes.SubstitutionMap as SubstitutionMap exposing ({- TODO may type alias Declaration expr annotation qualifiedness = { module_ : ModuleName , name : VarName + , commentsBefore : List Comment , body : DeclarationBody expr annotation qualifiedness } @@ -45,21 +47,21 @@ type alias Declaration expr annotation qualifiedness = -} type DeclarationBody expr annotation qualifiedness = Value - { expression : expr - - -- What information from the annotation is yet to be used in the current stage? - ----------------------------------- - -- Nothing: no annotation was given - -- Just Never: annotation was given but we successfully used all of it - ----------------------------------- - -- The `annotation` types used are: - -- FRONTEND: TypeAnnotation (for which we need to check that the name in the - -- annotation is the same as the name in the declaration) - -- CANONICAL: Type (for which we need to check that this advertised type is - -- unifiable with the type of the declaration) - -- TYPED: Never (where we've used up all the info from the annotation and - -- don't need it anymore) - , typeAnnotation : Maybe annotation + { -- What information from the annotation is yet to be used in the current stage? + ----------------------------------- + -- Nothing: no annotation was given + -- Just Never: annotation was given but we successfully used all of it + ----------------------------------- + -- The `annotation` types used are: + -- FRONTEND: TypeAnnotation (for which we need to check that the name in the + -- annotation is the same as the name in the declaration) + -- CANONICAL: Type (for which we need to check that this advertised type is + -- unifiable with the type of the declaration) + -- TYPED: Never (where we've used up all the info from the annotation and + -- don't need it anymore) + typeAnnotation : Maybe annotation + , commentsAfterTypeAnnotation : List Comment + , expression : expr } | TypeAlias (TypeAliasDeclaration qualifiedness) | CustomType @@ -110,6 +112,7 @@ map : map fnExpr fnAnnotation fnQualifiedness declaration = { module_ = declaration.module_ , name = declaration.name + , commentsBefore = declaration.commentsBefore , body = mapBody fnExpr fnAnnotation fnQualifiedness declaration.body } @@ -123,6 +126,7 @@ setAnnotation : setAnnotation annotation declaration = { module_ = declaration.module_ , name = declaration.name + , commentsBefore = declaration.commentsBefore , body = declaration.body |> mapBody @@ -142,10 +146,11 @@ mapBody : -> DeclarationBody exprB annotationB qualifiednessB mapBody fnExpr fnAnnotation fnQualifiedness body = case body of - Value { expression, typeAnnotation } -> + Value { expression, commentsAfterTypeAnnotation, typeAnnotation } -> Value - { expression = fnExpr expression - , typeAnnotation = fnAnnotation typeAnnotation + { typeAnnotation = fnAnnotation typeAnnotation + , commentsAfterTypeAnnotation = commentsAfterTypeAnnotation + , expression = fnExpr expression } TypeAlias r -> @@ -176,8 +181,9 @@ combineValue body = |> Result.map (\expr -> Value - { expression = expr - , typeAnnotation = r.typeAnnotation + { typeAnnotation = r.typeAnnotation + , commentsAfterTypeAnnotation = r.commentsAfterTypeAnnotation + , expression = expr } ) @@ -230,8 +236,9 @@ combineSubstitutionMap body = case r.expression of ( expr, map_ ) -> ( Value - { expression = expr - , typeAnnotation = r.typeAnnotation + { typeAnnotation = r.typeAnnotation + , commentsAfterTypeAnnotation = r.commentsAfterTypeAnnotation + , expression = expr } , map_ ) diff --git a/src/Elm/Data/Exposing.elm b/src/Elm/Data/Exposing.elm index 5e0b1ad7..d6cc8b27 100644 --- a/src/Elm/Data/Exposing.elm +++ b/src/Elm/Data/Exposing.elm @@ -18,13 +18,20 @@ module Elm.Data.Exposing exposing (Exposing(..), ExposedItem(..), name) -} +import Elm.Data.Comment exposing (Comment) import Elm.Data.VarName exposing (VarName) {-| -} type Exposing = ExposingAll -- exposing (..) - | ExposingSome (List ExposedItem) -- exposing (foo, Foo, Bar(..)) + | ExposingSome + (List + { commentsBefore : List Comment -- exposing (>{-before-}< foo) + , item : ExposedItem -- exposing (>foo<, Foo, Bar(..)) + , commentsAfter : List Comment -- exposing (foo >{-after-}<) + } + ) {-| -} diff --git a/src/Elm/Data/Import.elm b/src/Elm/Data/Import.elm index f9c012f0..241953b3 100644 --- a/src/Elm/Data/Import.elm +++ b/src/Elm/Data/Import.elm @@ -21,13 +21,50 @@ module Elm.Data.Import exposing (Import) -} +import Elm.Data.Comment exposing (Comment) import Elm.Data.Exposing exposing (Exposing) import Elm.Data.ModuleName exposing (ModuleName) {-| -} type alias Import = - { moduleName : ModuleName - , as_ : Maybe ModuleName - , exposing_ : Maybe Exposing + --{ moduleName : ModuleName + --, as_ : Maybe ModuleName + --, exposing_ : Maybe Exposing + --} + Commented + + +{-| Import with comments: + + {- commentsBefore -} + import {- commentsBeforeModuleName -} Html.Attributes {- commentsAfterModuleName -} as {-commentsBeforeAs-} HtmlA {- commentsAfterAs -} exposing {- commentsBeforeExposing -} (..) + +-} +type alias Commented = + { commentsBefore : List Comment + , commentsBeforeModuleName : List Comment + , moduleName : ModuleName + , commentsAfterModuleName : List Comment + , as_ : + Maybe + { commentsBeforeAs : List Comment + , as_ : ModuleName + , commentsAfterAs : List Comment + } + , exposing_ : + Maybe + { commentsBeforeExposing : List Comment + , exposing_ : Exposing + } } + + + +{- Create an [Import](#Import) from a [Commented](#Commented). -} +--fromCommented : Commented -> Import +--fromCommented { moduleName, as_, exposing_ } = +-- { moduleName = moduleName +-- , as_ = Maybe.map .as_ as_ +-- , exposing_ = Maybe.map .exposing_ exposing_ +-- } diff --git a/src/Elm/Data/Module.elm b/src/Elm/Data/Module.elm index 275f4897..7bb4c945 100644 --- a/src/Elm/Data/Module.elm +++ b/src/Elm/Data/Module.elm @@ -16,10 +16,12 @@ Name, imports, contents, etc. import Dict exposing (Dict) import Dict.Extra as Dict import Elm.Compiler.Error exposing (DesugarError(..)) +import Elm.Data.Comment exposing (Comment) import Elm.Data.Declaration as Declaration exposing (Declaration) import Elm.Data.Exposing exposing (ExposedItem(..), Exposing(..)) import Elm.Data.FilePath exposing (FilePath) import Elm.Data.Import exposing (Import) +import Elm.Data.Located exposing (Located) import Elm.Data.ModuleName exposing (ModuleName) import Elm.Data.Qualifiedness exposing (PossiblyQualified(..), Qualified(..)) import Elm.Data.VarName exposing (VarName) @@ -29,7 +31,6 @@ import Result.Extra {-| -} type alias Module expr annotation qualifiedness = - -- TODO comments? doc comments? { -- TODO somewhere check that dependencies' exposing lists contain only what's in that module's exposing list imports : Dict ModuleName Import , name : ModuleName @@ -37,6 +38,8 @@ type alias Module expr annotation qualifiedness = , declarations : Dict VarName (Declaration expr annotation qualifiedness) , type_ : ModuleType , exposing_ : Exposing + , startComments : List Comment + , endComments : List Comment } @@ -81,8 +84,8 @@ exposes varName module_ = ExposingSome items -> List.any - (\exposedItem -> - case exposedItem of + (\{ item } -> + case item of ExposedValue value -> value == varName @@ -117,7 +120,7 @@ Given `import Foo as F`: unalias : Module expr annotation qualifiedness -> ModuleName -> Maybe ModuleName unalias thisModule moduleName = thisModule.imports - |> Dict.find (\_ dep -> dep.as_ == Just moduleName) + |> Dict.find (\_ dep -> Maybe.map .as_ dep.as_ == Just moduleName) |> Maybe.map (Tuple.second >> .moduleName) @@ -144,6 +147,8 @@ map fnExpr fnAnnotation fnQualifiedness module_ = module_.declarations , type_ = module_.type_ , exposing_ = module_.exposing_ + , startComments = module_.startComments + , endComments = module_.endComments } diff --git a/src/Elm/Data/TypeAnnotation.elm b/src/Elm/Data/TypeAnnotation.elm index 25ec4d97..c864e5aa 100644 --- a/src/Elm/Data/TypeAnnotation.elm +++ b/src/Elm/Data/TypeAnnotation.elm @@ -1,5 +1,6 @@ module Elm.Data.TypeAnnotation exposing (TypeAnnotation) +import Elm.Data.Comment exposing (Comment) import Elm.Data.Qualifiedness exposing (PossiblyQualified) import Elm.Data.Type.Concrete exposing (ConcreteType) import Elm.Data.VarName exposing (VarName) @@ -22,5 +23,7 @@ but also qualified: -} type alias TypeAnnotation = { varName : VarName + , commentsAfterVarName : List Comment + , commentsBeforeType : List Comment , type_ : ConcreteType PossiblyQualified } diff --git a/src/Stage/Desugar.elm b/src/Stage/Desugar.elm index 1da99dcc..203c0918 100644 --- a/src/Stage/Desugar.elm +++ b/src/Stage/Desugar.elm @@ -85,6 +85,12 @@ desugarExpr modules thisModule locatedExpr = ) in case Located.unwrap locatedExpr of + Frontend.Unit -> + return Canonical.Unit + + Frontend.Bool bool -> + return <| Canonical.Bool bool + Frontend.Int int -> return <| Canonical.Int int @@ -97,9 +103,6 @@ desugarExpr modules thisModule locatedExpr = Frontend.String string -> return <| Canonical.String string - Frontend.Bool bool -> - return <| Canonical.Bool bool - Frontend.Var var -> Module.findModuleOfVar modules thisModule var |> map @@ -113,17 +116,72 @@ desugarExpr modules thisModule locatedExpr = Frontend.Argument varName -> return <| Canonical.Argument varName - Frontend.Plus e1 e2 -> + Frontend.Parenthesized e1 -> + recurse e1.expr + + Frontend.Tuple e1 e2 -> + map2 Canonical.Tuple + (recurse e1.expr) + (recurse e2.expr) + + Frontend.Tuple3 e1 e2 e3 -> + map3 Canonical.Tuple3 + (recurse e1.expr) + (recurse e2.expr) + (recurse e3.expr) + + Frontend.Record bindings -> + let + bindings_ = + List.map (.binding >> Binding.fromCommented) bindings + in + case maybeDuplicateBindingsError thisModule.name bindings_ of + Just error -> + Err error + + Nothing -> + bindings_ + |> List.map (Binding.map recurse >> Binding.combine) + |> Result.combine + |> map + (\canonicalBindings -> + canonicalBindings + |> List.map (\canonicalBinding -> ( canonicalBinding.name, canonicalBinding )) + |> Dict.fromList + |> Canonical.Record + ) + + Frontend.List items -> + List.map (.expr >> recurse) items + |> Result.combine + |> map Canonical.List + + Frontend.Call { fn, argument } -> + map2 + (\fn_ argument_ -> + Canonical.Call + { fn = fn_ + , argument = argument_ + } + ) + (recurse fn) + (recurse argument) + + Frontend.Lambda { arguments, body } -> + recurse body + |> Result.map (curryLambda locatedExpr (List.map .argument arguments)) + + Frontend.Plus { left, right } -> map2 Canonical.Plus - (recurse e1) - (recurse e2) + (recurse left) + (recurse right) - Frontend.Cons e1 e2 -> + Frontend.Cons { left, right } -> map2 Canonical.Cons - (recurse e1) - (recurse e2) + (recurse left) + (recurse right) - Frontend.ListConcat e1 e2 -> + Frontend.ListConcat { left, commentsAfterLeft, commentsBeforeRight, right } -> let region = Located.getRegion locatedExpr @@ -136,27 +194,46 @@ desugarExpr modules thisModule locatedExpr = |> Located.located region firstCall = - Frontend.Call { fn = listConcatVar, argument = e1 } |> Located.located region + Frontend.Call + { fn = listConcatVar + , comments = commentsAfterLeft + , argument = left + } + |> Located.located region expr = - Frontend.Call { fn = firstCall, argument = e2 } |> Located.located region + Frontend.Call + { fn = firstCall + , comments = commentsBeforeRight + , argument = right + } + |> Located.located region in recurse expr - Frontend.Lambda { arguments, body } -> - recurse body - |> Result.map (curryLambda locatedExpr arguments) - - Frontend.Call { fn, argument } -> + Frontend.Let { bindings, body } -> map2 - (\fn_ argument_ -> - Canonical.Call - { fn = fn_ - , argument = argument_ + (\bindings_ body_ -> + Canonical.Let + { bindings = + bindings_ + |> List.map (\binding -> ( binding.name, binding )) + |> Dict.fromList + , body = body_ } ) - (recurse fn) - (recurse argument) + -- TODO a bit mouthful: + (Result.combine + (List.map + (.binding + >> Binding.fromCommented + >> Binding.map recurse + >> Binding.combine + ) + bindings + ) + ) + (recurse body) Frontend.If { test, then_, else_ } -> map3 @@ -171,58 +248,7 @@ desugarExpr modules thisModule locatedExpr = (recurse then_) (recurse else_) - Frontend.Let { bindings, body } -> - map2 - (\bindings_ body_ -> - Canonical.Let - { bindings = - bindings_ - |> List.map (\binding -> ( binding.name, binding )) - |> Dict.fromList - , body = body_ - } - ) - -- TODO a bit mouthful: - (Result.combine (List.map (Binding.map recurse >> Binding.combine) bindings)) - (recurse body) - - Frontend.List items -> - List.map recurse items - |> Result.combine - |> map Canonical.List - - Frontend.Tuple e1 e2 -> - map2 Canonical.Tuple - (recurse e1) - (recurse e2) - - Frontend.Tuple3 e1 e2 e3 -> - map3 Canonical.Tuple3 - (recurse e1) - (recurse e2) - (recurse e3) - - Frontend.Unit -> - return Canonical.Unit - - Frontend.Record bindings -> - case maybeDuplicateBindingsError thisModule.name bindings of - Just error -> - Err error - - Nothing -> - bindings - |> List.map (Binding.map recurse >> Binding.combine) - |> Result.combine - |> map - (\canonicalBindings -> - canonicalBindings - |> List.map (\canonicalBinding -> ( canonicalBinding.name, canonicalBinding )) - |> Dict.fromList - |> Canonical.Record - ) - - Frontend.Case test branches -> + Frontend.Case { test, branches } -> Result.map2 (\expr branches_ -> Located.replaceWith @@ -285,54 +311,57 @@ desugarPattern located = Frontend.PAnything -> return <| Canonical.PAnything - Frontend.PVar varName -> - return <| Canonical.PVar varName - - Frontend.PRecord varNames -> - return <| Canonical.PRecord varNames - - Frontend.PAlias pattern varName -> - recurse pattern - |> map (\p -> Canonical.PAlias p varName) - Frontend.PUnit -> return <| Canonical.PUnit - Frontend.PTuple pattern1 pattern2 -> - map2 Canonical.PTuple - (recurse pattern1) - (recurse pattern2) - - Frontend.PTuple3 pattern1 pattern2 pattern3 -> - map3 Canonical.PTuple3 - (recurse pattern1) - (recurse pattern2) - (recurse pattern3) - - Frontend.PList patterns -> - List.map recurse patterns - |> List.foldr (Result.map2 (::)) (Ok []) - |> map Canonical.PList - - Frontend.PCons pattern1 pattern2 -> - map2 Canonical.PCons - (recurse pattern1) - (recurse pattern2) - Frontend.PBool bool -> return <| Canonical.PBool bool + Frontend.PInt int -> + return <| Canonical.PInt int + + Frontend.PFloat float -> + return <| Canonical.PFloat float + Frontend.PChar char -> return <| Canonical.PChar char Frontend.PString string -> return <| Canonical.PString string - Frontend.PInt int -> - return <| Canonical.PInt int + Frontend.PVar varName -> + return <| Canonical.PVar varName - Frontend.PFloat float -> - return <| Canonical.PFloat float + Frontend.PParenthesized item -> + recurse item.pattern + + Frontend.PTuple item1 item2 -> + map2 Canonical.PTuple + (recurse item1.pattern) + (recurse item2.pattern) + + Frontend.PTuple3 item1 item2 item3 -> + map3 Canonical.PTuple3 + (recurse item1.pattern) + (recurse item2.pattern) + (recurse item3.pattern) + + Frontend.PRecord varNames -> + return <| Canonical.PRecord (List.map .varName varNames) + + Frontend.PList items -> + List.map (.pattern >> recurse) items + |> List.foldr (Result.map2 (::)) (Ok []) + |> map Canonical.PList + + Frontend.PAlias { pattern, alias } -> + recurse pattern + |> map (\p -> Canonical.PAlias p alias) + + Frontend.PCons { left, right } -> + map2 Canonical.PCons + (recurse left) + (recurse right) desugarQualifiedness : @@ -521,6 +550,7 @@ checkNamesAgree decl = Ok { module_ = decl.module_ , name = decl.name + , commentsBefore = decl.commentsBefore , body = TypeAlias r } @@ -528,6 +558,7 @@ checkNamesAgree decl = Ok { module_ = decl.module_ , name = decl.name + , commentsBefore = decl.commentsBefore , body = CustomType r } @@ -538,12 +569,14 @@ throwAwayTypeAnnotationName : throwAwayTypeAnnotationName decl = { module_ = decl.module_ , name = decl.name + , commentsBefore = decl.commentsBefore , body = case decl.body of Value r -> Value { expression = r.expression , typeAnnotation = Maybe.map .type_ r.typeAnnotation + , commentsAfterTypeAnnotation = r.commentsAfterTypeAnnotation } TypeAlias r -> diff --git a/src/Stage/Desugar/Boilerplate.elm b/src/Stage/Desugar/Boilerplate.elm index 989a2933..86db5697 100644 --- a/src/Stage/Desugar/Boilerplate.elm +++ b/src/Stage/Desugar/Boilerplate.elm @@ -103,6 +103,8 @@ moduleOfNewType old newDecls = , filePath = old.filePath , type_ = old.type_ , exposing_ = old.exposing_ + , startComments = old.startComments + , endComments = old.endComments -- all that code because of this: , declarations = newDecls @@ -134,6 +136,7 @@ declarationOfNewType : declarationOfNewType old newBody = { name = old.name , module_ = old.module_ + , commentsBefore = old.commentsBefore -- all that code because of this: , body = newBody diff --git a/src/Stage/Emit.elm b/src/Stage/Emit.elm index 40e25a34..1b8b564e 100644 --- a/src/Stage/Emit.elm +++ b/src/Stage/Emit.elm @@ -111,7 +111,7 @@ findPathForEachModule project graph = ExposingSome exposedItems -> exposedItems -- throwing away stuff - see comment for `exposedItemToDeclaration` - |> List.filterMap (exposedItemToDeclaration module_) + |> List.filterMap (.item >> exposedItemToDeclaration module_) {- We'll be throwing away Nothings created here - those happen if the compiler can't find a definition that is supposed to be exposed. diff --git a/src/Stage/InferTypes.elm b/src/Stage/InferTypes.elm index 687fd468..c296cf52 100644 --- a/src/Stage/InferTypes.elm +++ b/src/Stage/InferTypes.elm @@ -262,12 +262,14 @@ throwAwayType : Declaration a (ConcreteType Qualified) b -> Declaration a Never throwAwayType decl = { module_ = decl.module_ , name = decl.name + , commentsBefore = decl.commentsBefore , body = case decl.body of Declaration.Value r -> Declaration.Value { expression = r.expression , typeAnnotation = Nothing + , commentsAfterTypeAnnotation = r.commentsAfterTypeAnnotation } Declaration.TypeAlias r -> diff --git a/src/Stage/InferTypes/Boilerplate.elm b/src/Stage/InferTypes/Boilerplate.elm index 790e8c3f..c26c5561 100644 --- a/src/Stage/InferTypes/Boilerplate.elm +++ b/src/Stage/InferTypes/Boilerplate.elm @@ -153,6 +153,8 @@ moduleOfNewType old newDecls = , filePath = old.filePath , type_ = old.type_ , exposing_ = old.exposing_ + , startComments = old.startComments + , endComments = old.endComments -- all that code because of this: , declarations = newDecls @@ -188,6 +190,7 @@ declarationOfNewType : declarationOfNewType old newBody = { name = old.name , module_ = old.module_ + , commentsBefore = old.commentsBefore -- all that code because of this: , body = newBody diff --git a/src/Stage/Parse.elm b/src/Stage/Parse.elm index 05f9fc13..4e2b8a35 100644 --- a/src/Stage/Parse.elm +++ b/src/Stage/Parse.elm @@ -5,9 +5,10 @@ import Elm.Compiler.Error exposing (Error(..), ParseError(..)) import Elm.Data.FileContents exposing (FileContents) import Elm.Data.FilePath exposing (FilePath) import Elm.Data.Module exposing (Module) +import Elm.Data.ModuleName as ModuleName exposing (ModuleName) import Elm.Data.Qualifiedness exposing (PossiblyQualified) import Elm.Data.TypeAnnotation exposing (TypeAnnotation) -import Parser.Advanced as P +import Stage.Parse.AdvancedWithState as P import Stage.Parse.Parser as Parser @@ -18,6 +19,7 @@ parse : { filePath : FilePath, fileContents : FileContents } -> Result Error (Module Frontend.LocatedExpr TypeAnnotation PossiblyQualified) parse { filePath, fileContents } = - P.run (Parser.module_ filePath) fileContents + P.run (Parser.module_ filePath) { comments = [] } fileContents + |> Result.map Tuple.second |> Result.mapError (\errorList -> ParseError (ParseProblem ( errorList, fileContents ))) diff --git a/src/Stage/Parse/Parser.elm b/src/Stage/Parse/Parser.elm index 96eb9f06..1c4bce81 100644 --- a/src/Stage/Parse/Parser.elm +++ b/src/Stage/Parse/Parser.elm @@ -8,9 +8,8 @@ module Stage.Parse.Parser exposing , moduleDeclaration , moduleName , module_ - , spacesOnly + , spacesAndComments , typeAliasDeclaration - , typeAnnotation , type_ , valueDeclaration ) @@ -25,7 +24,8 @@ import Elm.Compiler.Error , ParseError(..) , ParseProblem(..) ) -import Elm.Data.Binding exposing (Binding) +import Elm.Data.Binding as Binding exposing (Binding) +import Elm.Data.Comment exposing (Comment, CommentType(..)) import Elm.Data.Declaration as Declaration exposing ( Constructor @@ -54,15 +54,15 @@ type alias Parser_ a = type alias ExprConfig = - PP.Config ParseContext ParseProblem LocatedExpr + PP.Config ParseContext ParseProblem ( LocatedExpr, List Comment ) type alias PatternConfig = - PP.Config ParseContext ParseProblem LocatedPattern + PP.Config ParseContext ParseProblem ( LocatedPattern, List Comment ) type alias TypeConfig = - PP.Config ParseContext ParseProblem (ConcreteType PossiblyQualified) + PP.Config ParseContext ParseProblem ( ConcreteType PossiblyQualified, List Comment ) located : Parser_ p -> Parser_ (Located p) @@ -83,7 +83,7 @@ located p = module_ : FilePath -> Parser_ (Module LocatedExpr TypeAnnotation PossiblyQualified) module_ filePath = P.succeed - (\( moduleType_, moduleName_, exposing_ ) imports_ declarations_ -> + (\startComments ( moduleType_, moduleName_, exposing_ ) ( imports_, ( declarations_, commentsAfterDeclarations ) ) -> { imports = imports_ , name = moduleName_ , filePath = filePath @@ -100,12 +100,21 @@ module_ filePath = |> Dict.fromList , type_ = moduleType_ , exposing_ = exposing_ + , startComments = startComments + , endComments = commentsAfterDeclarations } ) + |= spacesAndComments |= moduleDeclaration - -- TODO what about module doc comment? is it before the imports or after? - |= imports - |= declarations + |= (spacesAndComments + |> P.andThen imports + |> P.andThen + (\( imports_, commentsAfterImports ) -> + declarations commentsAfterImports + |> P.map (Tuple.pair imports_) + ) + ) + |> P.withIndent 1 |> P.inContext (InFile filePath) @@ -113,66 +122,116 @@ moduleDeclaration : Parser_ ( ModuleType, ModuleName, Exposing ) moduleDeclaration = P.succeed (\moduleType_ moduleName_ exposing_ -> - ( moduleType_ - , moduleName_ - , exposing_ - ) + ( moduleType_, moduleName_, exposing_ ) ) |= moduleType - |. spacesOnly - -- TODO check the assumption... does Elm allow newlines there? + |. spacesCommentsAndGreaterIndent |= moduleName - |. P.spaces + |. spacesCommentsAndGreaterIndent |. P.keyword (P.Token "exposing" ExpectingExposingKeyword) - |. P.spaces + |. spacesCommentsAndGreaterIndent |= exposingList - |. newlines -imports : Parser_ (Dict ModuleName Import) -imports = - P.succeed - (List.map (\dep -> ( dep.moduleName, dep )) - >> Dict.fromList - ) - |= oneOrMoreWith P.spaces import_ +imports : List Comment -> Parser_ ( Dict ModuleName Import, List Comment ) +imports commentsBefore = + zeroOrMoreWithSpacesAndCommentsInBetween import_ commentsBefore + |> P.map + (Tuple.mapFirst + (List.map (\dep -> ( dep.moduleName, dep )) + >> Dict.fromList + ) + ) -import_ : Parser_ Import +import_ : Parser_ (List Comment -> ( Import, List Comment )) import_ = P.succeed - (\moduleName_ as_ exposing_ -> - { moduleName = moduleName_ - , as_ = as_ - , exposing_ = exposing_ - } + (\commentsBeforeModuleName moduleName_ commentsAfterModuleName as_ exposing_ commentsBefore -> + let + adjusts = + case ( as_, exposing_ ) of + ( Nothing, Nothing ) -> + -- Comments after the module name will be + -- passed to the next parser. + { commentsAfterModuleName = [] + , as_ = Nothing + , exposing_ = Nothing + , commentsAfter = commentsAfterModuleName + } + + ( Just as__, Nothing ) -> + -- Comments after the alias module name will + -- be passed to the next parser. + { commentsAfterModuleName = commentsAfterModuleName + , as_ = Just { as__ | commentsAfterAs = [] } + , exposing_ = Nothing + , commentsAfter = as__.commentsAfterAs + } + + ( _, Just ( exposing__, commentsAfter ) ) -> + -- Comments after the exposing will be passed + -- to the next parser. + { commentsAfterModuleName = commentsAfterModuleName + , as_ = as_ + , exposing_ = Just exposing__ + , commentsAfter = commentsAfter + } + in + ( { commentsBefore = commentsBefore + , commentsBeforeModuleName = commentsBeforeModuleName + , moduleName = moduleName_ + , commentsAfterModuleName = adjusts.commentsAfterModuleName + , as_ = adjusts.as_ + , exposing_ = adjusts.exposing_ + } + , adjusts.commentsAfter + ) ) |. P.keyword (P.Token "import" ExpectingImportKeyword) - |. spacesOnly - -- TODO check expectation ... what about newlines here? + |. checkTooMuchIndentation "import" + |= spacesCommentsAndGreaterIndent |= moduleName - |. P.spaces + |= spacesAndComments |= P.oneOf - [ P.succeed Just + [ P.succeed + (\commentsBeforeAs as_ commentsAfterAs -> + Just + { commentsBeforeAs = commentsBeforeAs + , as_ = as_ + , commentsAfterAs = commentsAfterAs + } + ) + |. checkIndent (<) ExpectingIndentation |. P.keyword (P.Token "as" ExpectingAsKeyword) - |. P.spaces + |= spacesCommentsAndGreaterIndent |= moduleNameWithoutDots + |. P.oneOf + [ P.symbol (P.Token "." ExpectingModuleNameWithoutDots) + |> P.andThen (always (P.problem ExpectingModuleNameWithoutDots)) + , P.succeed () + ] + |= spacesAndComments , P.succeed Nothing ] - |. P.oneOf - [ -- not sure if this is idiomatic - P.symbol (P.Token "." ExpectingModuleNameWithoutDots) - |. P.problem ExpectingModuleNameWithoutDots - , P.spaces - ] - |. P.spaces |= P.oneOf - [ P.succeed Just + [ P.succeed + (\commentsBeforeExposing exposing_ commentsAfterExposing -> + Just + ( { commentsBeforeExposing = commentsBeforeExposing + , exposing_ = exposing_ + } + , commentsAfterExposing + ) + ) + |. checkIndent (<) ExpectingIndentation |. P.keyword (P.Token "exposing" ExpectingExposingKeyword) - |. P.spaces + |= spacesCommentsAndGreaterIndent |= exposingList + |= spacesAndComments , P.succeed Nothing ] + |> P.inContext InImport moduleType : Parser_ ModuleType @@ -188,13 +247,15 @@ plainModuleType : Parser_ ModuleType plainModuleType = P.succeed PlainModule |. P.keyword (P.Token "module" ExpectingModuleKeyword) + |. checkTooMuchIndentation "module" portModuleType : Parser_ ModuleType portModuleType = P.succeed PortModule |. P.keyword (P.Token "port" ExpectingPortKeyword) - |. spacesOnly + |. checkTooMuchIndentation "port" + |. spacesCommentsAndGreaterIndent |. P.keyword (P.Token "module" ExpectingModuleKeyword) @@ -203,28 +264,34 @@ effectModuleType = -- TODO some metadata? P.succeed EffectModule |. P.keyword (P.Token "effect" ExpectingEffectKeyword) - |. spacesOnly + |. checkTooMuchIndentation "effect" + |. spacesCommentsAndGreaterIndent |. P.keyword (P.Token "module" ExpectingModuleKeyword) moduleName : Parser_ String moduleName = - P.sequence - { start = P.Token "" (ParseCompilerBug ModuleNameStartParserFailed) - , separator = P.Token "." ExpectingModuleDot - , end = P.Token "" (ParseCompilerBug ModuleNameEndParserFailed) - , spaces = P.succeed () - , item = moduleNameWithoutDots - , trailing = P.Forbidden - } - |> P.andThen - (\list_ -> - if List.isEmpty list_ then - P.problem ExpectingModuleName + P.loop [] moduleNameHelp - else - P.succeed (String.join "." list_) - ) + +moduleNameHelp : List String -> Parser_ (P.Step (List String) String) +moduleNameHelp acc = + P.succeed + (\moduleNameStr continue -> + if continue then + P.Loop (moduleNameStr :: acc) + + else + List.reverse (moduleNameStr :: acc) + |> String.join "." + |> P.Done + ) + |= moduleNameWithoutDots + |= P.oneOf + [ P.symbol (P.Token "." ExpectingModuleDot) + |> P.map (\_ -> True) + , P.succeed False + ] moduleNameWithoutDots : Parser_ String @@ -257,8 +324,18 @@ exposingSome = { start = P.Token "(" ExpectingLeftParen , separator = P.Token "," ExpectingComma , end = P.Token ")" ExpectingRightParen - , spaces = P.spaces - , item = exposedItem + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore item commentsAfter -> + { commentsBefore = commentsBefore + , item = item + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= exposedItem + |= spacesCommentsAndGreaterIndent , trailing = P.Forbidden } |> P.andThen @@ -335,65 +412,37 @@ reservedWords = ] -declarations : Parser_ (List (ModuleName -> Declaration LocatedExpr TypeAnnotation PossiblyQualified)) -declarations = - oneOrMoreWith P.spaces - (P.succeed identity - |= declaration - |. P.spaces - ) +declarations : List Comment -> Parser_ ( List (ModuleName -> Declaration LocatedExpr TypeAnnotation PossiblyQualified), List Comment ) +declarations commentsBefore = + zeroOrMoreWithSpacesAndCommentsInBetween declaration commentsBefore -declaration : Parser_ (ModuleName -> Declaration LocatedExpr TypeAnnotation PossiblyQualified) +declaration : Parser_ (List Comment -> ( ModuleName -> Declaration LocatedExpr TypeAnnotation PossiblyQualified, List Comment )) declaration = P.succeed - (\( name, body ) module__ -> - { module_ = module__ - , name = name - , body = body - } + (\( name, body, commentsAfter ) commentsBefore -> + ( \module__ -> + { module_ = module__ + , name = name + , commentsBefore = commentsBefore + , body = body + } + , commentsAfter + ) ) |= declarationBody |> P.inContext InDeclaration -declarationBody : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified ) +declarationBody : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified, List Comment ) declarationBody = P.oneOf [ typeAliasDeclaration , customTypeDeclaration - , valueDeclaration + , valueDeclaration Nothing ] -valueDeclaration : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified ) -valueDeclaration = - P.succeed - (\annotation name expr_ -> - ( name - , Declaration.Value - { typeAnnotation = annotation - , expression = expr_ - } - ) - ) - |= P.oneOf - -- TODO refactor the `backtrackable` away - -- TODO is it even working correctly? - [ P.backtrackable - (P.succeed Just - |= typeAnnotation - |. P.spaces - ) - , P.succeed Nothing - ] - |= varName - |. P.spaces - |. P.symbol (P.Token "=" ExpectingEqualsSign) - |. P.spaces - |= expr - - {-| type alias X = Int @@ -404,26 +453,39 @@ More generally, type alias * = -} -typeAliasDeclaration : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified ) +typeAliasDeclaration : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified, List Comment ) typeAliasDeclaration = P.succeed - (\name parameters type__ -> + (\commentsBeforeName name ( parameters, commentsAfterParameters ) ( type__, commentsAfter ) -> ( name , Declaration.TypeAlias { parameters = parameters , definition = type__ } + , commentsAfter ) ) |. P.keyword (P.Token "type alias" ExpectingTypeAlias) - |. P.spaces + |= spacesCommentsAndGreaterIndent |= moduleNameWithoutDots - |. P.symbol (P.Token " " ExpectingSpace) - |. P.spaces - |= zeroOrMoreWith P.spaces varName - |. P.spaces + |= (spacesCommentsAndGreaterIndent + |> P.andThen + (zeroOrMoreWithSpacesAndCommentsInBetween + (P.succeed + (\parameter commentsAfterParameter commentsBefore -> + {- TODO -} + ( parameter + , commentsAfterParameter + ) + ) + |= varName + |= spacesCommentsAndGreaterIndent + ) + ) + ) + |. spacesCommentsAndGreaterIndent |. P.symbol (P.Token "=" ExpectingEqualsSign) - |. P.spaces + |. spacesCommentsAndGreaterIndent |= type_ |> P.inContext InTypeAlias @@ -440,104 +502,240 @@ More generally, Constructor := [ ]* -} -customTypeDeclaration : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified ) +customTypeDeclaration : Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified, List Comment ) customTypeDeclaration = P.succeed - (\name parameters constructors_ -> + (\name ( parameters, commentsAfterParameters {- TODO -} ) ( constructors_, commentsAfter ) -> ( name , Declaration.CustomType { parameters = parameters , constructors = constructors_ } + , commentsAfter ) ) |. P.keyword (P.Token "type" ExpectingTypeAlias) - |. P.spaces + |. spacesCommentsAndGreaterIndent |= moduleNameWithoutDots - |. P.symbol (P.Token " " ExpectingSpace) - |. P.spaces - |= zeroOrMoreWith P.spaces varName - |. P.spaces + |= (spacesCommentsAndGreaterIndent + |> P.andThen + (zeroOrMoreWithSpacesAndCommentsInBetween + (P.succeed + (\parameter commentsAfterParameter commentsBefore -> + {- TODO -} + ( parameter + , commentsAfterParameter + ) + ) + |= varName + |= spacesCommentsAndGreaterIndent + ) + ) + ) |. P.symbol (P.Token "=" ExpectingEqualsSign) - |. P.spaces |= constructors |> P.inContext InCustomType -constructors : Parser_ (NonEmpty (Constructor PossiblyQualified)) +constructors : Parser_ ( NonEmpty (Constructor PossiblyQualified), List Comment ) constructors = - P.sequence - { start = P.Token "" (ParseCompilerBug ConstructorsStartParserFailed) - , separator = P.Token "|" ExpectingPipe - , end = P.Token "" (ParseCompilerBug ConstructorsEndParserFailed) - , spaces = P.spaces - , item = constructor - , trailing = P.Forbidden - } + {- TODO store comments -} + P.succeed Tuple.pair + |= spacesCommentsAndGreaterIndent + |= P.oneOf + [ constructor + , P.succeed () + |> P.andThen (\_ -> P.problem EmptyListOfConstructors) + ] |> P.andThen - (\constructors_ -> - case List.NonEmpty.fromList constructors_ of - Nothing -> - P.problem EmptyListOfConstructors - - Just c -> - P.succeed c + (\( commentsBefore, ( firstConstructor, commentsAfterFirstConstructor ) ) -> + zeroOrMoreWithSpacesAndCommentsInBetween + (P.succeed + (\commentsBeforeConstructor ( constructor_, commentsAfter ) commentsBeforePipe -> + ( constructor_, commentsAfter ) + ) + |. P.keyword (P.Token "|" ExpectingPipe) + |= spacesCommentsAndGreaterIndent + |= constructor + ) + commentsAfterFirstConstructor + |> P.map + (Tuple.mapFirst + (List.NonEmpty.fromCons firstConstructor) + ) ) |> P.inContext InConstructors -constructor : Parser_ (Constructor PossiblyQualified) +constructor : Parser_ ( Constructor PossiblyQualified, List Comment ) constructor = - P.succeed Declaration.Constructor + P.succeed + (\moduleName_ ( types, commentsAfter ) -> + ( Declaration.Constructor moduleName_ types + , commentsAfter + ) + ) |= moduleNameWithoutDots - |. P.spaces - |= oneOrMoreWith P.spaces type_ + |= (spacesAndComments + |> P.andThen userDefinedTypeArgs + ) + + +valueDeclaration : Maybe ( TypeAnnotation, List Comment ) -> Parser_ ( String, DeclarationBody LocatedExpr TypeAnnotation PossiblyQualified, List Comment ) +valueDeclaration maybeAnn = + P.succeed + (\name commentsAfterVarName -> + P.oneOf + [ P.succeed + (\commentsBeforeType ( type__, commentsAfter ) -> + ( { varName = name + , commentsAfterVarName = commentsAfterVarName + , commentsBeforeType = commentsBeforeType + , type_ = type__ + } + , commentsAfter + ) + ) + |. P.symbol (P.Token ":" ExpectingColon) + |. (case maybeAnn of + Nothing -> + P.succeed () + + Just _ -> + P.problem ExpectingTypeAnnotationDefinition + ) + |= spacesCommentsAndGreaterIndent + |= type_ + |> P.inContext InTypeAnnotation + |> P.andThen (Just >> valueDeclaration) + , P.succeed + (\commentsExpression ( expr_, commentsAfter ) -> + ( name + , Declaration.Value + { typeAnnotation = Maybe.map Tuple.first maybeAnn + , commentsAfterTypeAnnotation = + Maybe.map Tuple.second maybeAnn + |> Maybe.withDefault [] + , expression = expr_ + } + , commentsAfter + ) + ) + |. P.symbol (P.Token "=" ExpectingEqualsSign) + |= spacesCommentsAndGreaterIndent + |= expr + ] + ) + |= varName + |= spacesCommentsAndGreaterIndent + |> P.andThen identity -expr : Parser_ LocatedExpr +expr : Parser_ ( LocatedExpr, List Comment ) expr = PP.expression { oneOf = [ if_ , let_ , lambda - , PP.literal literal - , always var - , unit - , list - , tuple - , tuple3 - , parenthesizedExpr - , record + , PP.literal (withCommentsAfter literal) + , PP.literal (withCommentsAfter var) + , PP.literal (withCommentsAfter unit) + , list >> withCommentsAfter + , parenthesized >> withCommentsAfter + , record >> withCommentsAfter , case_ ] , andThenOneOf = + [ infixLeftWithOperatorResult 1 + (P.succeed identity + |. P.symbol (P.Token "++" ExpectingConcatOperator) + |= spacesCommentsAndGreaterIndent + ) + (\( locatedLeft, commentsAfterLeft ) commentsBeforeRight ( locatedRight, commentsAfterRight ) -> + ( Located.merge + (\left right -> + ListConcat + { left = left + , commentsAfterLeft = commentsAfterLeft + , commentsBeforeRight = commentsBeforeRight + , right = right + } + ) + locatedLeft + locatedRight + , commentsAfterRight + ) + ) + , infixLeftWithOperatorResult 1 + (P.succeed identity + |. P.symbol (P.Token "+" ExpectingPlusOperator) + |= spacesCommentsAndGreaterIndent + ) + (\( locatedLeft, commentsAfterLeft ) commentsBeforeRight ( locatedRight, commentsAfterRight ) -> + ( Located.merge + (\left right -> + Plus + { left = left + , commentsAfterLeft = commentsAfterLeft + , commentsBeforeRight = commentsBeforeRight + , right = right + } + ) + locatedLeft + locatedRight + , commentsAfterRight + ) + ) + , infixRightWithOperatorResult 1 + (P.succeed identity + |. P.symbol (P.Token "::" ExpectingConsOperator) + |= spacesCommentsAndGreaterIndent + ) + (\( locatedLeft, commentsAfterLeft ) commentsBeforeRight ( locatedRight, commentsAfterRight ) -> + ( Located.merge + (\left right -> + Cons + { left = left + , commentsAfterLeft = commentsAfterLeft + , commentsBeforeRight = commentsBeforeRight + , right = right + } + ) + locatedLeft + locatedRight + , commentsAfterRight + ) + ) + -- TODO test this: does `x =\n call 1\n+ something` work? (it shouldn't: no space before '+') - [ PP.infixLeft 99 - (ignorablesAndCheckIndent (<) ExpectingIndentation) - (Located.merge - (\fn argument -> - Frontend.Call - { fn = fn - , argument = argument - } + , PP.infixLeft 99 + (checkIndent (<) ExpectingIndentation) + (\( locatedLeft, commentsAfterLeft ) ( locatedRight, commentsAfterRight ) -> + ( Located.merge + (\left right -> + Frontend.Call + { fn = left + , comments = commentsAfterLeft + , argument = right + } + ) + locatedLeft + locatedRight + , commentsAfterRight ) ) - , PP.infixLeft 1 (P.symbol (P.Token "++" ExpectingConcatOperator)) (Located.merge ListConcat) - , PP.infixLeft 1 (P.symbol (P.Token "+" ExpectingPlusOperator)) (Located.merge Plus) - , PP.infixRight 1 (P.symbol (P.Token "::" ExpectingConsOperator)) (Located.merge Cons) ] - , spaces = P.spaces + , spaces = P.succeed () } |> P.inContext InExpr -parenthesizedExpr : ExprConfig -> Parser_ LocatedExpr -parenthesizedExpr config = - P.succeed identity - |. P.symbol (P.Token "(" ExpectingLeftParen) - |= PP.subExpression 0 config - |. P.symbol (P.Token ")" ExpectingRightParen) +withCommentsAfter : Parser_ a -> Parser_ ( a, List Comment ) +withCommentsAfter parser = + P.succeed Tuple.pair + |= parser + |= spacesAndComments literal : Parser_ LocatedExpr @@ -769,19 +967,6 @@ varName = |> P.inContext InVarName -qualifiers : Parser_ (List ModuleName) -qualifiers = - P.sequence - { start = P.Token "" (ParseCompilerBug QualifiersStartParserFailed) - , separator = P.Token "." ExpectingQualifiedVarNameDot - , end = P.Token "" (ParseCompilerBug QualifiersEndParserFailed) - , spaces = P.succeed () - , item = moduleNameWithoutDots - , trailing = P.Mandatory - } - |> P.inContext InQualifiers - - qualify : List ModuleName -> PossiblyQualified qualify modules = PossiblyQualified <| @@ -794,27 +979,36 @@ qualify modules = qualifiedVar : Parser_ Expr qualifiedVar = - qualifiers - |> P.andThen - (\modules -> - P.map - (\varName_ -> - Frontend.Var - { qualifiedness = qualify modules - , name = varName_ - } - ) - varName - ) + P.loop [] qualifiedVarHelp |> P.inContext InQualifiedVar -lambda : ExprConfig -> Parser_ LocatedExpr +qualifiedVarHelp : List String -> Parser_ (P.Step (List String) Expr) +qualifiedVarHelp acc = + P.oneOf + [ P.succeed (\moduleNameStr -> P.Loop (moduleNameStr :: acc)) + |= moduleNameWithoutDots + |. P.symbol (P.Token "." ExpectingQualifiedVarNameDot) + , varName + |> P.map + (\varName_ -> + { qualifiedness = qualify (List.reverse acc) + , name = varName_ + } + |> Frontend.Var + |> P.Done + ) + ] + + +lambda : ExprConfig -> Parser_ ( LocatedExpr, List Comment ) lambda config = P.succeed - (\arguments body -> - Frontend.Lambda + (\( startRow, startCol ) ( arguments, commentsAfterArguments ) commentsBeforeBody ( body, commentsAfter ) -> + ( Frontend.Lambda { arguments = arguments + , commentsAfterArguments = commentsAfterArguments + , commentsBeforeBody = commentsBeforeBody , body = {- Run the promoting transformation on every subexpression, so that after parsing all the arguments aren't unqualified @@ -832,79 +1026,176 @@ lambda config = TODO add a fuzz test for this invariant? -} - Located.map (Frontend.transform (promoteArguments arguments)) body + Located.map + (Frontend.transform + (promoteArguments (List.map .argument arguments)) + ) + body } + |> Located.located + { start = { row = startRow, col = startCol } + , end = .end (Located.getRegion body) + } + , commentsAfter + ) ) + |= P.getPosition |. P.symbol (P.Token "\\" ExpectingBackslash) - |= oneOrMoreWith spacesOnly varName - |. spacesOnly - |. P.symbol (P.Token "->" ExpectingRightArrow) - |. P.spaces + |= (spacesCommentsAndGreaterIndent + |> P.andThen (zeroOrMoreWithSpacesAndCommentsInBetween lambdaArgument) + ) + |. P.keyword (P.Token "->" ExpectingRightArrow) + |= spacesCommentsAndGreaterIndent |= PP.subExpression 0 config |> P.inContext InLambda - |> located -if_ : ExprConfig -> Parser_ LocatedExpr +lambdaArgument : Parser_ (List Comment -> ( { commentsBefore : List Comment, argument : VarName }, List Comment )) +lambdaArgument = + P.succeed + (\argument commentsAfter commentsBefore -> + ( { commentsBefore = commentsBefore + , argument = argument + } + , commentsAfter + ) + ) + |= varName + |= spacesCommentsAndGreaterIndent + + +if_ : ExprConfig -> Parser_ ( LocatedExpr, List Comment ) if_ config = P.succeed - (\test then_ else_ -> - Frontend.If - { test = test + (\( startRow, startCol ) commentsBeforeTest ( test, commentsAfterTest ) commentsBeforeThen ( then_, commentsAfterThen ) commentsBeforeElse ( else_, commentsAfterElse ) -> + ( Frontend.If + { commentsBeforeTest = commentsBeforeTest + , test = test + , commentsAfterTest = commentsAfterTest + , commentsBeforeThen = commentsBeforeThen , then_ = then_ + , commentsAfterThen = commentsAfterThen + , commentsBeforeElse = commentsBeforeElse , else_ = else_ } + |> Located.located + { start = { row = startRow, col = startCol } + , end = .end (Located.getRegion else_) + } + , commentsAfterElse + ) ) + |= P.getPosition |. P.keyword (P.Token "if" ExpectingIf) + |= spacesAndComments |= PP.subExpression 0 config |. P.keyword (P.Token "then" ExpectingThen) + |= spacesAndComments |= PP.subExpression 0 config |. P.keyword (P.Token "else" ExpectingElse) + |= spacesAndComments |= PP.subExpression 0 config |> P.inContext InIf - |> located -let_ : ExprConfig -> Parser_ LocatedExpr +let_ : ExprConfig -> Parser_ ( LocatedExpr, List Comment ) let_ config = + P.succeed identity + |= P.getPosition + |. P.keyword (P.Token "let" ExpectingLet) + |> P.andThen + (\( startRow, startCol ) -> + P.succeed + (\commentsBeforeBindings bindingIndent -> + ( { row = startRow, col = startCol } + , commentsBeforeBindings + , bindingIndent + ) + ) + |= spacesCommentsAndCheckIndent + (\_ col -> startCol < col) + ExpectingLetIndentation + |= P.getCol + ) + |> P.andThen + (\( start, commentsBeforeBindings, bindingIndent ) -> + P.succeed + (\( bindings, commentsAfterBindings ) commentsBeforeBody ( body, commentsAfterBody ) -> + ( Frontend.Let + { bindings = bindings + , commentsAfterBindings = commentsAfterBindings + , commentsBeforeBody = commentsBeforeBody + , body = body + } + |> Located.located + { start = start + , end = .end (Located.getRegion body) + } + , commentsAfterBody + ) + ) + |= P.withIndent bindingIndent + (zeroOrMoreWithSpacesAndCommentsInBetween + (letBinding config) + commentsBeforeBindings + ) + |. P.keyword (P.Token "in" ExpectingIn) + |= spacesCommentsAndGreaterIndent + |= PP.subExpression 0 config + ) + |> P.inContext InLet + + +letBinding : + ExprConfig + -> Parser_ (List Comment -> ( { commentsBefore : List Comment, binding : Binding.Commented LocatedExpr }, List Comment )) +letBinding config = P.succeed - (\binding_ body -> - Frontend.Let - -- TODO multiple let bindings - { bindings = [ binding_ ] - , body = body - } + (\( binding_, commentsAfter ) commentsBefore -> + ( { commentsBefore = commentsBefore + , binding = binding_ + } + , commentsAfter + ) ) - |. P.keyword (P.Token "let" ExpectingLet) - |. P.spaces + |. checkIndent (==) ExpectingLetBindingIndentation |= binding config - |. P.spaces - |. P.keyword (P.Token "in" ExpectingIn) - |. P.spaces - |= PP.subExpression 0 config - |> P.inContext InLet - |> located + |> P.inContext InLetBinding + + +recordBinding : ExprConfig -> Parser_ ( Binding.Commented LocatedExpr, List Comment ) +recordBinding config = + binding config + |> P.inContext InRecordBinding -binding : ExprConfig -> Parser_ (Binding LocatedExpr) +binding : ExprConfig -> Parser_ ( Binding.Commented LocatedExpr, List Comment ) binding config = - P.succeed Binding + P.succeed + (\name commentsAfterName commentsBeforeBody ( body, commentsAfterBody ) -> + ( { name = name + , commentsAfterName = commentsAfterName + , commentsBeforeBody = commentsBeforeBody + , body = body + } + , commentsAfterBody + ) + ) |= varName - |. P.spaces + |= spacesCommentsAndGreaterIndent |. P.symbol (P.Token "=" ExpectingEqualsSign) - |. P.spaces + |= spacesCommentsAndGreaterIndent |= PP.subExpression 0 config - |> P.inContext InLetBinding -typeBinding : TypeConfig -> Parser_ ( VarName, ConcreteType PossiblyQualified ) -typeBinding config = +typeBinding : Parser_ ( VarName, ( ConcreteType PossiblyQualified, List Comment ) ) +typeBinding = P.succeed Tuple.pair |= varName - |. P.spaces + |. spacesCommentsAndGreaterIndent |. P.symbol (P.Token ":" ExpectingColon) - |. P.spaces - |= PP.subExpression 0 config + |. spacesCommentsAndGreaterIndent + |= P.lazy (\_ -> type_) |> P.inContext InTypeBinding @@ -926,144 +1217,242 @@ promoteArguments arguments expr_ = expr_ -unit : ExprConfig -> Parser_ LocatedExpr -unit _ = +unit : Parser_ LocatedExpr +unit = P.succeed Frontend.Unit |. P.keyword (P.Token "()" ExpectingUnit) |> P.inContext InUnit |> located -list : ExprConfig -> Parser_ LocatedExpr -list config = - P.succeed Frontend.List - |= P.sequence - { start = P.Token "[" ExpectingLeftBracket - , separator = P.Token "," ExpectingListSeparator - , end = P.Token "]" ExpectingRightBracket - , spaces = spacesOnly - , item = PP.subExpression 0 config - , trailing = P.Forbidden - } - |> P.inContext InList +parenthesized : ExprConfig -> Parser_ LocatedExpr +parenthesized config = + P.sequence + { start = P.Token "(" ExpectingLeftParen + , separator = P.Token "," ExpectingTupleSeparator + , end = P.Token ")" ExpectingRightParen + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( expr_, commentsAfter ) -> + { commentsBefore = commentsBefore + , expr = expr_ + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= PP.subExpression 0 config + , trailing = P.Forbidden + } |> located + |> P.andThen + (\items -> + case Located.unwrap items of + [] -> + P.problem ExpectingExpression + + [ item ] -> + Located.map + (\_ -> Parenthesized item) + items + |> P.succeed + [ item1, item2 ] -> + Located.map + (\_ -> Tuple item1 item2) + items + |> P.succeed + |> P.inContext InTuple -tuple : ExprConfig -> Parser_ LocatedExpr -tuple config = - P.backtrackable - (P.succeed Tuple - |. P.symbol (P.Token "(" ExpectingLeftParen) - |. P.spaces - |= PP.subExpression 0 config - |. P.spaces - |. P.symbol (P.Token "," ExpectingTupleSeparator) - |. P.spaces - |= PP.subExpression 0 config - |. P.spaces - |. P.symbol (P.Token ")" ExpectingRightParen) - |> P.inContext InTuple - ) - |> located - + [ item1, item2, item3 ] -> + Located.map + (\_ -> Tuple3 item1 item2 item3) + items + |> P.succeed + |> P.inContext InTuple -tuple3 : ExprConfig -> Parser_ LocatedExpr -tuple3 config = - P.backtrackable - (P.succeed Frontend.Tuple3 - |. P.symbol (P.Token "(" ExpectingLeftParen) - |. P.spaces - |= PP.subExpression 0 config - |. P.spaces - |. P.symbol (P.Token "," ExpectingTupleSeparator) - |. P.spaces - |= PP.subExpression 0 config - |. P.spaces - |. P.symbol (P.Token "," ExpectingTupleSeparator) - |. P.spaces - |= PP.subExpression 0 config - |. P.spaces - |. P.symbol (P.Token ")" ExpectingRightParen) - |> P.inContext InTuple3 - ) - |> located + _ -> + P.problem ExpectingMaxThreeTuple + ) record : ExprConfig -> Parser_ LocatedExpr record config = - P.succeed Frontend.Record - |= P.sequence - { start = P.Token "{" ExpectingLeftBrace - , separator = P.Token "," ExpectingComma - , end = P.Token "}" ExpectingRightBrace - , spaces = spacesOnly - , item = binding config - , trailing = P.Forbidden - } + P.sequence + { start = P.Token "{" ExpectingRecordLeftBrace + , separator = P.Token "," ExpectingRecordSeparator + , end = P.Token "}" ExpectingRecordRightBrace + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( binding_, commentsAfter ) -> + { commentsBefore = commentsBefore + , binding = binding_ + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= recordBinding config + , trailing = P.Forbidden + } + |> P.map Frontend.Record |> P.inContext InRecord |> located -case_ : ExprConfig -> Parser_ LocatedExpr +list : ExprConfig -> Parser_ LocatedExpr +list config = + P.sequence + { start = P.Token "[" ExpectingLeftBracket + , separator = P.Token "," ExpectingListSeparator + , end = P.Token "]" ExpectingRightBracket + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( expr_, commentsAfter ) -> + { commentsBefore = commentsBefore + , expr = expr_ + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= PP.subExpression 0 config + , trailing = P.Forbidden + } + |> P.map Frontend.List + |> P.inContext InList + |> located + + +case_ : ExprConfig -> Parser_ ( LocatedExpr, List Comment ) case_ config = P.succeed - (\test branchAlignCol -> - P.succeed (Frontend.Case test) - |= P.withIndent branchAlignCol - (oneOrMoreWith ignorables (caseBranch config)) + (\( startRow, startCol ) commentsBeforeTest ( test, commentsAfterTest ) commentsBeforePattern branchAlignCol -> + zeroOrMoreWithSpacesAndCommentsInBetween (caseBranch config) + commentsBeforePattern + |> P.withIndent branchAlignCol + |> P.map + (\( branches, commentsAfter ) -> + ( Frontend.Case + { commentsBeforeTest = commentsBeforeTest + , test = test + , commentsAfterTest = commentsAfterTest + , branches = branches + } + |> Located.located + { start = { row = startRow, col = startCol } + , end = + --TODO: end is the last branch's end + { row = startRow, col = startCol } + } + , commentsAfter + ) + ) ) + |= P.getPosition |. P.keyword (P.Token "case" ExpectingCase) - |. ignorables + |= spacesAndComments |= PP.subExpression 0 config - |. ignorables |. P.keyword (P.Token "of" ExpectingOf) - |. ignorables + |= spacesAndComments |= P.getCol |> P.andThen identity |> P.inContext InCase - |> located -caseBranch : ExprConfig -> Parser_ { pattern : LocatedPattern, body : LocatedExpr } +caseBranch : + ExprConfig + -> + Parser_ + (List Comment + -> + ( { commentsBeforePattern : List Comment + , pattern : LocatedPattern + , commentsAfterPattern : List Comment + , commentsBeforeBody : List Comment + , body : LocatedExpr + } + , List Comment + ) + ) caseBranch config = P.succeed - (\pattern_ body -> - { pattern = pattern_ - , body = body - } + (\( pattern_, commentsAfterPattern ) commentsBeforeBody ( body, commentsAfter ) commentsBeforePattern -> + ( { commentsBeforePattern = commentsBeforePattern + , pattern = pattern_ + , commentsAfterPattern = commentsAfterPattern + , commentsBeforeBody = commentsBeforeBody + , body = body + } + , commentsAfter + ) ) |. checkIndent (==) ExpectingIndentation |= pattern - |. ignorablesAndCheckIndent (<) ExpectingRightArrow + |. checkIndent (<) ExpectingRightArrow |. P.symbol (P.Token "->" ExpectingRightArrow) - |. ignorablesAndCheckIndent (<) ExpectingCaseBody + |= spacesCommentsAndCheckIndent (<) ExpectingCaseBody |= PP.subExpression 0 config -pattern : Parser_ LocatedPattern +pattern : Parser_ ( LocatedPattern, List Comment ) pattern = PP.expression { oneOf = - [ PP.literal patternLiteral - , patternList - , patternTuple + [ PP.literal (withCommentsAfter patternLiteral) + , patternTuple >> withCommentsAfter + , patternList >> withCommentsAfter ] , andThenOneOf = - [ PP.infixRight 1 (P.symbol (P.Token "::" ExpectingConsOperator)) (Located.merge PCons) - , postfix 1 + [ infixRightWithOperatorResult 1 (P.succeed identity + |. P.symbol (P.Token "::" ExpectingConsOperator) + |= spacesCommentsAndCheckIndent (<) ExpectingIndentation + ) + (\( locatedLeft, commentsAfterLeft ) commentsBeforeRight ( locatedRight, commentsAfterRight ) -> + ( Located.merge + (\left right -> + PCons + { left = left + , commentsAfterLeft = commentsAfterLeft + , commentsBeforeRight = commentsBeforeRight + , right = right + } + ) + locatedLeft + locatedRight + , commentsAfterRight + ) + ) + , postfixWithOperatorResult 99 + (P.succeed (\a b c -> ( a, b, c )) |. P.keyword (P.Token "as" ExpectingAsKeyword) - |. ignorablesAndCheckIndent (<) ExpectingPatternAliasName + |= spacesCommentsAndCheckIndent (<) ExpectingPatternAliasName |= varName + |= spacesCommentsAndCheckIndent (<) ExpectingPatternAliasName |> located ) - (Located.merge - (\pattern_ alias_ -> - PAlias pattern_ (Located.unwrap alias_) + (\( locatedPattern, commentsAfterPattern ) locatedAlias -> + let + ( commentsBeforeAlias, alias_, commentsAfter ) = + Located.unwrap locatedAlias + in + ( Located.merge + (\pattern_ _ -> + PAlias + { pattern = pattern_ + , commentsAfterPattern = commentsAfterPattern + , commentsBeforeAlias = commentsBeforeAlias + , alias = alias_ + } + ) + locatedPattern + locatedAlias + , commentsAfter ) ) ] - , spaces = ignorables + , spaces = P.succeed () } |> P.inContext InPattern @@ -1073,10 +1462,10 @@ patternLiteral = P.oneOf [ patternAnything , patternUnit + , patternBool , patternVar , patternChar , patternString - , patternBool , patternNumber , patternRecord ] @@ -1096,40 +1485,12 @@ patternUnit = |> located -patternChar : Parser_ LocatedPattern -patternChar = - P.succeed PChar - |. P.symbol singleQuote - |= character SingleQuote - |. P.symbol singleQuote - |> P.inContext InChar - |> located - - -patternString : Parser_ LocatedPattern -patternString = - P.succeed PString - |= P.oneOf - [ tripleQuoteString - , doubleQuoteString - ] - |> P.inContext InString - |> located - - patternBool : Parser_ LocatedPattern patternBool = P.map PBool bool |> located -patternVar : Parser_ LocatedPattern -patternVar = - P.map PVar varName - |> located - |> P.inContext InPatternVar - - patternNumber : Parser_ LocatedPattern patternNumber = let @@ -1166,14 +1527,103 @@ patternNumber = |> located +patternChar : Parser_ LocatedPattern +patternChar = + P.succeed PChar + |. P.symbol singleQuote + |= character SingleQuote + |. P.symbol singleQuote + |> P.inContext InChar + |> located + + +patternString : Parser_ LocatedPattern +patternString = + P.succeed PString + |= P.oneOf + [ tripleQuoteString + , doubleQuoteString + ] + |> P.inContext InString + |> located + + +patternVar : Parser_ LocatedPattern +patternVar = + P.map PVar varName + |> located + |> P.inContext InPatternVar + + +patternTuple : PatternConfig -> Parser_ LocatedPattern +patternTuple config = + P.sequence + { start = P.Token "(" ExpectingLeftParen + , separator = P.Token "," ExpectingTupleSeparator + , end = P.Token ")" ExpectingRightParen + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( pattern_, commentsAfter ) -> + { commentsBefore = commentsBefore + , pattern = pattern_ + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= PP.subExpression 0 config + , trailing = P.Forbidden + } + |> located + |> P.andThen + (\items -> + case Located.unwrap items of + [] -> + P.problem ExpectingExpression + + [ item ] -> + Located.map + (\_ -> PParenthesized item) + items + |> P.succeed + + [ item1, item2 ] -> + Located.map + (\_ -> PTuple item1 item2) + items + |> P.succeed + |> P.inContext InTuple + + [ item1, item2, item3 ] -> + Located.map + (\_ -> PTuple3 item1 item2 item3) + items + |> P.succeed + |> P.inContext InTuple + + _ -> + P.problem ExpectingMaxThreeTuple + ) + + patternRecord : Parser_ LocatedPattern patternRecord = P.sequence { start = P.Token "{" ExpectingRecordLeftBrace , separator = P.Token "," ExpectingRecordSeparator , end = P.Token "}" ExpectingRecordRightBrace - , spaces = ignorablesAndCheckIndent (<) ExpectingIndentation - , item = varName + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore varName_ commentsAfter -> + { commentsBefore = commentsBefore + , varName = varName_ + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= varName + |= spacesCommentsAndGreaterIndent , trailing = P.Forbidden } |> P.map PRecord @@ -1187,8 +1637,17 @@ patternList config = { start = P.Token "[" ExpectingLeftBracket , separator = P.Token "," ExpectingListSeparator , end = P.Token "]" ExpectingRightBracket - , spaces = ignorablesAndCheckIndent (<) ExpectingIndentation - , item = PP.subExpression 0 config + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( pattern_, commentsAfter ) -> + { commentsBefore = commentsBefore + , pattern = pattern_ + , commentsAfter = commentsAfter + } + ) + |= spacesCommentsAndGreaterIndent + |= PP.subExpression 0 config , trailing = P.Forbidden } |> P.map PList @@ -1196,56 +1655,310 @@ patternList config = |> located -patternTuple : PatternConfig -> Parser_ LocatedPattern -patternTuple config = +type_ : Parser_ ( ConcreteType PossiblyQualified, List Comment ) +type_ = + PP.expression + { oneOf = + [ PP.literal (withCommentsAfter unitType) + , PP.literal (withCommentsAfter varType) + , PP.literal (withCommentsAfter recordType) + , PP.literal (withCommentsAfter parenthesizedType) + , PP.literal userDefinedType + ] + , andThenOneOf = + [ infixRightWithOperatorResult 1 + (P.succeed identity + |. P.token (P.Token "->" ExpectingRightArrow) + |= spacesAndComments + ) + (\( from, commentsAfterFrom ) commentsBeforeTo ( to, commentsAfterTo ) -> + ( ConcreteType.Function + { from = from + , to = to + } + , commentsAfterTo + ) + ) + ] + , spaces = P.succeed () + } + |> P.inContext InType + + +unitType : Parser_ (ConcreteType PossiblyQualified) +unitType = + P.succeed ConcreteType.Unit + |. P.keyword (P.Token "()" (ExpectingSimpleType "()")) + + +varType : Parser_ (ConcreteType PossiblyQualified) +varType = + P.succeed ConcreteType.TypeVar + |= varName + |> P.inContext InTypeVarType + + +recordType : Parser_ (ConcreteType PossiblyQualified) +recordType = + P.sequence + { start = P.Token "{" ExpectingLeftBrace + , separator = P.Token "," ExpectingComma + , end = P.Token "}" ExpectingRightBrace + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( name, ( type__, commentsAfter ) ) -> + --TODO + --{ commentsBefore = commentsBefore + --, item = item + --, commentsAfter = commentsAfter + --} + ( name, type__ ) + ) + |= spacesCommentsAndGreaterIndent + |= typeBinding + , trailing = P.Forbidden + } + |> P.map (Dict.fromList >> ConcreteType.Record) + + +parenthesizedType : Parser_ (ConcreteType PossiblyQualified) +parenthesizedType = P.sequence { start = P.Token "(" ExpectingLeftParen , separator = P.Token "," ExpectingTupleSeparator , end = P.Token ")" ExpectingRightParen - , spaces = ignorablesAndCheckIndent (<) ExpectingIndentation - , item = PP.subExpression 0 config + , spaces = P.succeed () + , item = + P.succeed + (\commentsBefore ( type__, commentsAfter ) -> + --TODO + --{ commentsBefore = commentsBefore + --, item = item + --, commentsAfter = commentsAfter + --} + type__ + ) + |= spacesCommentsAndGreaterIndent + |= P.lazy (\_ -> type_) , trailing = P.Forbidden } - |> located |> P.andThen - (\locatedPattern -> - case Located.unwrap locatedPattern of - [ pattern1, pattern2, pattern3 ] -> - Located.map (\_ -> PTuple3 pattern1 pattern2 pattern3) - locatedPattern - |> P.succeed - |> P.inContext InTuple3 + (\types -> + case types of + [] -> + P.problem ExpectingExpression + + [ type1 ] -> + P.succeed type1 + |> P.inContext InParenthesizedType - [ pattern1, pattern2 ] -> - Located.map (\_ -> PTuple pattern1 pattern2) - locatedPattern + [ type1, type2 ] -> + ConcreteType.Tuple type1 type2 |> P.succeed |> P.inContext InTuple - [ pattern_ ] -> - P.succeed pattern_ + [ type1, type2, type3 ] -> + ConcreteType.Tuple3 type1 type2 type3 + |> P.succeed + |> P.inContext InTuple3 _ -> P.problem ExpectingMaxThreeTuple ) +{-| Examples: + + - Maybe a + - List Int + - Result Foo.Bar + - Browser.Position Int + - MyModule.MyDataStructure + +-} +userDefinedType : Parser_ ( ConcreteType PossiblyQualified, List Comment ) +userDefinedType = + P.succeed + (\( modules, name ) ( args, commentsAfter ) -> + ( processUserDefinedType modules name args + , commentsAfter + ) + ) + |= qualifiersAndTypeName + |= (spacesAndComments + |> P.andThen userDefinedTypeArgs + ) + |> P.inContext InUserDefinedType + + +qualifiersAndTypeName : Parser_ ( List ModuleName, String ) +qualifiersAndTypeName = + P.loop [] qualifiersAndTypeNameHelp + |> P.inContext InQualifiersAndTypeName + + +qualifiersAndTypeNameHelp : List String -> Parser_ (P.Step (List String) ( List ModuleName, String )) +qualifiersAndTypeNameHelp acc = + P.succeed + (\moduleOrTypeName continue -> + if continue then + P.Loop (moduleOrTypeName :: acc) + + else + P.Done ( List.reverse acc, moduleOrTypeName ) + ) + |= moduleNameWithoutDots + |= P.oneOf + [ P.symbol (P.Token "." ExpectingQualifiedVarNameDot) + |> P.map (\_ -> True) + , P.succeed False + ] + + +userDefinedTypeArgs : List Comment -> Parser_ ( List (ConcreteType PossiblyQualified), List Comment ) +userDefinedTypeArgs commentsBefore = + P.loop ( [], commentsBefore ) userDefinedTypeArgsHelp + + +userDefinedTypeArgsHelp : ( List (ConcreteType PossiblyQualified), List Comment ) -> Parser_ (P.Step ( List (ConcreteType PossiblyQualified), List Comment ) ( List (ConcreteType PossiblyQualified), List Comment )) +userDefinedTypeArgsHelp ( acc, commentsBefore {- TODO -} ) = + P.oneOf + [ {- consider `x : Foo.Bar\nx = 1` vs `x : Foo.Bar\n x` + + Right now we've chomped the `Bar` and we may want to chomp + some arguments. + + If the current indent if greater or equal the current column, + it means that it will start the `x = ...` declaration. + This first parser will succeed and return the accumulation. + + -} + checkIndent (>=) ExpectingIndentation + |> P.map (\_ -> P.Done ( List.reverse acc, commentsBefore )) + , {- The next thing to try is a continuation of the type + annotation - custom type args! + -} + withCommentsAfter userDefinedTypeArg + |> P.map + (\( arg, commentsAfter ) -> + P.Loop ( arg :: acc, commentsAfter ) + ) + , {- If the subExpression fails, can be a `->` so we are done with this type -} + P.Done ( List.reverse acc, commentsBefore ) + |> P.succeed + ] + + +userDefinedTypeArg : Parser_ (ConcreteType PossiblyQualified) +userDefinedTypeArg = + P.oneOf + [ unitType + , varType + , recordType + , parenthesizedType + , qualifiersAndTypeName + |> P.map + (\( modules, name ) -> + processUserDefinedType modules name [] + ) + ] + + + +--TODO: Convert on next stage? + + +processUserDefinedType : List String -> String -> List (ConcreteType PossiblyQualified) -> ConcreteType PossiblyQualified +processUserDefinedType modules name args = + let + userDefinedType_ = + ConcreteType.UserDefinedType + { qualifiedness = qualify modules + , name = name + , args = args + } + in + if List.isEmpty modules || modules == [ "Basics" ] then + case args of + [] -> + case name of + "Bool" -> + ConcreteType.Bool + + "Int" -> + ConcreteType.Int + + "Float" -> + ConcreteType.Float + + "Char" -> + ConcreteType.Char + + "String" -> + ConcreteType.String + + _ -> + userDefinedType_ + + [ singleArg ] -> + case name of + "List" -> + ConcreteType.List singleArg + + _ -> + userDefinedType_ + + _ -> + userDefinedType_ + + else + userDefinedType_ + + -- Helpers -spacesOnly : Parser_ () -spacesOnly = +{-| Parse spaces and comments then check the current defined indentation and the +current column. +-} +spacesCommentsAndCheckIndent : (Int -> Int -> Bool) -> ParseProblem -> Parser_ (List Comment) +spacesCommentsAndCheckIndent check error = P.succeed identity - |= P.chompWhile ((==) ' ') + |= spacesAndComments + |. checkIndent check error -newlines : Parser_ () -newlines = - P.chompWhile ((==) '\n') +{-| Parse spaces and comments. +-} +spacesAndComments : Parser_ (List Comment) +spacesAndComments = + P.succeed identity + |. spaces + |= zeroOrMoreWith spaces comment -{-| Parse zero or more ignorables Elm code. +{-| Parse comment. +-} +comment : Parser_ Comment +comment = + P.oneOf + [ P.lineComment (P.Token "--" ExpectingSingleLineCommentStart) + |> P.getChompedString + |> located + |> P.map (\str -> Comment str SingleLine) + , P.multiComment + (P.Token "{-" ExpectingMultiLineCommentStart) + (P.Token "-}" ExpectingMultiLineCommentEnd) + P.Nestable + |> P.getChompedString + |> located + |> P.map (\str -> Comment str MultiLine) + ] + + +{-| Parse zero or more spaces. It will ignore spaces (' ', '\\n' and '\\r') and raise an error if it finds a tab. @@ -1257,8 +1970,8 @@ This possibility of success without consumption is also why wee need the ifProgress helper. It detects if there is no more whitespace to consume. -} -ignorables : Parser_ () -ignorables = +spaces : Parser_ () +spaces = P.loop 0 <| ifProgress <| P.oneOf @@ -1312,25 +2025,6 @@ checkIndent check error = |> P.andThen identity -{-| Fail if current column <= 1 (these are 1-based, so 1 is leftmost.) --} -onlyIndented : Parser_ a -> Parser_ a -onlyIndented parser = - P.succeed identity - |. checkIndent (\_ column -> column > 1) ExpectingIndentation - |= parser - - -{-| Parse ignorable code then check the current defined indentation and the -current column. --} -ignorablesAndCheckIndent : (Int -> Int -> Bool) -> ParseProblem -> Parser_ () -ignorablesAndCheckIndent check error = - P.succeed () - |. ignorables - |. checkIndent check error - - {-| Taken from Punie/elm-parser-extras (original name: `many`), made to work with Parser.Advanced.Parser instead of the simple one. @@ -1338,8 +2032,8 @@ Adapted to behave like \* instead of +. -} zeroOrMoreWith : Parser_ () -> Parser_ a -> Parser_ (List a) -zeroOrMoreWith spaces p = - P.loop [] (zeroOrMoreHelp spaces p) +zeroOrMoreWith spaces_ p = + P.loop [] (zeroOrMoreHelp spaces_ p) {-| Taken from Punie/elm-parser-extras (original name: `many`), made to work with @@ -1349,253 +2043,35 @@ Adapted to behave like \* instead of +. -} zeroOrMoreHelp : Parser_ () -> Parser_ a -> List a -> Parser_ (P.Step (List a) (List a)) -zeroOrMoreHelp spaces p vs = +zeroOrMoreHelp spaces_ p vs = P.oneOf [ P.backtrackable (P.succeed (\v -> P.Loop (v :: vs)) |= p - |. spaces + |. spaces_ ) , P.succeed () |> P.map (always (P.Done (List.reverse vs))) ] -{-| Taken from Punie/elm-parser-extras (original name: `many`), made to work with -Parser.Advanced.Parser instead of the simple one. --} -oneOrMoreWith : Parser_ () -> Parser_ a -> Parser_ (List a) -oneOrMoreWith spaces p = - P.loop [] (oneOrMoreHelp spaces p) +zeroOrMoreWithSpacesAndCommentsInBetween : Parser_ (List Comment -> ( a, List Comment )) -> List Comment -> Parser_ ( List a, List Comment ) +zeroOrMoreWithSpacesAndCommentsInBetween parser commentsBefore = + P.loop ( [], commentsBefore ) (zeroOrMoreWithSpacesAndCommentsInBetweenHelper parser) -{-| Taken from Punie/elm-parser-extras (original name: `many`), made to work with -Parser.Advanced.Parser instead of the simple one. --} -oneOrMoreHelp : Parser_ () -> Parser_ a -> List a -> Parser_ (P.Step (List a) (List a)) -oneOrMoreHelp spaces p vs = +zeroOrMoreWithSpacesAndCommentsInBetweenHelper : Parser_ (List Comment -> ( a, List Comment )) -> ( List a, List Comment ) -> Parser_ (P.Step ( List a, List Comment ) ( List a, List Comment )) +zeroOrMoreWithSpacesAndCommentsInBetweenHelper parser ( acc, commentsBefore ) = P.oneOf - [ P.succeed (\v -> P.Loop (v :: vs)) - |= p - |. spaces - , P.succeed () - |> P.map (always (P.Done (List.reverse vs))) - ] - - -typeAnnotation : Parser_ TypeAnnotation -typeAnnotation = - -- TODO don't support newline without a space afterward... see the commented out tests - P.succeed TypeAnnotation - |= varName - |. P.spaces - |. P.symbol (P.Token ":" ExpectingColon) - |. P.spaces - |= type_ - |> P.inContext InTypeAnnotation - - -type_ : Parser_ (ConcreteType PossiblyQualified) -type_ = - PP.expression - { oneOf = - [ PP.literal varType - , simpleType "Int" ConcreteType.Int - , simpleType "Float" ConcreteType.Float - , simpleType "Char" ConcreteType.Char - , simpleType "String" ConcreteType.String - , simpleType "Bool" ConcreteType.Bool - , simpleType "()" ConcreteType.Unit - , listType - , tupleType - , tuple3Type - , parenthesizedType - , recordType - , userDefinedType - ] - , andThenOneOf = - [ PP.infixRight 1 - (P.token (P.Token "->" ExpectingRightArrow)) - (\from to -> ConcreteType.Function { from = from, to = to }) - ] - , spaces = P.spaces - } - |> P.inContext InType - - -parenthesizedType : TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -parenthesizedType config = - P.succeed identity - |. P.symbol (P.Token "(" ExpectingLeftParen) - |= PP.subExpression 0 config - |. P.symbol (P.Token ")" ExpectingRightParen) - |> P.inContext InParenthesizedType - - -varType : Parser_ (ConcreteType PossiblyQualified) -varType = - varName - |> P.getChompedString - |> P.map ConcreteType.TypeVar - |> P.inContext InTypeVarType - - -simpleType : String -> ConcreteType PossiblyQualified -> TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -simpleType name parsedType config = - PP.constant - (P.keyword (P.Token name (ExpectingSimpleType name))) - parsedType - config - - -listType : TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -listType config = - P.succeed ConcreteType.List - |. P.keyword (P.Token "List" ExpectingListType) - |. spacesOnly - |= PP.subExpression 0 config - - -tupleType : TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -tupleType config = - P.backtrackable - (P.succeed ConcreteType.Tuple - |. P.token (P.Token "(" ExpectingLeftParen) - |. spacesOnly - |= PP.subExpression 0 config - |. spacesOnly - |. P.token (P.Token "," ExpectingComma) - |. spacesOnly - |= PP.subExpression 0 config - |. spacesOnly - |. P.token (P.Token ")" ExpectingRightParen) - ) - - -tuple3Type : TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -tuple3Type config = - P.backtrackable - (P.succeed ConcreteType.Tuple3 - |. P.token (P.Token "(" ExpectingLeftParen) - |. spacesOnly - |= PP.subExpression 0 config - |. spacesOnly - |. P.token (P.Token "," ExpectingComma) - |. spacesOnly - |= PP.subExpression 0 config - |. spacesOnly - |. P.token (P.Token "," ExpectingComma) - |. spacesOnly - |= PP.subExpression 0 config - |. spacesOnly - |. P.token (P.Token ")" ExpectingRightParen) - ) - - -recordType : TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -recordType config = - P.succeed (Dict.fromList >> ConcreteType.Record) - |= P.sequence - { start = P.Token "{" ExpectingLeftBrace - , separator = P.Token "," ExpectingComma - , end = P.Token "}" ExpectingRightBrace - , spaces = spacesOnly -- TODO what about definitions of type aliases etc? - , item = typeBinding config - , trailing = P.Forbidden - } - - -{-| Examples: - - - Maybe a - - List Int - - Result Foo.Bar - - Browser.Position Int - - MyModule.MyDataStructure - --} -userDefinedType : TypeConfig -> Parser_ (ConcreteType PossiblyQualified) -userDefinedType config = - P.succeed - (\( modules, name ) args -> - ConcreteType.UserDefinedType - { qualifiedness = qualify modules - , name = name - , args = args - } - ) - |= qualifiersAndTypeName - |. spacesOnly - |= P.oneOf - [ {- consider `x : Foo.Bar\nx = 1` vs `x : Foo.Bar\n x` - - Right now we've chomped the `Bar` and we want to chomp some - arguments. - - We have to explicitly check whether the next non-newline char is - a space or not. - - If it is, we have a multi-line type annotation - on our hands and the `x` at the end is a type argument. - - If it isn't, we have finished the type annotation - parsing and the argument list for the `Foo.Bar` is empty. - And that's this `oneOf` case! - -} - P.backtrackable - (P.succeed [] - |. spacesOnly - |. checkNextCharIs '\n' ExpectingNewlineAfterTypeAnnotation - |. newlines - |. checkNextCharIsNot ' ' ExpectingNonSpaceAfterTypeAnnotationNewlines - ) - , {- Here, the next thing to parse isn't the `x = ...` declaration - but a continuation of the type annotation - custom type args! - -} - zeroOrMoreWith P.spaces (onlyIndented (PP.subExpression 0 config)) - ] - |> P.inContext InUserDefinedType - - -qualifiersAndTypeName : Parser_ ( List ModuleName, String ) -qualifiersAndTypeName = - P.sequence - { start = P.Token "" (ParseCompilerBug QualifiersStartParserFailed) - , separator = P.Token "." ExpectingQualifiedVarNameDot - , end = P.Token "" (ParseCompilerBug QualifiersEndParserFailed) - , spaces = P.succeed () - , item = moduleNameWithoutDots - , trailing = P.Forbidden - } - |> P.andThen - (\names -> - case List.reverse names of - typeName :: reversedQualifiers -> - P.succeed ( List.reverse reversedQualifiers, typeName ) - - _ -> - P.problem ExpectingTypeName + [ P.succeed + (\fn -> + fn commentsBefore + |> Tuple.mapFirst (\i -> i :: acc) + |> P.Loop ) - |> P.inContext InQualifiersAndTypeName - - -{-| Taken from [dmy/elm-pratt-parser](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt-Advanced#postfix), -made to accept the operator parser result. - -It differs from an _infix_ expression by not having left _and_ right expressions. -It has only a left expression and an operator, eg.: 180º (the degree (`º`) -symbol is the postfix operator). - -It can be used to parse Elm's aliasing expressions, like `{ foo } as bar`, -since only the `{ foo }` is a pattern expression, but we also need the `bar` -string, which is not another expression. - --} -postfix : Int -> P.Parser c x a -> (e -> a -> e) -> PP.Config c x e -> ( Int, e -> P.Parser c x e ) -postfix precedence operator apply _ = - ( precedence - , \left -> P.map (apply left) operator - ) + |= parser + , P.succeed (P.Done ( List.reverse acc, commentsBefore )) + ] shouldLog : String -> Bool @@ -1689,33 +2165,53 @@ simpleLog msg parser = |= P.getOffset -checkNextCharIs : Char -> ParseProblem -> Parser_ () -checkNextCharIs mandatoryChar problem = - checkNextChar ((==) mandatoryChar) problem +checkTooMuchIndentation : String -> Parser_ () +checkTooMuchIndentation firstSucceedString = + checkIndent (\indent col -> col == indent + String.length firstSucceedString) + (TooMuchIndentation firstSucceedString) + + +spacesCommentsAndGreaterIndent : Parser_ (List Comment) +spacesCommentsAndGreaterIndent = + spacesCommentsAndCheckIndent (<) ExpectingIndentation -checkNextCharIsNot : Char -> ParseProblem -> Parser_ () -checkNextCharIsNot forbiddenChar problem = - checkNextChar ((/=) forbiddenChar) problem +{-| Taken from [dmy/elm-pratt-parser](https://package.elm-lang.org/packages/dmy/elm-pratt-parser/latest/Pratt-Advanced#postfix), +made to accept the operator parser result. +It differs from an _infix_ expression by not having left _and_ right expressions. +It has only a left expression and an operator, eg.: 180º (the degree (`º`) +symbol is the postfix operator). + +It can be used to parse Elm's aliasing expressions, like `{ foo } as bar`, +since only the `{ foo }` is a pattern expression, but we also need the `bar` +string, which is not another expression. -{-| Likely very inefficient... -} -checkNextChar : (Char -> Bool) -> ParseProblem -> Parser_ () -checkNextChar charPredicate problem = - P.succeed - (\source offset -> - case String.uncons (String.slice offset (offset + 1) source) of - Nothing -> - P.problem problem +postfixWithOperatorResult : Int -> Parser c x a -> (e -> a -> e) -> PP.Config c x e -> ( Int, e -> Parser c x e ) +postfixWithOperatorResult precedence operator apply _ = + ( precedence + , \left -> P.map (apply left) operator + ) - Just ( nextChar, _ ) -> - if charPredicate nextChar then - P.succeed () - else - P.problem problem - ) - |= P.getSource - |= P.getOffset - |> P.andThen identity +infixLeftWithOperatorResult : Int -> Parser c x a -> (e -> a -> e -> e) -> PP.Config c x e -> ( Int, e -> Parser c x e ) +infixLeftWithOperatorResult precedence = + infixWithOperatorResultHelp ( precedence, precedence ) + + +infixRightWithOperatorResult : Int -> Parser c x a -> (e -> a -> e -> e) -> PP.Config c x e -> ( Int, e -> Parser c x e ) +infixRightWithOperatorResult precedence = + -- To get right associativity, we use (precedence - 1) for the + -- right precedence. + infixWithOperatorResultHelp ( precedence, precedence - 1 ) + + +infixWithOperatorResultHelp : ( Int, Int ) -> Parser c x a -> (e -> a -> e -> e) -> PP.Config c x e -> ( Int, e -> Parser c x e ) +infixWithOperatorResultHelp ( leftPrecedence, rightPrecedence ) operator apply config = + ( leftPrecedence + , \left -> + P.succeed (apply left) + |= operator + |= PP.subExpression rightPrecedence config + ) diff --git a/tests/DesugarTest.elm b/tests/DesugarTest.elm index ff05d99e..86b10d5a 100644 --- a/tests/DesugarTest.elm +++ b/tests/DesugarTest.elm @@ -5,6 +5,7 @@ import Elm.AST.Canonical as Canonical import Elm.AST.Canonical.Unwrapped as CanonicalU import Elm.AST.Frontend as Frontend import Elm.Compiler.Error as Error exposing (DesugarError) +import Elm.Data.Binding as Binding import Elm.Data.Declaration as Declaration exposing (Declaration) import Elm.Data.Exposing as Exposing import Elm.Data.Import exposing (Import) @@ -139,11 +140,10 @@ desugarTest = bRegion = { start = { row = 3, col = 3 }, end = { row = 4, col = 4 } } in - [ { name = "aaa", body = Located.located aRegion Frontend.Unit } - , { name = "aaa", body = Located.located bRegion Frontend.Unit } + [ frontendBinding "aaa" (Located.located aRegion Frontend.Unit) + , frontendBinding "aaa" (Located.located bRegion Frontend.Unit) ] - |> Frontend.Record - |> located + |> frontendRecord |> Desugar.desugarExpr Dict.empty (moduleFromName "A") |> mapUnwrap |> Expect.equal @@ -158,18 +158,49 @@ desugarTest = ] +frontendBinding : String -> Frontend.LocatedExpr -> Binding.Commented Frontend.LocatedExpr +frontendBinding name body = + { name = name + , commentsAfterName = [] + , commentsBeforeBody = [] + , body = body + } + + +frontendRecord : List (Binding.Commented Frontend.LocatedExpr) -> Frontend.LocatedExpr +frontendRecord bindings = + located <| + Frontend.Record <| + List.map + (\b -> + { commentsBefore = [] + , binding = b + , commentsAfter = [] + } + ) + bindings + + {-| `frontendLambda "a" "b"` builds `\a b -> a + b`. -} frontendLambda : String -> String -> Frontend.LocatedExpr frontendLambda arg1 arg2 = located <| Frontend.Lambda - { arguments = [ arg1, arg2 ] + { arguments = + [ { commentsBefore = [], argument = arg1 } + , { commentsBefore = [], argument = arg2 } + ] + , commentsAfterArguments = [] + , commentsBeforeBody = [] , body = located <| Frontend.Plus - (located <| Frontend.Argument arg1) - (located <| Frontend.Argument arg2) + { left = located <| Frontend.Argument arg1 + , commentsAfterLeft = [] + , commentsBeforeRight = [] + , right = located <| Frontend.Argument arg2 + } } @@ -254,7 +285,10 @@ buildExpectedResult ( moduleName, varName ) = importFromName : ModuleName -> ( ModuleName, Import ) importFromName moduleName = ( moduleName - , { moduleName = moduleName + , { commentsBefore = [] + , commentsBeforeModuleName = [] + , moduleName = moduleName + , commentsAfterModuleName = [] , as_ = Nothing , exposing_ = Nothing } @@ -264,13 +298,37 @@ importFromName moduleName = exposingValuesInImport : List VarName -> ( ModuleName, Import ) -> ( ModuleName, Import ) exposingValuesInImport vars ( moduleName, import_ ) = ( moduleName - , { import_ | exposing_ = Just <| Exposing.ExposingSome <| List.map Exposing.ExposedValue vars } + , { import_ + | exposing_ = + Just + { commentsBeforeExposing = [] + , exposing_ = + Exposing.ExposingSome <| + List.map + (\var_ -> + { commentsBefore = [] + , item = Exposing.ExposedValue var_ + , commentsAfter = [] + } + ) + vars + } + } ) as_ : ModuleName -> ( ModuleName, Import ) -> ( ModuleName, Import ) as_ alias_ ( moduleName, import_ ) = - ( moduleName, { import_ | as_ = Just alias_ } ) + ( moduleName + , { import_ + | as_ = + Just + { commentsBeforeAs = [] + , as_ = alias_ + , commentsAfterAs = [] + } + } + ) moduleFromName : ModuleName -> Module expr ann qual @@ -281,6 +339,8 @@ moduleFromName name = , declarations = Dict.empty , type_ = Module.PlainModule , exposing_ = Exposing.ExposingSome [] + , startComments = [] + , endComments = [] } @@ -300,9 +360,11 @@ addDeclaration varName module_ = decl = { module_ = module_.name , name = varName + , commentsBefore = [] , body = Declaration.Value { typeAnnotation = Nothing + , commentsAfterTypeAnnotation = [] , expression = located <| Frontend.Int 42 } } @@ -324,7 +386,18 @@ addDeclarations varNames module_ = exposingValuesInModule : List VarName -> Module expr ann qual -> Module expr ann qual exposingValuesInModule varNames exposable = - { exposable | exposing_ = Exposing.ExposingSome (List.map Exposing.ExposedValue varNames) } + { exposable + | exposing_ = + Exposing.ExposingSome <| + List.map + (\varName -> + { commentsBefore = [] + , item = Exposing.ExposedValue varName + , commentsAfter = [] + } + ) + varNames + } diff --git a/tests/EmitJsonTest.elm b/tests/EmitJsonTest.elm index 1bb2e06c..ca8619a5 100644 --- a/tests/EmitJsonTest.elm +++ b/tests/EmitJsonTest.elm @@ -223,9 +223,11 @@ json = [ ( "simple" , { module_ = "Foo" , name = "bar" + , commentsBefore = [] , body = Value { typeAnnotation = Nothing + , commentsAfterTypeAnnotation = [] , expression = typedInt 1 } } diff --git a/tests/EmitTest.elm b/tests/EmitTest.elm index 49ad9b63..40eac27f 100644 --- a/tests/EmitTest.elm +++ b/tests/EmitTest.elm @@ -320,9 +320,11 @@ javascript = [ ( "simple" , { module_ = "Foo" , name = "bar" + , commentsBefore = [] , body = Value { typeAnnotation = Nothing + , commentsAfterTypeAnnotation = [] , expression = typedInt 1 } } diff --git a/tests/ParserTest.elm b/tests/ParserTest.elm index 0d415110..fd14b1f2 100644 --- a/tests/ParserTest.elm +++ b/tests/ParserTest.elm @@ -1,12 +1,13 @@ module ParserTest exposing ( customTypeDeclaration , exposingList - , expr + , expr + --, exprComments + , imports , moduleDeclaration , moduleName , typeAliasDeclaration - , typeAnnotation , type_ , valueDeclaration ) @@ -15,8 +16,10 @@ import Dict import Elm.AST.Frontend as Frontend import Elm.AST.Frontend.Unwrapped exposing (Expr(..), Pattern(..)) import Elm.Compiler.Error exposing (ParseContext, ParseProblem) +import Elm.Data.Comment exposing (Comment) import Elm.Data.Declaration as Declaration exposing (DeclarationBody) import Elm.Data.Exposing exposing (ExposedItem(..), Exposing(..)) +import Elm.Data.Located as Located import Elm.Data.Module exposing (ModuleType(..)) import Elm.Data.Qualifiedness exposing (PossiblyQualified(..)) import Elm.Data.Type.Concrete as ConcreteType exposing (ConcreteType) @@ -63,7 +66,7 @@ moduleDeclaration = , Just ( PlainModule, "Foo", ExposingAll ) ) , ( "allows a newline between the module name and the `exposing` keyword" - , "module Foo\nexposing (..)" + , "module Foo\n exposing (..)" , Just ( PlainModule, "Foo", ExposingAll ) ) , ( "allows multiple spaces between the `exposing` keyword and the exposing list" @@ -71,7 +74,7 @@ moduleDeclaration = , Just ( PlainModule, "Foo", ExposingAll ) ) , ( "allows a newline between the `exposing` keyword and the exposing list" - , "module Foo exposing\n(..)" + , "module Foo exposing\n (..)" , Just ( PlainModule, "Foo", ExposingAll ) ) , ( "doesn't work without something after the `exposing` keyword" @@ -133,7 +136,7 @@ exposingList = , ( "works with spaces between items" , "(foo, bar)" , Just - (ExposingSome + (exposingSome [ ExposedValue "foo" , ExposedValue "bar" ] @@ -142,7 +145,7 @@ exposingList = , ( "works with even more spaces between items" , "(foo , bar)" , Just - (ExposingSome + (exposingSome [ ExposedValue "foo" , ExposedValue "bar" ] @@ -151,7 +154,7 @@ exposingList = , ( "works with mixed values" , "(foo, Bar, Baz(..))" , Just - (ExposingSome + (exposingSome [ ExposedValue "foo" , ExposedType "Bar" , ExposedTypeAndAllConstructors "Baz" @@ -159,9 +162,9 @@ exposingList = ) ) , ( "allows for newline" - , "(foo\n,bar)" + , "(foo\n ,bar)" , Just - (ExposingSome + (exposingSome [ ExposedValue "foo" , ExposedValue "bar" ] @@ -173,7 +176,7 @@ exposingList = (List.map runTest [ ( "works with a value" , "(foo)" - , Just (ExposingSome [ ExposedValue "foo" ]) + , Just (exposingSome [ ExposedValue "foo" ]) ) ] ) @@ -181,7 +184,7 @@ exposingList = (List.map runTest [ ( "works with exposed type" , "(Foo)" - , Just (ExposingSome [ ExposedType "Foo" ]) + , Just (exposingSome [ ExposedType "Foo" ]) ) ] ) @@ -189,7 +192,7 @@ exposingList = (List.map runTest [ ( "works with exposed type and all constructors" , "(Foo(..))" - , Just (ExposingSome [ ExposedTypeAndAllConstructors "Foo" ]) + , Just (exposingSome [ ExposedTypeAndAllConstructors "Foo" ]) ) , ( "doesn't allow spaces between the module name and the double period list" , "(Foo (..))" @@ -216,8 +219,18 @@ imports = test description <| \() -> input - |> P.run Stage.Parse.Parser.imports + |> P.run (Stage.Parse.Parser.imports []) |> Result.toMaybe + |> Maybe.map + (Tuple.first + >> Dict.map + (\_ import_ -> + { moduleName = import_.moduleName + , as_ = Maybe.map .as_ import_.as_ + , exposing_ = Maybe.map .exposing_ import_.exposing_ + } + ) + ) |> Expect.equal output in describe "Stage.Parse.Parser.imports" @@ -269,13 +282,13 @@ imports = ) ) , ( "allows for multiple newlines between imports" - , "import Foo\n\nimport Bar" + , "import Foo exposing (..)\n\nimport Bar" , Just (Dict.fromList [ ( "Foo" , { moduleName = "Foo" , as_ = Nothing - , exposing_ = Nothing + , exposing_ = Just ExposingAll } ) , ( "Bar" @@ -291,6 +304,10 @@ imports = , "import foo" , Nothing ) + , ( "doesn't allow indentation" + , "import Foo\n import Bar" + , Nothing + ) ] ) , describe "simple" @@ -325,6 +342,10 @@ imports = ] ) ) + , ( "doesn't work without somethign after as" + , "import Foo as" + , Nothing + ) , ( "doesn't work with lowercase alias" , "import Foo as f" , Nothing @@ -345,13 +366,12 @@ imports = , { moduleName = "Foo" , as_ = Nothing , exposing_ = - Just - (ExposingSome - [ ExposedValue "bar" - , ExposedType "Baz" - , ExposedTypeAndAllConstructors "Quux" - ] - ) + [ ExposedValue "bar" + , ExposedType "Baz" + , ExposedTypeAndAllConstructors "Quux" + ] + |> exposingSome + |> Just } ) ] @@ -366,6 +386,18 @@ imports = ] +exposingSome : List ExposedItem -> Exposing +exposingSome = + List.map + (\i -> + { commentsBefore = [] + , item = i + , commentsAfter = [] + } + ) + >> ExposingSome + + moduleName : Test moduleName = let @@ -434,7 +466,7 @@ expr = \() -> input |> P.run Stage.Parse.Parser.expr - |> Result.map Frontend.unwrap + |> Result.map (Tuple.first >> Frontend.unwrap) |> expectEqualParseResult input output in describe "Stage.Parse.Parser.expr" @@ -804,6 +836,48 @@ expr = } ) ) + , ( "binding must be indented" + , "let\nx = 1\nin\n 2" + , Nothing + ) + , ( "two bindings" + , "let\n x = 1\n y = 2\nin\n 2" + , Just + (Let + { bindings = + [ { body = Int 1, name = "x" } + , { body = Int 2, name = "y" } + ] + , body = Int 2 + } + ) + ) + , ( "bindings name must be aligned" + , "let\n x = 1\n y = 2\nin\n 2" + , Nothing + ) + , ( "'in' can have any indentation" + , "let\n x = 1\n in\n 2" + , Just + (Let + { bindings = [ { body = Int 1, name = "x" } ] + , body = Int 2 + } + ) + ) + , ( "binding body must be more indented than the name" + , "let\n x =\n 1\nin\n 2" + , Just + (Let + { bindings = [ { body = Int 1, name = "x" } ] + , body = Int 2 + } + ) + ) + , ( "binding body equal indented with the name" + , "let\n x =\n 1\nin\n 2" + , Nothing + ) ] ) , ( "list" @@ -975,9 +1049,14 @@ expr = , [ ( "simple case" , "case True of _->True" , Just - (Case (Bool True) - [ { pattern = PAnything, body = Bool True } - ] + (Case + { test = Bool True + , branches = + [ { pattern = PAnything + , body = Bool True + } + ] + } ) ) , ( "multiline case" @@ -988,12 +1067,16 @@ expr = _ -> False """ |> String.unindent + |> String.trim , Just - (Case (Int 21) - [ { pattern = PInt 31, body = Bool True } - , { pattern = PInt 5, body = Bool True } - , { pattern = PAnything, body = Bool False } - ] + (Case + { test = Int 21 + , branches = + [ { pattern = PInt 31, body = Bool True } + , { pattern = PInt 5, body = Bool True } + , { pattern = PAnything, body = Bool False } + ] + } ) ) , ( "complex case" @@ -1007,39 +1090,46 @@ expr = False [_, 45, (67.7)] -> False - fst :: snd :: tail -> + fst :: snd as snd :: tail -> False ({ count } as alias1) as alias2 -> False """ |> String.unindent + |> String.trim , Just - (Case (Var { name = "arg", qualifiedness = PossiblyQualified Nothing }) - [ { pattern = PTuple (PChar 'c') (PInt 23) - , body = Bool True - } - , { pattern = PString "string", body = Bool True } - , { pattern = - PTuple - (PTuple (PVar "arg1") (PVar "arg2")) - (PFloat 435.4) - , body = Bool False - } - , { pattern = PList [ PAnything, PInt 45, PFloat 67.7 ] - , body = Bool False - } - , { pattern = - PCons (PVar "fst") - (PCons (PVar "snd") (PVar "tail")) - , body = Bool False - } - , { pattern = - PAlias - (PAlias (PRecord [ "count" ]) "alias1") - "alias2" - , body = Bool False - } - ] + (Case + { test = Var { name = "arg", qualifiedness = PossiblyQualified Nothing } + , branches = + [ { pattern = PTuple (PChar 'c') (PInt 23) + , body = Bool True + } + , { pattern = PString "string", body = Bool True } + , { pattern = + PTuple + (PTuple (PVar "arg1") (PVar "arg2")) + (PFloat 435.4) + , body = Bool False + } + , { pattern = PList [ PAnything, PInt 45, PFloat 67.7 ] + , body = Bool False + } + , { pattern = + PCons (PVar "fst") + (PCons + (PAlias (PVar "snd") "snd") + (PVar "tail") + ) + , body = Bool False + } + , { pattern = + PAlias + (PAlias (PRecord [ "count" ]) "alias1") + "alias2" + , body = Bool False + } + ] + } ) ) ] @@ -1048,6 +1138,82 @@ expr = ) + +--exprComments : Test +--exprComments = +-- let +-- runSection ( description, tests ) = +-- describe description +-- (List.map runTest tests) +-- runTest ( description, input, output ) = +-- test description <| +-- \() -> +-- input +-- |> P.run Stage.Parse.Parser.expr +-- |> Result.toMaybe +-- |> Maybe.map +-- (Tuple.first +-- >> .comments +-- >> List.map +-- (.content +-- >> Located.unwrap +-- ) +-- -- Comments are automatically reversed +-- -- when added to Module in Parser.module_ +-- >> List.reverse +-- ) +-- |> Expect.equal output +-- in +-- describe "Stage.Parse.Parser.expr comments" +-- (List.map runSection +-- [ ( "lambda comments" +-- , [ ( "accepts comments anywhere" +-- , "\\x {- ML1 -} y -> {- ML2 -} x {- ML3 -} + {- ML4 -} 1 -- SL1" +-- , Just [ "{- ML1 -}", "{- ML2 -}", "{- ML3 -}", "{- ML4 -}", "-- SL1" ] +-- ) +-- ] +-- ) +-- , ( "case comments" +-- , [ ( "accepts comments anywhere" +-- , """ +-- case -- SL1 +-- arg -- SL2 +-- of -- SL3 +-- ('c', 23) -> -- SL4 +-- True -- SL5 +-- ("string") {- ML1 -} -> +-- True +-- ((arg1, arg2), {- ML2 -} 435.4) -> +-- {- ML3 -} False +-- [45, {- ML4 -} (67.7)] -> +-- False +-- fst :: snd {- ML5 -} :: tail -> +-- False +-- { count } {- ML6 -} as {- ML7 -} alias -> +-- False +-- """ +-- |> String.unindent +-- , Just +-- [ "-- SL1" +-- , "-- SL2" +-- , "-- SL3" +-- , "-- SL4" +-- , "-- SL5" +-- , "{- ML1 -}" +-- , "{- ML2 -}" +-- , "{- ML3 -}" +-- , "{- ML4 -}" +-- , "{- ML5 -}" +-- , "{- ML6 -}" +-- , "{- ML7 -}" +-- ] +-- ) +-- ] +-- ) +-- ] +-- ) + + expectEqualParseResult : String -> Maybe a @@ -1118,6 +1284,7 @@ type_ = input |> P.run Stage.Parse.Parser.type_ |> Result.toMaybe + |> Maybe.map Tuple.first |> Expect.equal (Just output) in describe "Stage.Parse.Parser.type_" @@ -1143,11 +1310,40 @@ type_ = } } ) + , ( "multiple-arg function with function as arg" + , "Int -> (Int -> Char) -> Char" + , ConcreteType.Function + { from = ConcreteType.Int + , to = + ConcreteType.Function + { from = + ConcreteType.Function + { from = ConcreteType.Int + , to = ConcreteType.Char + } + , to = ConcreteType.Char + } + } + ) + , ( "multiline multiple-arg function" + , "Int\n ->\n ()\n ->\n Char" + , ConcreteType.Function + { from = ConcreteType.Int + , to = + ConcreteType.Function + { from = ConcreteType.Unit + , to = ConcreteType.Char + } + } + ) , ( "float", "Float", ConcreteType.Float ) , ( "char", "Char", ConcreteType.Char ) , ( "string", "String", ConcreteType.String ) , ( "bool", "Bool", ConcreteType.Bool ) - , ( "list", "List ()", ConcreteType.List ConcreteType.Unit ) + , ( "parenthesized" + , "(Int)" + , ConcreteType.Int + ) , ( "tuple" , "(Int, String)" , ConcreteType.Tuple @@ -1160,6 +1356,14 @@ type_ = ConcreteType.Int ConcreteType.String ) + , ( "tuple with different whitespace 2" + , "( Int , Bool )" + , ConcreteType.Tuple ConcreteType.Int ConcreteType.Bool + ) + , ( "tuple with different whitespace 3" + , "(Int,Bool)" + , ConcreteType.Tuple ConcreteType.Int ConcreteType.Bool + ) , ( "tuple3" , "(Int, String, Bool)" , ConcreteType.Tuple3 @@ -1207,7 +1411,7 @@ type_ = , ConcreteType.Record Dict.empty ) , ( "empty record with whitespace" - , "{ }" + , "{ \n }" , ConcreteType.Record Dict.empty ) , ( "record with one field" @@ -1224,229 +1428,75 @@ type_ = , ( "y", ConcreteType.String ) ] ) + , ( "multiline record" + , "{ x : Int\n , y : String\n }" + , ConcreteType.Record <| + Dict.fromList + [ ( "x", ConcreteType.Int ) + , ( "y", ConcreteType.String ) + ] + ) + , -- TODO List should be just another UserDefinedType + ( "list", "List Int", ConcreteType.List ConcreteType.Int ) + , ( "list 2" + , "List (List ())" + , ConcreteType.List (ConcreteType.List ConcreteType.Unit) + ) + , ( "var" + , "abcde1213" + , ConcreteType.TypeVar "abcde1213" + ) + , -- TODO later do something special about comparable etc! + ( "var special" + , "comparable" + , ConcreteType.TypeVar "comparable" + ) + , ( "user defined type unqualified noargs" + , "MyType" + , ConcreteType.UserDefinedType + { qualifiedness = PossiblyQualified Nothing + , name = "MyType" + , args = [] + } + ) + , ( "user defined type unqualified args" + , "MyType Int Float" + , ConcreteType.UserDefinedType + { qualifiedness = PossiblyQualified Nothing + , name = "MyType" + , args = + [ ConcreteType.Int + , ConcreteType.Float + ] + } + ) + , ( "user defined type qualified noargs" + , "Foo.MyType" + , ConcreteType.UserDefinedType + { qualifiedness = PossiblyQualified (Just "Foo") + , name = "MyType" + , args = [] + } + ) + , ( "user defined type qualified args" + , "Foo.MyType Int Float" + , ConcreteType.UserDefinedType + { qualifiedness = PossiblyQualified (Just "Foo") + , name = "MyType" + , args = + [ ConcreteType.Int + , ConcreteType.Float + ] + } + ) + + -- TODO extensible record + -- TODO parentheses behaviour + -- TODO whitespace behaviour of `->` type (esp. newlines) ] ) -typeAnnotation : Test -typeAnnotation = - let - runTest : ( String, String, Maybe TypeAnnotation ) -> Test - runTest ( description, input, output ) = - test description <| - \() -> - input - |> P.run Stage.Parse.Parser.typeAnnotation - |> Result.toMaybe - |> Expect.equal output - - xInt : TypeAnnotation - xInt = - { varName = "x", type_ = ConcreteType.Int } - in - describe "Stage.Parse.Parser.typeAnnotation" - [ describe "various cases" <| - List.map runTest <| - [ -- TODO extensible record - ( "x int", "x : Int", Just xInt ) - , ( "x int without whitespace", "x:Int", Just xInt ) - , ( "x float", "x : Float", Just { varName = "x", type_ = ConcreteType.Float } ) - , ( "x char", "x : Char", Just { varName = "x", type_ = ConcreteType.Char } ) - , ( "x string", "x : String", Just { varName = "x", type_ = ConcreteType.String } ) - , ( "x unit", "x : ()", Just { varName = "x", type_ = ConcreteType.Unit } ) - , ( "y bool", "y : Bool", Just { varName = "y", type_ = ConcreteType.Bool } ) - , ( "foo tuple" - , "foo : (Int, Bool)" - , Just { varName = "foo", type_ = ConcreteType.Tuple ConcreteType.Int ConcreteType.Bool } - ) - , ( "foo tuple with different whitespace 1" - , "foo : ( Int, Bool )" - , Just { varName = "foo", type_ = ConcreteType.Tuple ConcreteType.Int ConcreteType.Bool } - ) - , ( "foo tuple with different whitespace 2" - , "foo : ( Int , Bool )" - , Just { varName = "foo", type_ = ConcreteType.Tuple ConcreteType.Int ConcreteType.Bool } - ) - , ( "foo tuple with different whitespace 3" - , "foo : (Int,Bool)" - , Just { varName = "foo", type_ = ConcreteType.Tuple ConcreteType.Int ConcreteType.Bool } - ) - , ( "foo tuple3" - , "foo : (Int, Bool, String)" - , Just - { varName = "foo" - , type_ = - ConcreteType.Tuple3 - ConcreteType.Int - ConcreteType.Bool - ConcreteType.String - } - ) - , -- TODO List should be just another UserDefinedType - ( "x list", "x : List Int", Just { varName = "x", type_ = ConcreteType.List ConcreteType.Int } ) - , ( "x list 2" - , "x : List (List ())" - , Just - { varName = "x" - , type_ = ConcreteType.List (ConcreteType.List ConcreteType.Unit) - } - ) - , ( "x record", "x : {}", Just { varName = "x", type_ = ConcreteType.Record Dict.empty } ) - , ( "x record 2" - , "x : {foo : Int}" - , Just - { varName = "x" - , type_ = - ConcreteType.Record - (Dict.fromList [ ( "foo", ConcreteType.Int ) ]) - } - ) - , ( "x record 3" - , "x : {foo : Int, bar : ()}" - , Just - { varName = "x" - , type_ = - ConcreteType.Record - (Dict.fromList - [ ( "foo", ConcreteType.Int ) - , ( "bar", ConcreteType.Unit ) - ] - ) - } - ) - , ( "x var 2" - , "x : abcde1213" - , Just { varName = "x", type_ = ConcreteType.TypeVar "abcde1213" } - ) - , -- TODO later do something special about comparable etc! - ( "x var special" - , "x : comparable" - , Just { varName = "x", type_ = ConcreteType.TypeVar "comparable" } - ) - , ( "x function" - , "x : Int -> Bool" - , Just - { varName = "x" - , type_ = - ConcreteType.Function - { from = ConcreteType.Int - , to = ConcreteType.Bool - } - } - ) - , ( "x 2-arg function" - , "x : Int -> Bool -> String" - , Just - { varName = "x" - , type_ = - ConcreteType.Function - { from = ConcreteType.Int - , to = - ConcreteType.Function - { from = ConcreteType.Bool - , to = ConcreteType.String - } - } - } - ) - , ( "x user defined type unqualified noargs" - , "x : MyType" - , Just - { varName = "x" - , type_ = - ConcreteType.UserDefinedType - { qualifiedness = PossiblyQualified Nothing - , name = "MyType" - , args = [] - } - } - ) - , ( "x user defined type unqualified args" - , "x : MyType Int Float" - , Just - { varName = "x" - , type_ = - ConcreteType.UserDefinedType - { qualifiedness = PossiblyQualified Nothing - , name = "MyType" - , args = - [ ConcreteType.Int - , ConcreteType.Float - ] - } - } - ) - , ( "x user defined type qualified noargs" - , "x : Foo.MyType" - , Just - { varName = "x" - , type_ = - ConcreteType.UserDefinedType - { qualifiedness = PossiblyQualified (Just "Foo") - , name = "MyType" - , args = [] - } - } - ) - , ( "x user defined type qualified args" - , "x : Foo.MyType Int Float" - , Just - { varName = "x" - , type_ = - ConcreteType.UserDefinedType - { qualifiedness = PossiblyQualified (Just "Foo") - , name = "MyType" - , args = - [ ConcreteType.Int - , ConcreteType.Float - ] - } - } - ) - ] - , describe "whitespace behaviour" - (List.map runTest - [ ( "canonical format", "x : Int", Just xInt ) - , ( "no spaces", "x:Int", Just xInt ) - , ( "multiple spaces before", "x : Int", Just xInt ) - , ( "multiple spaces after", "x : Int", Just xInt ) - , ( "newline and space before", "x\n : Int", Just xInt ) - , ( "newline and space after", "x :\n Int", Just xInt ) - , ( "newline and space near UserDefinedType args" - , "x : Foo.Bar\n a" - , Just - { varName = "x" - , type_ = - ConcreteType.UserDefinedType - { qualifiedness = PossiblyQualified (Just "Foo") - , name = "Bar" - , args = [ ConcreteType.TypeVar "a" ] - } - } - ) - , ( "newline but not a space near UserDefinedType args means the rest is ignored" - , "x : Foo.Bar\na" - , Just - { varName = "x" - , type_ = - ConcreteType.UserDefinedType - { qualifiedness = PossiblyQualified (Just "Foo") - , name = "Bar" - , args = [] - } - } - ) - - -- TODO , ( "newline before", "x\n: Int", Nothing ) - -- TODO , ( "newline after", "x :\nInt", Nothing ) - ] - ) - - -- TODO parentheses behaviour - -- TODO whitespace behaviour of `->` type (esp. newlines) - ] - - valueDeclaration : Test valueDeclaration = let @@ -1455,14 +1505,16 @@ valueDeclaration = test description <| \() -> input - |> P.run Stage.Parse.Parser.valueDeclaration + |> P.run (Stage.Parse.Parser.valueDeclaration Nothing) |> Result.toMaybe |> Maybe.map - (Tuple.mapSecond - (Declaration.mapBody + (\( name_, body, _ ) -> + ( name_ + , Declaration.mapBody Frontend.unwrap identity identity + body ) ) |> Expect.equal output @@ -1474,8 +1526,9 @@ valueDeclaration = , Just ( "x" , Declaration.Value - { expression = Unit - , typeAnnotation = Nothing + { typeAnnotation = Nothing + , commentsAfterTypeAnnotation = [] + , expression = Unit } ) ) @@ -1484,18 +1537,60 @@ valueDeclaration = , Just ( "y" , Declaration.Value - { expression = Unit - , typeAnnotation = + { typeAnnotation = Just { varName = "y" + , commentsAfterVarName = [] + , commentsBeforeType = [] , type_ = ConcreteType.Unit } + , commentsAfterTypeAnnotation = [] + , expression = Unit } ) ) ] + +--, describe "whitespace behaviour" +-- (List.map runTest +-- [ ( "canonical format", "x : Int", Just xInt ) +-- , ( "no spaces", "x:Int", Just xInt ) +-- , ( "multiple spaces before", "x : Int", Just xInt ) +-- , ( "multiple spaces after", "x : Int", Just xInt ) +-- , ( "newline and space before", "x\n : Int", Just xInt ) +-- , ( "newline and space after", "x :\n Int", Just xInt ) +-- , ( "newline and space near UserDefinedType args" +-- , "x : Foo.Bar\n a" +-- , Just +-- { varName = "x" +-- , type_ = +-- ConcreteType.UserDefinedType +-- { qualifiedness = PossiblyQualified (Just "Foo") +-- , name = "Bar" +-- , args = [ ConcreteType.TypeVar "a" ] +-- } +-- } +-- ) +-- , ( "newline but not a space near UserDefinedType args means the rest is ignored" +-- , "x : Foo.Bar\na" +-- , Just +-- { varName = "x" +-- , type_ = +-- ConcreteType.UserDefinedType +-- { qualifiedness = PossiblyQualified (Just "Foo") +-- , name = "Bar" +-- , args = [] +-- } +-- } +-- ) +-- , ( "newline before", "x\n: Int", Nothing ) +-- , ( "newline after", "x :\nInt", Nothing ) +-- ] +-- ) + + typeAliasDeclaration : Test typeAliasDeclaration = let @@ -1506,6 +1601,8 @@ typeAliasDeclaration = input |> P.run Stage.Parse.Parser.typeAliasDeclaration |> Result.toMaybe + |> Maybe.map + (\( name_, type__, _ ) -> ( name_, type__ )) |> Expect.equal output in describe "Stage.Parse.Parser.typeAliasDeclaration" <| @@ -1591,6 +1688,7 @@ customTypeDeclaration = input |> P.run Stage.Parse.Parser.customTypeDeclaration |> Result.toMaybe + |> Maybe.map (\( name_, decls, _ ) -> ( name_, decls )) |> Expect.equal output in describe "Stage.Parse.Parser.customTypeDeclaration" <| @@ -1697,4 +1795,58 @@ customTypeDeclaration = } ) ) + , ( "doesnt confuse value declaration with parameter" + , "type Foo a = Bar a \nx = 2" + , Just + ( "Foo" + , Declaration.CustomType + { parameters = [ "a" ] + , constructors = + ( { name = "Bar" + , arguments = [ ConcreteType.TypeVar "a" ] + } + , [] + ) + } + ) + ) + , ( "doesnt accept unested arrow" + , "type Foo a -> b = Bar a" + , Nothing + ) + , ( "doesnt accept unested arrow 2" + , "type Foo = Bar a -> b" + , Just + ( "Foo" + , Declaration.CustomType + { constructors = + ( { arguments = [ ConcreteType.TypeVar "a" ] + , name = "Bar" + } + , [] + ) + , parameters = [] + } + ) + ) + , ( "accept nested arrow" + , "type Foo a b = Bar (a -> b)" + , Just + ( "Foo" + , Declaration.CustomType + { constructors = + ( { arguments = + [ ConcreteType.Function + { from = ConcreteType.TypeVar "a" + , to = ConcreteType.TypeVar "b" + } + ] + , name = "Bar" + } + , [] + ) + , parameters = [ "a", "b" ] + } + ) + ) ]