fnotation-0.2.0.0: src/FNotation/Parser.hs
module FNotation.Parser where
import Data.IORef
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Text (Text)
import Data.Vector qualified as V
import Diagnostician
import FNotation.Config
import FNotation.Kinds qualified as T
import FNotation.Names
import FNotation.Tokens qualified as T
import FNotation.Trees
import Prettyprinter
import Prelude hiding (lookup)
-- Parser diagnostics
--------------------------------------------------------------------------------
data ParserCode
= UnexpectedToken
| DefaultedPrec
| IncompatiblePrecedences
| ModifierWithoutModifyee
deriving (Eq, Ord)
parserCodeTable :: Map ParserCode CodeMeta
parserCodeTable =
Map.fromList
[ (UnexpectedToken, CodeMeta 0 SError Nothing)
, (DefaultedPrec, CodeMeta 1 SWarning Nothing)
, (IncompatiblePrecedences, CodeMeta 2 SError Nothing)
, (ModifierWithoutModifyee, CodeMeta 3 SError Nothing)
]
-- Parser monad
--------------------------------------------------------------------------------
data ParseState = ParseState
{ pos :: IORef Int
, gas :: IORef Int
, skipNewlines :: IORef Bool
, tokens :: V.Vector T.Token
, file :: File
, reporter :: Reporter ParserCode
, config :: ConfTable Prec
}
-- Parsing utilities
--------------------------------------------------------------------------------
report :: ParseState -> Span -> ParserCode -> DDoc -> IO ()
report st s c m = do
let n = Note (Just (SourceLoc st.file s)) Nothing
let d = Diagnostic c m [n]
st.reporter.reportIO d
cur :: ParseState -> IO T.Kind
cur st = do
gas <- readIORef st.gas
if gas <= 0
then do
pos <- readIORef st.pos
let token = st.tokens V.! pos
error $ "out of gas at token " ++ show (dpretty token)
else writeIORef st.gas (gas - 1)
pos <- readIORef st.pos
pure (st.tokens V.! pos).kind
locally :: IORef a -> a -> IO b -> IO b
locally ref v action = do
old <- readIORef ref
writeIORef ref v
res <- action
writeIORef ref old
pure res
ignoreNewlines :: ParseState -> IO a -> IO a
ignoreNewlines st = locally st.skipNewlines True
withNewlines :: ParseState -> IO a -> IO a
withNewlines st = locally st.skipNewlines False
curSpan :: ParseState -> IO Span
curSpan st = do
pos <- readIORef st.pos
pure (V.unsafeIndex st.tokens pos).span
curValue :: ParseState -> IO T.TokenValue
curValue st = do
pos <- readIORef st.pos
pure (V.unsafeIndex st.tokens pos).value
curName :: ParseState -> IO Name
curName st =
curValue st >>= \case
T.VName x -> pure x
_ -> error "expected token to be associated with a name"
curInt :: ParseState -> IO Int
curInt st =
curValue st >>= \case
T.VInt x -> pure x
_ -> error "expected token to be associated with an int"
curString :: ParseState -> IO Text
curString st =
curValue st >>= \case
T.VString x -> pure x
_ -> error "expected token to be associated with a string"
at :: ParseState -> T.Kind -> IO Bool
at st k = (k ==) <$> cur st
advance :: ParseState -> IO ()
advance st = do
pos <- readIORef st.pos
let n = V.length st.tokens
if pos < n - 1
then do
let next j
| j < n = case (V.unsafeIndex st.tokens j).kind of
T.Nl -> next (j + 1)
_ -> j
| otherwise = j
readIORef st.skipNewlines >>= \case
True -> writeIORef st.pos $ next (pos + 1)
False -> writeIORef st.pos $ pos + 1
writeIORef st.gas 256
else pure ()
eat :: ParseState -> T.Kind -> IO Bool
eat st k =
at st k >>= \case
True -> advance st >> pure True
False -> pure False
reportUnexpected :: ParseState -> T.Kind -> T.Class -> IO ()
reportUnexpected st k c = do
s <- curSpan st
report st s UnexpectedToken $
"Unexpected token kind" <+> dpretty k <> ", expected" <+> dpretty c
expect :: ParseState -> T.Kind -> IO ()
expect st k = do
k' <- cur st
if k == k'
then advance st
else
reportUnexpected st k' (T.CSpecific k) >> pure ()
openingPos :: ParseState -> IO Pos
openingPos st = (.start) <$> curSpan st
close :: ParseState -> Pos -> (Span -> Ntn) -> IO Ntn
close st s f = do
(Span _ e) <- curSpan st
pure $ f (Span s e)
advanceClose :: ParseState -> Pos -> (Span -> Ntn) -> IO Ntn
advanceClose st s f = do
n <- close st s f
advance st
pure n
-- The fnotation grammar
--------------------------------------------------------------------------------
argStarts :: V.Vector T.Kind
argStarts =
V.fromList
[ T.LParen
, T.LBrack
, T.AIdent
, T.AKeyword
, T.Field
, T.Tag
, T.Int
, T.Block
]
argStart :: T.Kind -> Bool
argStart k = V.elem k argStarts
tupleElems :: ParseState -> IO [Ntn]
tupleElems st =
cur st >>= \case
T.RBrack -> pure []
k | argStart k -> do
n <- expr st
cur st >>= \case
T.RBrack -> pure [n]
T.Comma -> do
advance st
ns <- tupleElems st
pure $ n : ns
k' -> do
reportUnexpected st k' T.CTupleMark
pure [n]
k -> do
reportUnexpected st k T.CExprStart
pure []
arg :: ParseState -> IO Ntn
arg st = do
m <- openingPos st
cur st >>= \case
T.LParen -> do
e <- ignoreNewlines st $ do
advance st
expr st
expect st T.RParen
pure e
T.LBrack -> do
ns <- ignoreNewlines st $ do
advance st
tupleElems st
expect st T.RBrack
close st m $ Tuple ns
T.AIdent -> do
x <- curName st
advanceClose st m $ Ident x
T.AKeyword -> do
x <- curName st
advanceClose st m $ Keyword x
T.Field -> do
x <- curName st
advanceClose st m $ Field x
T.Tag -> do
x <- curName st
advanceClose st m $ Tag x
T.Int -> do
i <- curInt st
advanceClose st m $ Int i
T.String -> do
x <- curString st
advanceClose st m $ String x
T.Block -> block st
k -> do
reportUnexpected st k T.CExprStart
advanceClose st m Error
args :: ParseState -> IO [Ntn]
args st = do
k <- cur st
if argStart k
then (:) <$> arg st <*> args st
else pure []
expr :: ParseState -> IO Ntn
expr st = arg st >>= go (Prec 0 AssocNon)
where
go p lhs = do
cur st >>= \case
k@(T.SIdent; T.SKeyword) -> do
s <- curSpan st
x <- curName st
let n = case k of
T.SIdent -> Ident x s
T.SKeyword -> Keyword x s
p' <- case confTableLookup st.config x.last of
Just p' -> pure p'
Nothing -> do
report st s DefaultedPrec $
"Defaulted precedence of" <+> dpretty x <+> "to the same as +"
pure $ Prec 50 AssocL
case precLe p p' of
Nothing -> do
report st s IncompatiblePrecedences "Incompatible precedences"
pure lhs
Just False -> pure lhs
Just True -> do
advance st
rhs <- arg st >>= go p'
go p (Infix lhs n rhs)
k | argStart k -> do
rhs <- arg st
go p (Juxt lhs rhs)
_ -> pure lhs
decl :: ParseState -> IO Ntn
decl st = do
p <- openingPos st
go p []
where
go p mods =
cur st >>= \case
T.Modifier -> do
m <- curName st
advance st
go p (m : mods)
T.Decl -> do
x <- curName st
advance st
n <- expr st
expect st T.Nl
pure $ MDecl (reverse mods) x n (Span p (endPos n))
_ -> do
s <- curSpan st
report st s ModifierWithoutModifyee "expected a declaration after a declaration modifier"
advanceClose st p Error
stmt :: ParseState -> IO Ntn
stmt st =
cur st >>= \case
T.Modifier; T.Decl -> decl st
_ -> expr st
stmts :: ParseState -> IO [Ntn]
stmts st = go []
where
go ns =
cur st >>= \case
T.Nl -> do
advance st
go ns
k | k == T.End || k == T.Eof -> pure $ reverse ns
_ -> do
n <- stmt st
go $ n : ns
block :: ParseState -> IO Ntn
block st =
cur st >>= \case
T.Block -> do
m <- openingPos st
x <- curName st
advance st
h <-
cur st >>= \case
k | argStart k -> Just <$> arg st
_ -> pure Nothing
ns <- stmts st
advanceClose st m $ Block x h ns
_ -> error "expected a block"
-- Toplevel parsing interface
--------------------------------------------------------------------------------
parse :: ConfTable Prec -> Reporter ParserCode -> File -> V.Vector T.Token -> IO [Ntn]
parse config reporter file tokens = do
pos <- newIORef 0
gas <- newIORef 256
skipNewlines <- newIORef False
let st = ParseState pos gas skipNewlines tokens file reporter config
stmts st