220 lines
7.0 KiB
Idris
220 lines
7.0 KiB
Idris
module Lib.Parser.Impl
|
|
|
|
import Prelude
|
|
import Lib.Token
|
|
import Lib.Common
|
|
import Data.String
|
|
import Data.Nat
|
|
import Data.List1
|
|
import Data.SortedMap
|
|
|
|
public export
|
|
TokenList : Type
|
|
TokenList = List BTok
|
|
|
|
-- Result of a parse
|
|
public export
|
|
data Result : Type -> Type where
|
|
OK : a -> (toks : TokenList) -> (com : Bool) -> Operators -> Result a
|
|
Fail : Bool -> Error -> (toks : TokenList) -> (com : Bool) -> Operators -> Result a
|
|
|
|
export
|
|
Functor Result where
|
|
map f (OK a toks com ops) = OK (f a) toks com ops
|
|
map _ (Fail fatal err toks com ops) = Fail fatal err toks com ops
|
|
|
|
-- So sixty just has a newtype function here now (probably for perf).
|
|
-- A record might be more ergonomic, but would require a record impl before
|
|
-- self hosting.
|
|
|
|
-- FC is a line and column for indents. The idea being that we check
|
|
-- either the col < tokCol or line == tokLine, enabling sameLevel.
|
|
|
|
-- We need State for operators (or postpone that to elaboration)
|
|
-- Since I've already built out the pratt stuff, I guess I'll
|
|
-- leave it in the parser for now
|
|
|
|
-- This is a Reader in FC, a State in Operators, Commit flag, TokenList
|
|
|
|
export
|
|
data Parser a = P (TokenList -> Bool -> Operators -> (lc : FC) -> Result a)
|
|
|
|
export
|
|
runP : Parser a -> TokenList -> Bool -> Operators -> FC -> Result a
|
|
runP (P f) = f
|
|
|
|
-- FIXME no filename, also half the time it is pointing at the token after the error
|
|
error : String -> TokenList -> String -> Error
|
|
error fn [] msg = E (MkFC fn (0,0)) msg
|
|
error fn ((MkBounded val (MkBounds line col _ _)) :: _) msg = E (MkFC fn (line,col)) msg
|
|
|
|
export
|
|
parse : String -> Parser a -> TokenList -> Either Error a
|
|
parse fn pa toks = case runP pa toks False empty (MkFC fn (-1,-1)) of
|
|
Fail fatal err toks com ops => Left err
|
|
OK a [] _ _ => Right a
|
|
OK a ts _ _ => Left (error fn ts "Extra toks")
|
|
|
|
||| Intended for parsing a top level declaration
|
|
export
|
|
partialParse : String -> Parser a -> Operators -> TokenList -> Either (Error, TokenList) (a, Operators, TokenList)
|
|
partialParse fn pa ops toks = case runP pa toks False ops (MkFC fn (0,0)) of
|
|
Fail fatal err toks com ops => Left (err, toks)
|
|
OK a ts _ ops => Right (a,ops,ts)
|
|
|
|
-- I think I want to drop the typeclasses for v1
|
|
|
|
export
|
|
try : Parser a -> Parser a
|
|
try (P pa) = P $ \toks,com,ops,col => case pa toks com ops col of
|
|
(Fail x err toks com ops) => Fail x err toks False ops
|
|
res => res
|
|
|
|
export
|
|
fail : String -> Parser a
|
|
fail msg = P $ \toks,com,ops,col => Fail False (error col.file toks msg) toks com ops
|
|
|
|
export
|
|
fatal : String -> Parser a
|
|
fatal msg = P $ \toks,com,ops,col => Fail True (error col.file toks msg) toks com ops
|
|
|
|
export
|
|
getOps : Parser (Operators)
|
|
getOps = P $ \ toks, com, ops, col => OK ops toks com ops
|
|
|
|
export
|
|
addOp : String -> Int -> Fixity -> Parser ()
|
|
addOp nm prec fix = P $ \ toks, com, ops, col =>
|
|
let parts = split (=='_') nm in
|
|
case parts of
|
|
"" ::: key :: rule => OK () toks com (insert key (MkOp nm prec fix False rule) ops)
|
|
key ::: rule => OK () toks com (insert key (MkOp nm prec fix True rule) ops)
|
|
|
|
|
|
|
|
export
|
|
Functor Parser where
|
|
map f (P pa) = P $ \ toks, com, ops, col => map f (pa toks com ops col)
|
|
|
|
export
|
|
Applicative Parser where
|
|
pure pa = P (\ toks, com, ops, col => OK pa toks com ops)
|
|
P pab <*> P pa = P $ \toks,com,ops,col =>
|
|
case pab toks com ops col of
|
|
Fail fatal err toks com ops => Fail fatal err toks com ops
|
|
OK f toks com ops =>
|
|
case pa toks com ops col of
|
|
(OK x toks com ops) => OK (f x) toks com ops
|
|
(Fail fatal err toks com ops) => Fail fatal err toks com ops
|
|
|
|
-- Second argument lazy so we don't have circular refs when defining parsers.
|
|
export
|
|
(<|>) : Parser a -> (Parser a) -> Parser a
|
|
(P pa) <|> (P pb) = P $ \toks,com,ops,col =>
|
|
case pa toks False ops col of
|
|
OK a toks' _ ops => OK a toks' com ops
|
|
Fail True err toks' com ops => Fail True err toks' com ops
|
|
Fail fatal err toks' True ops => Fail fatal err toks' True ops
|
|
Fail fatal err toks' False ops => pb toks com ops col
|
|
|
|
export
|
|
Monad Parser where
|
|
P pa >>= pab = P $ \toks,com,ops,col =>
|
|
case pa toks com ops col of
|
|
(OK a toks com ops) => runP (pab a) toks com ops col
|
|
(Fail fatal err xs x ops) => Fail fatal err xs x ops
|
|
|
|
|
|
satisfy : (BTok -> Bool) -> String -> Parser String
|
|
satisfy f msg = P $ \toks,com,ops,col =>
|
|
case toks of
|
|
(t :: ts) => if f t then OK (value t) ts True ops else Fail False (error col.file toks "\{msg} at \{show $ kind t}:\{value t}") toks com ops
|
|
[] => Fail False (error col.file toks "\{msg} at EOF") toks com ops
|
|
|
|
export
|
|
commit : Parser ()
|
|
commit = P $ \toks,com,ops,col => OK () toks True ops
|
|
|
|
|
|
export some : Parser a -> Parser (List a)
|
|
export many : Parser a -> Parser (List a)
|
|
some p = (::) <$> p <*> many p
|
|
many p = some p <|> pure []
|
|
|
|
-- one or more `a` seperated by `s`
|
|
export
|
|
sepBy : Parser s -> Parser a -> Parser (List a)
|
|
sepBy s a = (::) <$> a <*> many (s *> a)
|
|
|
|
export
|
|
getPos : Parser FC
|
|
getPos = P $ \toks, com, ops, indent => case toks of
|
|
[] => OK emptyFC toks com ops
|
|
(t :: ts) => OK (MkFC indent.file (getStart t)) toks com ops
|
|
|
|
||| Start an indented block and run parser in it
|
|
export
|
|
startBlock : Parser a -> Parser a
|
|
startBlock (P p) = P $ \toks,com,ops,indent => case toks of
|
|
[] => p toks com ops indent
|
|
(t :: _) =>
|
|
-- If next token is at or before the current level, we've got an empty block
|
|
let (tl,tc) = getStart t in
|
|
let (MkFC file (line,col)) = indent in
|
|
p toks com ops (MkFC file (tl, ifThenElse (tc <= col) (col + 1) tc))
|
|
|
|
||| Assert that parser starts at our current column by
|
|
||| checking column and then updating line to match the current
|
|
export
|
|
sameLevel : Parser a -> Parser a
|
|
sameLevel (P p) = P $ \toks, com, ops, indent => case toks of
|
|
[] => p toks com ops indent
|
|
(t :: _) =>
|
|
let (tl,tc) = getStart t in
|
|
let (MkFC file (line,col)) = indent in
|
|
if tc == col then p toks com ops (MkFC file (tl, col))
|
|
else if col < tc then Fail False (error file toks "unexpected indent") toks com ops
|
|
else Fail False (error file toks "unexpected indent") toks com ops
|
|
|
|
export
|
|
someSame : Parser a -> Parser (List a)
|
|
someSame pa = some $ sameLevel pa
|
|
|
|
export
|
|
manySame : Parser a -> Parser (List a)
|
|
manySame pa = many $ sameLevel pa
|
|
|
|
||| check indent on next token and run parser
|
|
export
|
|
indented : Parser a -> Parser a
|
|
indented (P p) = P $ \toks,com,ops,indent => case toks of
|
|
[] => p toks com ops indent
|
|
(t::_) =>
|
|
let (tl,tc) = getStart t
|
|
in if tc > fcCol indent || tl == fcLine indent then p toks com ops indent
|
|
else Fail False (error (file indent) toks "unexpected outdent") toks com ops
|
|
|
|
||| expect token of given kind
|
|
export
|
|
token' : Kind -> Parser String
|
|
token' k = satisfy (\t => t.val.kind == k) "Expected a \{show k} token"
|
|
|
|
export
|
|
keyword' : String -> Parser ()
|
|
-- FIXME make this an appropriate whitelist
|
|
keyword' kw = ignore $ satisfy (\t => t.val.text == kw && (t.val.kind == Keyword || t.val.kind == Symbol || t.val.kind == Number)) "Expected \{kw}"
|
|
|
|
||| expect indented token of given kind
|
|
export
|
|
token : Kind -> Parser String
|
|
token = indented . token'
|
|
|
|
export
|
|
keyword : String -> Parser ()
|
|
keyword kw = indented $ keyword' kw
|
|
|
|
export
|
|
sym : String -> Parser ()
|
|
sym = keyword
|
|
|