{-# OPTIONS_GHC -Wall #-} {-# LANGUAGE OverloadedStrings #-} module Reporting.Error.Syntax ( Error(..) , ParseError(..) , Problem(..), Theory(..) , EscapeProblem(..) , ContextStack, Context(..) , BadOp(..), Next(..) , toReport ) where import qualified Data.Set as Set import qualified Data.Text as Text import Data.Text (Text) import qualified Reporting.Render.Type as RenderType import qualified Reporting.Region as R import qualified Reporting.Report as Report import qualified Reporting.Helpers as Help import Reporting.Helpers ((<>), dullyellow, green, hsep, i2t, reflowParagraph, text) -- ALL SYNTAX ERRORS data Error = Parse (Maybe R.Region) Problem | BadFunctionName Int | BadPattern Text | CommentOnNothing | SettingsOnNormalModule | SettingsOnPortModule | DuplicateSettingOnEffectModule Text | BadSettingOnEffectModule Text | NoSettingsOnEffectModule | MissingManagerOnEffectModule Text | UnexpectedPort Text | TypeWithoutDefinition Text | DuplicateArgument Text Text | DuplicateFieldName R.Region Text | DuplicateValueDeclaration Text | DuplicateTypeDeclaration Text | DuplicateDefinition Text | UnboundTypeVarsInUnion Text [Text] [Text] | UnboundTypeVarsInAlias Text [Text] [Text] | UnusedTypeVarsInAlias Text [Text] [Text] | MessyTypeVarsInAlias Text [Text] [Text] [Text] -- PARSE ERRORS data ParseError = ParseError !Int !Int !Problem data Problem = Tab | EndOfFile_Comment | EndOfFile_Shader | EndOfFile_String | EndOfFile_MultiString | EndOfFile_Char | NewLineInString | NewLineInChar | BadEscape Int EscapeProblem | BadChar Int | BadNumberDot | BadNumberEnd | BadNumberExp | BadNumberHex | BadNumberZero | BadShader Text | BadUnderscore Char | BadOp BadOp ContextStack | Theories ContextStack [Theory] data EscapeProblem = UnknownEscape | UnicodeSyntax | UnicodeRange | UnicodeLength Int Text data BadOp = HasType | Equals | Arrow | Pipe | Dot data Theory = Keyword Text | Symbol Text | Expecting Next | LowVar | CapVar | InfixOp | Digit | BadSpace deriving (Eq, Ord) data Next = Decl | Expr | AfterOpExpr Text | ElseBranch | Arg | Pattern | Type | Listing | Exposing deriving (Eq, Ord) type ContextStack = [(Context, R.Position)] data Context = ExprIf | ExprLet | ExprFunc | ExprCase | ExprList | ExprTuple | ExprRecord ---------------- | Definition Text | Annotation Text ---------------- | TypeTuple | TypeRecord ---------------- | PatternList | PatternTuple | PatternRecord ---------------- | Module | Import | TypeUnion | TypeAlias | Infix | Port deriving (Eq, Ord, Show) -- TO REPORT toReport :: RenderType.Localizer -> Error -> Report.Report toReport _localizer err = case err of Parse subRegion problem -> problemToReport subRegion problem BadFunctionName arity -> Report.report "BAD FUNCTION DEFINITION" Nothing "Complex patterns cannot be used as function names." ( reflowParagraph $ "This function definition has " <> i2t arity <> " arguments, but instead\ \ of a normal name like `add` or `reverse` it has a pattern. There is no\ \ way to \"deconstruct\" a function with pattern matching, so this needs to\ \ be changed to a normal name." ) BadPattern name -> Report.report "BAD PATTERN" Nothing ("The free variable `" <> name <> "` appears more than once in this pattern.") ( Help.stack [ text "Rename the variables so there are no duplicates." , reflowParagraph $ "In Elm, pattern matching works by binding these free variables to subsections\ \ of the matching value. It does not make sense to have the same name for two\ \ different subsections though! When you say `" <> name <> "` in some code, there\ \ is no way for me to know which subsection you mean!" ] ) CommentOnNothing -> Report.report "STRAY COMMENT" Nothing ("This documentation comment is not followed by anything.") ( reflowParagraph $ "All documentation comments need to be right above the declaration they\ \ describe. Maybe some code got deleted or commented out by accident? Or\ \ maybe this comment is here by accident?" ) SettingsOnNormalModule -> Report.report "BAD MODULE DECLARATION" Nothing "A normal module can expose values, but not settings like this." ( reflowParagraph $ "If you want a normal module, just remove this stuff. If you want to create\ \ an `effect module` you forgot to use the `effect` keyword. In that\ \ case, change `module` to `effect module` and you should be headed in\ \ the right direction!" ) SettingsOnPortModule -> Report.report "BAD MODULE DECLARATION" Nothing "A port module can expose values, but not have settings like this." ( reflowParagraph $ "If you want a port module, just remove this stuff. If you want to create\ \ a custom effect module instead (which is rare) change `port module`\ \ to `effect module` and you should be headed in the right direction!" ) DuplicateSettingOnEffectModule name -> Report.report "EFFECT MODULE PROBLEM" Nothing ("You have defined " <> Help.functionName name <> " multiple times.") ( reflowParagraph $ "There can only be one " <> Help.functionName name <> " though! Remove until there is one left." ) BadSettingOnEffectModule name -> Report.report "EFFECT MODULE PROBLEM" Nothing ("Setting " <> Help.functionName name <> " is not recognized.") ( reflowParagraph $ "Remove this entry and you should be all set!" ) NoSettingsOnEffectModule -> Report.report "EFFECT MODULE PROBLEM" Nothing ("You are defining an effect module, but it has no settings.") ( reflowParagraph $ "If you just wanted a normal module, change the keywords `effect module`\ \ to `module` and you should be all set. If you want a proper effect module,\ \ you need to specify your commands and/or subscriptions. Read more about this\ \ here: " ) MissingManagerOnEffectModule name -> Report.report "EFFECT MODULE PROBLEM" Nothing ("You are defining an effect module, but there is no " <> Help.functionName name <> " defined.") ( reflowParagraph $ "There is a small set of top-level functions and values that must be defined\ \ in any complete effect module. The best thing is probably to just read more\ \ about effect modules here:\ \ " ) UnexpectedPort name -> Report.report "BAD PORT" Nothing ("You are declaring port " <> Help.functionName name <> " in a normal module.") ( reflowParagraph $ "All ports must be defined in a `port module`. You should probably have only\ \ one of these for your project. This way all of your foreign interactions\ \ stay relatively organized." ) TypeWithoutDefinition valueName -> Report.report "MISSING DEFINITION" Nothing ("There is a type annotation for `" <> valueName <> "` but there" <> " is no corresponding definition!" ) ( text $ "Directly below the type annotation, put a definition like:\n\n" <> " " <> valueName <> " = 42" ) DuplicateArgument funcName argName -> Report.report "DUPLICATE ARGUMENT" Nothing ( "The name `" <> argName <> "` is used more than once in the arguments of " <> Help.functionName funcName <> "." ) ( reflowParagraph $ "Rename things until `" <> argName <> "` is used only once.\ \ Otherwise how can we tell which one you want when you\ \ say `" <> argName <> "` it in the body of your function?" ) DuplicateFieldName region name -> Report.report "DUPLICATE FIELD" (Just region) ("This record has more than one field named `" <> name <> "`.") (text "There can only be one. Do some renaming to make sure the names are distinct!") DuplicateValueDeclaration name -> Report.report "DUPLICATE DEFINITION" Nothing ( "Naming multiple top-level values " <> Help.functionName name <> " makes things\n" <> "ambiguous. When you say " <> Help.functionName name <> " which one do you want?" ) ( reflowParagraph $ "Find all the top-level values named " <> Help.functionName name <> " and do some renaming. Make sure the names are distinct!" ) DuplicateTypeDeclaration name -> Report.report "DUPLICATE DEFINITION" Nothing ( "There are multiple types named `" <> name <> "` in this module." ) ( reflowParagraph $ "Search through this module, find all the types named `" <> name <> "`, and give each of them a unique name." ) DuplicateDefinition name -> Report.report "DUPLICATE DEFINITION" Nothing ("There are multiple values named `" <> name <> "` in this let-expression." ) ( reflowParagraph $ "Search through this let-expression, find all the values named `" <> name <> "`, and give each of them a unique name." ) UnboundTypeVarsInUnion typeName givenVars unbound -> unboundTypeVars "type" typeName givenVars unbound UnboundTypeVarsInAlias typeName givenVars unbound -> unboundTypeVars "type alias" typeName givenVars unbound UnusedTypeVarsInAlias typeName givenVars unused -> Report.report "UNUSED TYPE VARIABLES" Nothing ( "Type alias `" <> typeName <> "` cannot have unused type variables: " <> Help.commaSep unused ) ( Help.stack [ text "You probably need to change the declaration like this:" , dullyellow $ hsep $ map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ ["=", "..."]) ] ) MessyTypeVarsInAlias typeName givenVars unused unbound -> Report.report "TYPE VARIABLE PROBLEMS" Nothing ( "Type alias `" <> typeName <> "` has some problems with type variables." ) ( Help.stack [ reflowParagraph $ "The declaration says it uses certain type variables (" <> Help.commaSep unused <> ") but they do not appear in the aliased type. " <> "Furthermore, the aliased type says it uses type variables (" <> Help.commaSep unbound <> ") that do not appear in the declaration." , text "You probably need to change the declaration like this:" , dullyellow $ hsep $ map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ unbound ++ ["=", "..."]) ] ) unboundTypeVars :: Text -> Text -> [Text] -> [Text] -> Report.Report unboundTypeVars declKind typeName givenVars unboundVars = Report.report "UNBOUND TYPE VARIABLES" Nothing ( Help.capitalize declKind <> " `" <> typeName <> "` must declare its use of type variable" <> Help.commaSep unboundVars ) ( Help.stack [ text "You probably need to change the declaration like this:" , hsep $ map text (declKind : typeName : givenVars) ++ map (dullyellow . text) unboundVars ++ map text ["=", "..."] , reflowParagraph $ "Here's why. Imagine one `" <> typeName <> "` where `" <> head unboundVars <> "` is an Int and another where it is a Bool. When we explicitly list the type\ \ variables, the type checker can see that they are actually different types." ] ) -- PARSE ERROR TO REPORT makeParseReport :: Maybe R.Region -> Text -> Help.Doc -> Report.Report makeParseReport subRegion pre post = Report.report "PARSE ERROR" subRegion pre post problemToReport :: Maybe R.Region -> Problem -> Report.Report problemToReport subRegion problem = let parseReport = makeParseReport subRegion in case problem of Tab -> parseReport "I ran into a tab, but tabs are not allowed in Elm files." (reflowParagraph "Replace the tab with spages.") EndOfFile_Comment -> parseReport "I got to the end of the file while parsing a multi-line comment." ( Help.stack [ reflowParagraph $ "Multi-line comments look like {- comment -}, and it looks like\ \ you are missing the closing marker." , reflowParagraph $ "Nested multi-line comments like {- this {- and this -} -} are allowed.\ \ That means the opening and closing markers must be balanced, just\ \ like parentheses in normal code. Maybe that is the problem?" ] ) EndOfFile_Shader -> parseReport "I got to the end of the file while parsing a GLSL block." (reflowParagraph $ "A shader should be defined in a block like this: [glsl| ... |]" ) EndOfFile_String -> parseReport "I got to the end of the file while parsing a string." (reflowParagraph $ "Strings look like \"this\" with double quotes on each end.\ \ So the closing double quote seems to be missing." ) EndOfFile_MultiString -> parseReport "I got to the end of the file while parsing a multi-line string." (reflowParagraph $ "Multi-line strings begin and end with three double quotes in a\ \ row, like \"\"\"this\"\"\". So the closing marker seems\ \ to be missing." ) EndOfFile_Char -> parseReport "I got to the end of the file while parsing a character." (reflowParagraph $ "Characters look like 'x' with single quotes on each end.\ \ So the closing single quote seems to be missing." ) NewLineInString -> parseReport "This string is missing the closing quote." ( Help.stack [ reflowParagraph $ "Elm strings must start and end with a double quote, like \"this\"." , reflowParagraph $ "If you want a string that can contain newlines, use three double quotes in a\ \ row, like \"\"\"this\"\"\"." ] ) NewLineInChar -> parseReport "This character is missing the closing quote." ( reflowParagraph $ "Elm characters must start and end with a single quote, like 'a' and 'b'" ) BadEscape _ escapeProblem -> uncurry parseReport (escapeProblemToReport escapeProblem) BadChar _ -> parseReport "Ran into a bad use of single quotes." ( Help.stack [ reflowParagraph $ "If you want to create a string, switch to double quotes:" , Help.indent 4 $ dullyellow (text "'this'") <> text " => " <> green (text "\"this\"") , reflowParagraph $ "Unlike JavaScript, Elm distinguishes between strings like \"hello\"\ \ and individual characters like 'A' and '3'. If you really do want\ \ a character though, something went wrong and I did not find the\ \ closing single quote." ] ) BadNumberDot -> parseReport "Numbers cannot end with a decimal points." ( reflowParagraph $ "Leave it off or add some digits after it, like or 3 or 3.0" ) BadNumberEnd -> parseReport "Numbers cannot have letters or underscores in them." ( reflowParagraph $ "Maybe a space is missing between a number and a variable?" ) BadNumberExp -> parseReport "If you put the letter E in a number, it should followed by more digits." ( reflowParagraph $ "If you want to say 1000, you can also say 1e3.\ \ You cannot just end it with an E though!" ) BadNumberHex -> parseReport "I see the start of a hex number, but not the end." ( reflowParagraph $ "A hex number looks like 0x123ABC, where the 0x is followed by hexidecimal digits (i.e. 0123456789abcdefABCDEF)" ) BadNumberZero -> parseReport "Normal numbers cannot start with a zero. Take the zeros off the front." ( reflowParagraph $ "Only numbers like 0x0040 or 0.25 can start with a zero." ) BadShader msg -> parseReport "I ran into a problem while parsing this GLSL block." (reflowParagraph msg) BadUnderscore char -> parseReport ("An underscore cannot be immediately followed by `" <> Text.singleton char <> "`.") ( reflowParagraph $ "Variable names cannot start with an underscore in Elm. You can\ \ use an underscore as a \"wildcard\" that matches anything\ \ though. So maybe you forgot a space after the underscore?" ) BadOp op stack -> case op of HasType -> badOp subRegion stack "A" "\"has type\" operator" "type annotations and record types" "Maybe you want :: instead? Or maybe something is indented too much?" Equals -> badEquals subRegion stack Arrow -> if isCaseRelated stack then parseReport "I ran into a stray arrow while parsing this `case` expression." ( reflowParagraph $ "All branches in a `case` must be indented the exact\ \ same amount, so the patterns are vertically\ \ aligned. Maybe this branch is indented too much?" ) else badOp subRegion stack "An" "arrow" "cases expressions and anonymous functions" "Maybe you want > or >= instead?" Pipe -> badOp subRegion stack "A" "vertical bar" "type declarations" "Maybe you want || instead?" Dot -> parseReport "I was not expecting this dot." ( reflowParagraph $ "Dots are for record access and decimal points, so\ \ they cannot float around on their own. Maybe\ \ there is some extra whitespace?" ) Theories stack allTheories -> let starter = "Something went wrong while parsing " <> contextToText "your code" "" stack <> "." in parseReport starter $ case Set.toList (Set.fromList allTheories) of [] -> Help.stack [ reflowParagraph $ "I do not have any suggestions though!" , reflowParagraph $ "Can you get it down to a and share it at\ \ ?\ \ That way we can figure out how to give better advice!" ] [theory] -> reflowParagraph $ "I was expecting to see " <> addPeriod (theoryToText stack theory) theories -> Help.vcat $ [ text "I was expecting:" , text "" ] ++ map (bullet . theoryToText stack) theories escapeProblemToReport :: EscapeProblem -> ( Text, Help.Doc ) escapeProblemToReport escapeProblem = case escapeProblem of UnknownEscape -> ( "A backslash starts an escaped character, but I do not recognize this one:" , Help.stack [ "Maybe there is some typo?" , Help.toSimpleHint "Valid escape characters include:" , Help.indent 4 $ Help.vcat $ [ "\\n" , "\\r" , "\\t" , "\\\"" , "\\\'" , "\\\\" , "\\u{03BB}" ] , reflowParagraph $ "The last one lets encode ANY character by its Unicode code\ \ point, so there are no other escape characters in Elm." ] ) UnicodeSyntax -> ( "I ran into an invalid Unicode escape character:" , Help.stack [ "Here are some examples of valid Unicode escape characters:" , Help.indent 4 $ Help.vcat $ [ "\\u{0041}" , "\\u{03BB}" , "\\u{6728}" , "\\u{1F60A}" ] , reflowParagraph $ "Notice that the code point is always surrounded by curly\ \ braces. They are required!" ] ) UnicodeRange -> ( "This is not a real Unicode code point:" , "All valid Unicode code points are between 0 and 10FFFF." ) UnicodeLength numDigits badCode -> let moreOrFewer = if numDigits < 4 then "more" else "fewer" goodCode = Text.justifyRight 4 '0' $ Text.dropWhile (== '0') badCode suggestion = [ "Try" , "using" , dullyellow $ text $ "\\u{" <> goodCode <> "}" , "instead." ] in ( "This Unicode code point needs " <> moreOrFewer <> " digits:" , hsep suggestion ) -- HELPERS bullet :: Text -> Help.Doc bullet point = Help.hang 4 (" - " <> reflowParagraph point) addPeriod :: Text -> Text addPeriod msg = if Text.last msg `elem` ['`', ')', '.', '!', '?'] then msg else msg <> "." badOp :: Maybe R.Region -> ContextStack -> Text -> Text -> Text -> Text -> Report.Report badOp subRegion stack article opName setting hint = let pre = "I was not expecting this " <> opName <> contextToText "" " while parsing " stack <> "." in makeParseReport subRegion pre $ reflowParagraph $ article <> " " <> opName <> " should only appear in " <> setting <> ". " <> hint badEquals :: Maybe R.Region -> ContextStack -> Report.Report badEquals subRegion stack = let pre = "I was not expecting this equals sign" <> contextToText "" " while parsing " stack <> "." in makeParseReport subRegion pre (badEqualsHelp stack) badEqualsHelp :: ContextStack -> Help.Doc badEqualsHelp stack = case stack of [] -> reflowParagraph $ "Maybe you want == instead? Or maybe something is indented too much?" (ExprRecord, _) : _ -> reflowParagraph $ "Records look like { x = 3, y = 4 } with the equals sign right\ \ after the field name. Maybe you forgot a comma?" (Definition _, _) : rest -> reflowParagraph $ "Maybe this is supposed to be a separate definition? If so, it\ \ is indented too far. " <> if any ((==) ExprLet . fst) rest then "All definitions in a `let` expression must be vertically aligned." else "Spaces are not allowed before top-level definitions." _ : rest -> badEqualsHelp rest contextToText :: Text -> Text -> ContextStack -> Text contextToText defaultText prefixText stack = case stack of [] -> defaultText (context, _) : rest -> let anchor = getAnchor rest in prefixText <> case context of ExprIf -> "an `if`" <> anchor ExprLet -> "a `let`" <> anchor ExprFunc -> "an anonymous function" <> anchor ExprCase -> "a `case`" <> anchor ExprList -> "a list" <> anchor ExprTuple -> "an expression (in parentheses)" <> anchor ExprRecord -> "a record" <> anchor Definition name -> name <> "'s definition" Annotation name -> name <> "'s type annotation" TypeTuple -> "a type (in parentheses)" <> anchor TypeRecord -> "a record type" <> anchor PatternList -> "a list pattern" <> anchor PatternTuple -> "a pattern (in parentheses)" <> anchor PatternRecord -> "a record pattern" <> anchor Module -> "a module declaration" Import -> "an import" TypeUnion -> "a union type" TypeAlias -> "a type alias" Infix -> "an infix declaration" Port -> "a port declaration" getAnchor :: ContextStack -> Text getAnchor stack = case stack of [] -> "" (context, _) : rest -> case context of Definition name -> " in " <> name <> "'s definition" Annotation name -> " in " <> name <> "'s type annotation" _ -> getAnchor rest isCaseRelated :: ContextStack -> Bool isCaseRelated stack = case stack of [] -> False (context, _) : rest -> context == ExprCase || isCaseRelated rest theoryToText :: ContextStack -> Theory -> Text theoryToText context theory = case theory of Keyword keyword -> "the `" <> keyword <> "` keyword" Symbol symbol -> case symbol of "=" -> equalsTheory context "->" -> "an arrow (->) followed by an expression" ":" -> "the \"has type\" symbol (:) followed by a type" "," -> "a comma" "|" -> barTheory context "::" -> "the cons operator (::) followed by more list elements" "." -> "a dot (.)" "-" -> "a minus sign (-)" "_" -> "an underscore" "(" -> "a left paren, for grouping or starting tuples" ")" -> "a closing paren" "[" -> "a left square bracket, for starting lists" "]" -> "a right square bracket, to end a list" "{" -> "a left curly brace, for starting records" "}" -> "a right curly brace, to end a record" "{-|" -> "a doc comment, like {-| example -}" _ -> "the (" <> symbol <> ") symbol" LowVar -> "a lower-case variable, like `x` or `user`" CapVar -> "an upper-case variable, like `Maybe` or `Just`" InfixOp -> "an infix operator, like (+) or (==)" Digit -> "a digit from 0 to 9" BadSpace -> badSpace context Expecting next -> case next of Decl -> "a declaration, like `x = 5` or `type alias Model = { ... }`" Expr -> "an expression, like x or 42" AfterOpExpr op -> "an expression after that (" <> op <> ") operator, like x or 42" ElseBranch -> "an `else` branch. An `if` must handle both possibilities." Arg -> "an argument, like `name` or `total`" Pattern -> "a pattern, like `name` or (Just x)" Type -> "a type, like Int or (List String)" Listing -> "a list of exposed values and types, like (..) or (x,y,z)" Exposing -> "something like `exposing (..)`" equalsTheory :: ContextStack -> Text equalsTheory stack = case stack of [] -> "an equals sign (=)" (context, _) : rest -> case context of ExprRecord -> "an equals sign (=) followed by an expression" Definition name -> "an equals sign (=) followed by " <> name <> "'s definition" TypeUnion -> "an equals sign (=) followed by the first union type constructor" TypeAlias -> "an equals sign (=) followed by the aliased type" _ -> equalsTheory rest barTheory :: ContextStack -> Text barTheory stack = case stack of [] -> "a vertical bar (|)" (context, _) : rest -> case context of ExprRecord -> "a vertical bar (|) followed by the record fields you want to update" TypeRecord -> "a vertical bar (|) followed by some record field types" TypeUnion -> "a vertical bar (|) followed by more union type constructors" _ -> barTheory rest badSpace :: ContextStack -> Text badSpace stack = case stack of [] -> "more indentation? I was not done with that last thing yet." (context, _) : rest -> case context of ExprIf -> "the end of that `if`" <> badSpaceExprEnd rest ExprLet -> "the end of that `let`" <> badSpaceExprEnd rest ExprFunc -> badSpace rest ExprCase -> "more of that `case`" <> badSpaceExprEnd rest ExprList -> "the end of that list" <> badSpaceExprEnd rest ExprTuple -> "a closing paren" <> badSpaceExprEnd rest ExprRecord -> "the end of that record" <> badSpaceExprEnd rest Definition name -> "the rest of " <> name <> "'s definition" <> badSpaceExprEnd stack Annotation name -> "the rest of " <> name <> "'s type annotation" <> badSpaceEnd TypeTuple -> "a closing paren" <> badSpaceEnd TypeRecord -> "the end of that record" <> badSpaceEnd PatternList -> "the end of that list" <> badSpaceEnd PatternTuple -> "a closing paren" <> badSpaceEnd PatternRecord -> "the end of that record" <> badSpaceEnd Module -> "something like `module Main exposing (..)`" Import -> "something like `import Html exposing (..)`" TypeUnion -> "more of that union type" <> badSpaceEnd TypeAlias -> "more of that type alias" <> badSpaceEnd Infix -> "more of that infix declaration" <> badSpaceEnd Port -> "more of that port declaration" <> badSpaceEnd badSpaceEnd :: Text badSpaceEnd = ". Maybe you forgot some code? Or you need more indentation?" badSpaceExprEnd :: ContextStack -> Text badSpaceExprEnd stack = case stack of [] -> badSpaceEnd (Definition name, R.Position _ column) : _ -> let ending = if column <= 1 then "to be indented?" else "more indentation? (Try " <> i2t (column + 1) <> "+ spaces.)" in ". Maybe you forgot some code? Or maybe the body of `" <> name <> "` needs " <> ending _ : rest -> badSpaceExprEnd rest