{
{-|
Module      : Toml.Syntax.Parser
Description : Raw TOML expression parser
Copyright   : (c) Eric Mertens, 2023
License     : ISC
Maintainer  : emertens@gmail.com

This module parses TOML tokens into a list of raw,
uninterpreted sections and assignments.

-}
module Toml.Syntax.Parser (
  -- * Types
  Expr(..),
  SectionKind(..),
  Val(..),
  Key,

  -- * Parser
  parseRawToml,
  ) where

import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Text (Text)
import Toml.Syntax.Lexer (Context(..), Token(..))
import Toml.Syntax.ParserUtils
import Toml.Syntax.Position (Located(Located, locThing), Position)
import Toml.Syntax.Position (startPos)
import Toml.Syntax.Types (Expr(..), Key, Val(..), SectionKind(..))

}

%tokentype            { Located Token                               }
%token
','                   { Located $$ TokComma                         }
'='                   { Located $$ TokEquals                        }
NEWLINE               { Located $$ TokNewline                       }
'.'                   { Located $$ TokPeriod                        }
'['                   { Located $$ TokSquareO                       }
']'                   { Located $$ TokSquareC                       }
'[['                  { Located $$ Tok2SquareO                      }
']]'                  { Located $$ Tok2SquareC                      }
'{'                   { Located $$ TokCurlyO                        }
'}'                   { Located $$ TokCurlyC                        }
BAREKEY               { (traverse asBareKey        -> Just $$)      }
STRING                { (traverse asString         -> Just $$)      }
MLSTRING              { (traverse asMlString       -> Just $$)      }
BOOL                  { (traverse asBool           -> Just $$)      }
INTEGER               { (traverse asInteger        -> Just $$)      }
FLOAT                 { (traverse asFloat          -> Just $$)      }
OFFSETDATETIME        { (traverse asOffsetDateTime -> Just $$)      }
LOCALDATETIME         { (traverse asLocalDateTime  -> Just $$)      }
LOCALDATE             { (traverse asLocalDate      -> Just $$)      }
LOCALTIME             { (traverse asLocalTime      -> Just $$)      }

%monad                { Parser r } { thenP } { pureP }
%lexer                { lexerP } { Located _ TokEOF }
%error                { errorP }

%name parseRawToml_ toml

%%

toml ::               { [Expr Position]                             }
  : sepBy1(expression, NEWLINE)
                      { concat $1                                   }

expression ::         { [Expr Position]                             }
  :                   { []                                          }
  | keyval            { [uncurry KeyValExpr $1]                     }
  | '['  key ']'      { [TableExpr      $2    ]                     }
  | '[[' key ']]'     { [ArrayTableExpr $2    ]                     }

keyval ::             { (Key Position, Val Position)                }
  : key rhs '=' pop val
                      { ($1,$5)                                     }

key ::                { Key Position                                }
  : sepBy1(simplekey, '.')
                      { $1                                          }

simplekey ::          { (Position, Text)                            }
  : BAREKEY           { locVal (,) $1                               }
  | STRING            { locVal (,) $1                               }

val ::                { Val Position                                }
  : INTEGER           { locVal ValInteger    $1                     }
  | FLOAT             { locVal ValFloat      $1                     }
  | BOOL              { locVal ValBool       $1                     }
  | STRING            { locVal ValString     $1                     }
  | MLSTRING          { locVal ValString     $1                     }
  | LOCALDATE         { locVal ValDay        $1                     }
  | LOCALTIME         { locVal ValTimeOfDay  $1                     }
  | OFFSETDATETIME    { locVal ValZonedTime  $1                     }
  | LOCALDATETIME     { locVal ValLocalTime  $1                     }
  | array             { locVal ValArray      $1                     }
  | inlinetable       { locVal ValTable      $1                     }

inlinetable ::        { Located [(Key Position, Val Position)]      }
  : lhs '{' inlinetablekeyvals              newlines pop '}'
                      { Located $2 (reverse $3)                     }
  | lhs '{' inlinetablekeyvals newlines ',' newlines pop '}'
                      { Located $2 (reverse $3)                     }
  | lhs '{'                                 newlines pop '}'
                      { Located $2 []                               }

inlinetablekeyvals :: { [(Key Position, Val Position)]              }
  : newlines keyval   { [$2]                                        }
  | inlinetablekeyvals newlines ',' newlines keyval
                      { $5 : $1                                     }

array ::              { Located [Val Position]                      }
  : rhs '[' newlines                          pop ']'
                      { Located $2 []                               }
  | rhs '[' newlines arrayvalues              pop ']'
                      { Located $2 (reverse $4)                     }
  | rhs '[' newlines arrayvalues ',' newlines pop ']'
                      { Located $2 (reverse $4)                     }

arrayvalues ::        { [Val Position]                              }
  :                          val newlines
                      { [$1]                                        }
  | arrayvalues ',' newlines val newlines
                      { $4 : $1                                     }

newlines ::           { ()                                          }
  :                   { ()                                          }
  | newlines NEWLINE  { ()                                          }

sepBy(p,q) ::         { [p]                                         }
  :                   { []                                          }
  | sepBy1(p,q)       { NonEmpty.toList $1                          }

sepBy1(p,q) ::        { NonEmpty p                                  }
  : sepBy1_(p,q)      { NonEmpty.reverse $1                         }

sepBy1_(p,q) ::       { NonEmpty p                                  }
  :                p  { pure $1                                     }
  | sepBy1_(p,q) q p  { NonEmpty.cons $3 $1                         }

rhs ::                { ()                                          }
  :                   {% push ValueContext                          }

lhs ::                { ()                                          }
  :                   {% push TableContext                          }

pop ::                { ()                                          }
  :                   {% pop                                        }

{

-- | Parse a list of tokens either returning the first unexpected
-- token or a list of the TOML statements in the file to be
-- processed by "Toml.Semantics".
parseRawToml :: Text -> Either (Located String) [Expr Position]
parseRawToml = runParser parseRawToml_ TopContext . Located startPos

}
