212 lines
5.9 KiB
Idris
212 lines
5.9 KiB
Idris
module Lib.Parser.Impl
|
|
|
|
import Lib.Token
|
|
import Data.String
|
|
import Data.Nat
|
|
|
|
public export
|
|
TokenList : Type
|
|
TokenList = List BTok
|
|
|
|
-- I was going to use a record, but we're peeling this off of bounds at the moment.
|
|
public export
|
|
SourcePos : Type
|
|
SourcePos = (Int,Int)
|
|
|
|
emptyPos : SourcePos
|
|
emptyPos = (0,0)
|
|
|
|
-- Error of a parse
|
|
public export
|
|
data Error = E SourcePos String
|
|
%name Error err
|
|
|
|
public export
|
|
showError : String -> Error -> String
|
|
showError src (E (line, col) msg) = "Err at \{show (line,col)} \{msg}\n" ++ go 0 (lines src)
|
|
where
|
|
go : Int -> List String -> String
|
|
go l [] = ""
|
|
go l (x :: xs) =
|
|
if l == line then
|
|
"\{x}\n\{replicate (cast col) ' '}^\n"
|
|
else if line - 3 < l then x ++ "\n" ++ go (l + 1) xs
|
|
else go (l + 1) xs
|
|
|
|
-- Result of a parse
|
|
public export
|
|
data Result : Type -> Type where
|
|
OK : a -> (toks : TokenList) -> (com : Bool) -> Result a
|
|
Fail : Bool -> Error -> (toks : TokenList) -> (com : Bool) -> Result a
|
|
|
|
export
|
|
Functor Result where
|
|
map f (OK a toks com ) = OK (f a) toks com
|
|
map _ (Fail fatal err toks com) = Fail fatal err toks com
|
|
|
|
-- 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.
|
|
|
|
-- We keep the line and column for indents. The idea being that we check
|
|
-- either the col < tokCol or line == tokLine, enabling sameLevel.
|
|
|
|
-- dunno why I'm making that a pair..
|
|
export
|
|
data Parser a = P (TokenList -> Bool -> (lc : SourcePos) -> Result a)
|
|
|
|
export
|
|
runP : Parser a -> TokenList -> Bool -> SourcePos -> Result a
|
|
runP (P f) = f
|
|
|
|
error : TokenList -> String -> Error
|
|
error [] msg = E emptyPos msg
|
|
error ((MkBounded val isIrrelevant (MkBounds line col _ _)) :: _) msg = E (line, col) msg
|
|
|
|
export
|
|
parse : Parser a -> TokenList -> Either Error a
|
|
parse pa toks = case runP pa toks False emptyPos of
|
|
Fail fatal err toks com => Left err
|
|
OK a [] _ => Right a
|
|
OK a ts _ => Left (error ts "Extra toks")
|
|
|
|
-- I think I want to drop the typeclasses for v1
|
|
|
|
export
|
|
fail : String -> Parser a
|
|
fail msg = P $ \toks,com,col => Fail False (error toks msg) toks com
|
|
|
|
export
|
|
fatal : String -> Parser a
|
|
fatal msg = P $ \toks,com,col => Fail False (error toks msg) toks com
|
|
|
|
-- mustWork / commit copied from Idris, but I may switch to the parsec consumption thing.
|
|
export
|
|
mustWork : Parser a -> Parser a
|
|
mustWork (P pa) = P $ \ toks, com, col => case (pa toks com col) of
|
|
Fail x err xs y => Fail True err xs y
|
|
res => res
|
|
|
|
export
|
|
Functor Parser where
|
|
map f (P pa) = P $ \ toks, com, col => map f (pa toks com col)
|
|
|
|
export
|
|
Applicative Parser where
|
|
pure pa = P (\ toks, com, col => OK pa toks com)
|
|
P pab <*> P pa = P $ \toks,com,col =>
|
|
case pab toks com col of
|
|
Fail fatal err toks com => Fail fatal err toks com
|
|
OK f toks com =>
|
|
case pa toks com col of
|
|
(OK x toks com) => OK (f x) toks com
|
|
(Fail fatal err toks com) => Fail fatal err toks com
|
|
|
|
-- REVIEW it would be nice if the second argument was lazy...
|
|
export
|
|
(<|>) : Parser a -> Lazy (Parser a) -> Parser a
|
|
(P pa) <|> (P pb) = P $ \toks,com,col =>
|
|
case pa toks False col of
|
|
OK a toks' _ => OK a toks' com
|
|
Fail True err toks' com => Fail True err toks' com
|
|
Fail fatal err toks' True => Fail fatal err toks' com
|
|
Fail fatal err toks' False => pb toks com col
|
|
|
|
export
|
|
Monad Parser where
|
|
P pa >>= pab = P $ \toks,com,col =>
|
|
case pa toks com col of
|
|
(OK a toks com) => runP (pab a) toks com col
|
|
(Fail fatal err xs x) => Fail fatal err xs x
|
|
|
|
|
|
-- do we need this?
|
|
pred : (BTok -> Bool) -> String -> Parser String
|
|
pred f msg = P $ \toks,com,col =>
|
|
case toks of
|
|
(t :: ts) => if f t then OK (value t) ts com else Fail False (error toks "\{msg} vt:\{value t}") toks com
|
|
[] => Fail False (error toks "eof") toks com
|
|
|
|
export
|
|
commit : Parser ()
|
|
commit = P $ \toks,com,col => OK () toks True
|
|
|
|
export
|
|
defer : (() -> (Parser a)) -> Parser a
|
|
defer f = P $ \toks,com,col => runP (f ()) toks com col
|
|
|
|
mutual
|
|
export some : Parser a -> Parser (List a)
|
|
some p = defer $ \_ => [| p :: many p|] --(::) <$> p <*> many p)
|
|
|
|
export many : Parser a -> Parser (List a)
|
|
many p = some p <|> pure []
|
|
|
|
-- sixty let has some weird CPS stuff, but essentially:
|
|
|
|
-- case token_ of
|
|
-- Lexer.LLet -> PLet <$> blockOfMany let_ <* token Lexer.In <*> term
|
|
|
|
-- withIndentationBlock - sets the col
|
|
export
|
|
getPos : Parser SourcePos
|
|
getPos = P $ \toks,com, (l,c) => case toks of
|
|
[] => Fail False (error toks "End of file") toks com -- OK emptyPos toks com
|
|
(t :: ts) => OK (start t) toks com
|
|
|
|
||| Start an indented block and run parser in it
|
|
export
|
|
startBlock : Parser a -> Parser a
|
|
startBlock (P p) = P $ \toks,com,(l,c) => case toks of
|
|
[] => p toks com (l,c)
|
|
(t :: _) =>
|
|
let (tl,tc) = start t
|
|
in p toks com (tl,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,(l,c) => case toks of
|
|
[] => p toks com (l,c)
|
|
(t :: _) =>
|
|
let (tl,tc) = start t
|
|
in if tc == c then p toks com (tl, c)
|
|
else if c < tc then Fail False (error toks "unexpected indent") toks com
|
|
else Fail False (error toks "unexpected indent") toks com
|
|
|
|
export
|
|
someSame : Parser a -> Parser (List a)
|
|
someSame pa = some $ sameLevel pa
|
|
|
|
||| requires a token to be indented?
|
|
export
|
|
indented : Parser a -> Parser a
|
|
indented (P p) = P $ \toks,com,(l,c) => case toks of
|
|
[] => p toks com (l,c)
|
|
(t::_) =>
|
|
let (tl,tc) = start t
|
|
in if tc > c || tl == l then p toks com (l,c)
|
|
else Fail False (error toks "unexpected outdent") toks com
|
|
|
|
export
|
|
token' : Kind -> Parser String
|
|
token' k = pred (\t => t.val.kind == k) "Expected a \{show k} token"
|
|
|
|
export
|
|
keyword' : String -> Parser ()
|
|
keyword' kw = ignore $ pred (\t => t.val.text == kw) "Expected \{kw}"
|
|
|
|
export
|
|
token : Kind -> Parser String
|
|
token = indented . token'
|
|
|
|
export
|
|
keyword : String -> Parser ()
|
|
keyword kw = indented $ keyword' kw
|
|
|
|
export
|
|
sym : String -> Parser ()
|
|
sym = keyword
|
|
|