Henlo
This commit is contained in:
commit
6751c885cf
203
Main.hs
Normal file
203
Main.hs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
module Main(main) where
|
||||||
|
|
||||||
|
import Data.List ((\\), union)
|
||||||
|
import Control.Monad (when, foldM_)
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import Parser
|
||||||
|
|
||||||
|
data TopLevel
|
||||||
|
= Declaration String Expr
|
||||||
|
| Definition String Expr
|
||||||
|
| Comment String
|
||||||
|
instance Show TopLevel where
|
||||||
|
show (Declaration name value) = name ++ " : " ++ show value
|
||||||
|
show (Definition name value) = name ++ " = " ++ show value
|
||||||
|
show (Comment content) = "-- " ++ content
|
||||||
|
|
||||||
|
data Expr
|
||||||
|
= Abs String Expr Expr
|
||||||
|
| App Expr Expr
|
||||||
|
| Var String
|
||||||
|
| Pi String Expr Expr
|
||||||
|
| Kind Kinds
|
||||||
|
deriving (Eq)
|
||||||
|
instance Show Expr where
|
||||||
|
show (Abs param type_ body) = "\\" ++ param ++ ": " ++ show type_ ++ "." ++ show body
|
||||||
|
show (App (Var name) (Var name')) = name ++ " " ++ name'
|
||||||
|
show (App func (Var name)) = show func ++ " " ++ name
|
||||||
|
show (App (Var name) arg) = name ++ " " ++ "(" ++ show arg ++ ")"
|
||||||
|
show (App func arg) = "(" ++ show func ++ ")" ++ " " ++ "(" ++ show arg ++ ")"
|
||||||
|
show (Var name) = name
|
||||||
|
show (Kind k) = show k
|
||||||
|
show (Pi name value body) = "(" ++ name ++ ": " ++ show value ++ ") -> " ++ show body
|
||||||
|
|
||||||
|
data Kinds = Star | Box deriving (Eq)
|
||||||
|
instance Show Kinds where
|
||||||
|
show Star = "*"
|
||||||
|
show Box = "☐"
|
||||||
|
|
||||||
|
-- Parser
|
||||||
|
|
||||||
|
exprP, absP, piP, appP, varP, kindP, arrowP :: Parser Expr
|
||||||
|
absP = Abs
|
||||||
|
<$> ((charP 'λ' <|> charP '\\') *> wordP <* charP ':')
|
||||||
|
<*> (exprP <* charP '.')
|
||||||
|
<*> exprP
|
||||||
|
piP = Pi
|
||||||
|
<$> (charP '(' *> wordP <* charP ':')
|
||||||
|
<*> (exprP <* charP ')')
|
||||||
|
<*> (stringP "->" *> (piP <|> arrowP <|> exprP))
|
||||||
|
arrowP = do
|
||||||
|
let exprP' = (arrowP <|> absP <|> piP <|> kindP <|> appP <|> varP)
|
||||||
|
from <- ((absP <|> piP <|> kindP <|> appP <|> varP) <* stringP "->")
|
||||||
|
to <- exprP'
|
||||||
|
return $ Pi "_" from to
|
||||||
|
varP = Var <$> wordP
|
||||||
|
kindP = Kind Star <$ charP '*'
|
||||||
|
appP = do
|
||||||
|
let exprP' = (charP '(' *> appP <* charP ')') <|> absP <|> piP <|> kindP <|> varP <|> arrowP
|
||||||
|
head <- exprP'
|
||||||
|
tail <- some (charP ' ' *> exprP')
|
||||||
|
return $ foldl App head tail
|
||||||
|
exprP = piP <|> arrowP <|> appP <|> absP <|> kindP <|> varP
|
||||||
|
|
||||||
|
topLevelP, declP, defnP, commP :: Parser TopLevel
|
||||||
|
declP = Declaration
|
||||||
|
<$> wordP <* charP ':'
|
||||||
|
<*> exprP
|
||||||
|
defnP = Definition
|
||||||
|
<$> wordP <* charP '='
|
||||||
|
<*> exprP
|
||||||
|
commP = Comment <$> (stringP "--" *> many (predP (/= '\n')))
|
||||||
|
topLevelP = (declP <|> defnP <|> commP) <* many spaceP
|
||||||
|
|
||||||
|
-- Normalisation
|
||||||
|
|
||||||
|
newSymbol :: String -> [String] -> String
|
||||||
|
newSymbol param free
|
||||||
|
| param `elem` free = newSymbol (param ++ "'") free
|
||||||
|
| otherwise = param
|
||||||
|
|
||||||
|
freeVars :: Expr -> [String]
|
||||||
|
freeVars (Var name) = [name]
|
||||||
|
freeVars (App func arg) = freeVars func `union` freeVars arg
|
||||||
|
freeVars (Abs param _ body) = freeVars body \\ [param]
|
||||||
|
freeVars (Pi name value body) = freeVars value `union` freeVars body \\ [name]
|
||||||
|
freeVars (Kind _) = []
|
||||||
|
|
||||||
|
subst :: String -> Expr -> Expr -> Expr
|
||||||
|
subst var for abs@(Abs param type_ body)
|
||||||
|
| param == var = abs
|
||||||
|
| param `elem` freeVars for =
|
||||||
|
let param' = newSymbol param (freeVars for `union` freeVars body)
|
||||||
|
body' = subst param (Var param') body
|
||||||
|
in Abs param' type_ (subst var for body')
|
||||||
|
| otherwise = Abs param type_ (subst var for body)
|
||||||
|
subst var for (App func arg) = App (rec func) (rec arg)
|
||||||
|
where rec = subst var for
|
||||||
|
subst var for (Var name)
|
||||||
|
| var == name = for
|
||||||
|
| otherwise = Var name
|
||||||
|
subst var for (Kind k) = Kind k
|
||||||
|
subst var for pi@(Pi param value body)
|
||||||
|
| param == var = pi
|
||||||
|
| param `elem` freeVars for =
|
||||||
|
let param' = newSymbol param (freeVars for `union` freeVars body)
|
||||||
|
body' = subst param (Var param') body
|
||||||
|
in Pi param' (subst var for value) (subst var for body')
|
||||||
|
| otherwise = Pi param (subst var for value) (subst var for body)
|
||||||
|
|
||||||
|
alphaEq :: Expr -> Expr -> Bool
|
||||||
|
alphaEq (Var name) (Var name') = name == name'
|
||||||
|
alphaEq (App func arg) (App func' arg') = alphaEq func func' && alphaEq arg arg'
|
||||||
|
alphaEq (Abs param type_ body) (Abs param' type_' body') =
|
||||||
|
alphaEq type_ type_' && alphaEq body (subst param' (Var param) body')
|
||||||
|
alphaEq (Kind k) (Kind k') = k == k'
|
||||||
|
alphaEq (Pi param value body) (Pi param' value' body') =
|
||||||
|
alphaEq body (subst param' (Var param) body') && alphaEq value value'
|
||||||
|
alphaEq _ _ = False
|
||||||
|
|
||||||
|
nf :: Expr -> Expr
|
||||||
|
nf (App func arg) = case nf func of
|
||||||
|
Abs param _ body ->
|
||||||
|
let arg' = nf arg
|
||||||
|
in nf (subst param arg' body)
|
||||||
|
Pi name value body ->
|
||||||
|
let arg' = nf arg
|
||||||
|
in nf (subst name arg' body)
|
||||||
|
func' -> App func' (nf arg)
|
||||||
|
nf (Abs param type_ body) = Abs param type_ (nf body)
|
||||||
|
nf (Pi name value body) = Pi name (nf value) (nf body)
|
||||||
|
nf expr = expr
|
||||||
|
|
||||||
|
betaEq :: Expr -> Expr -> Bool
|
||||||
|
betaEq a b = alphaEq (nf a) (nf b)
|
||||||
|
|
||||||
|
-- Type checking
|
||||||
|
|
||||||
|
newtype Env = Env [(String, Expr)] deriving Show
|
||||||
|
instance Semigroup Env where
|
||||||
|
Env a <> Env b = Env $ union a b
|
||||||
|
instance Monoid Env where
|
||||||
|
mempty = Env []
|
||||||
|
extend :: Env -> String -> Expr -> Env
|
||||||
|
extend (Env env) name type_ = Env $ (name, type_) : env
|
||||||
|
|
||||||
|
findVar :: Env -> String -> Either String Expr
|
||||||
|
findVar (Env env) key = maybe (Left $ "Cannot find variable " ++ key) Right $ lookup key env
|
||||||
|
|
||||||
|
allowedKinds :: [(Expr, Expr)]
|
||||||
|
allowedKinds = [(Kind Star, Kind Star), (Kind Star, Kind Box), (Kind Box, Kind Star), (Kind Box, Kind Box)]
|
||||||
|
|
||||||
|
typeCheck :: Env -> Expr -> Either String Expr
|
||||||
|
typeCheck env (Var name) = findVar env name
|
||||||
|
typeCheck env (Abs param type_ body) = do
|
||||||
|
typeCheck env type_
|
||||||
|
retT <- typeCheck (extend env param type_) body
|
||||||
|
let funcT = Pi param type_ retT
|
||||||
|
typeCheck env funcT
|
||||||
|
return funcT
|
||||||
|
typeCheck env (Kind Star) = return (Kind Box)
|
||||||
|
typeCheck env (Kind Box) = Left "Found a box"
|
||||||
|
typeCheck env (Pi name value body) = do
|
||||||
|
valueT <- typeCheck env value
|
||||||
|
bodyT <- typeCheck (extend env name value) body
|
||||||
|
when ((valueT, bodyT) `notElem` allowedKinds) $ Left $
|
||||||
|
"bad abstraction: " ++ show valueT ++ " and " ++ show bodyT
|
||||||
|
return bodyT
|
||||||
|
typeCheck env (App func arg) = do
|
||||||
|
funcT <- typeCheck env func
|
||||||
|
case funcT of
|
||||||
|
Pi name value body -> do
|
||||||
|
argT <- typeCheck env arg
|
||||||
|
when (not $ betaEq value argT) $
|
||||||
|
Left $ "Cannot match types for function call: " ++ show value ++ " and " ++ show argT
|
||||||
|
return $ subst name arg body
|
||||||
|
_ -> Left "Calling a non-function"
|
||||||
|
|
||||||
|
typeCheckTopLevel :: Env -> TopLevel -> Either String Env
|
||||||
|
typeCheckTopLevel env (Declaration name value) = do
|
||||||
|
valueT <- typeCheck env value
|
||||||
|
return (extend env name value)
|
||||||
|
typeCheckTopLevel env (Definition name value) = do
|
||||||
|
valueT <- typeCheck env value
|
||||||
|
case findVar env name of
|
||||||
|
Right type_ -> do
|
||||||
|
when (not $ betaEq valueT type_) $ Left $
|
||||||
|
"Mismatched types between declaration and definition: " ++ show valueT ++ " and " ++ show type_
|
||||||
|
return env
|
||||||
|
Left _ -> return (extend env name valueT)
|
||||||
|
|
||||||
|
-- Interaction
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
[filename] <- getArgs
|
||||||
|
source <- readFile filename
|
||||||
|
case runParser (some topLevelP) source of
|
||||||
|
Just (program, []) ->
|
||||||
|
case foldM_ typeCheckTopLevel mempty program of
|
||||||
|
Right ty -> putStrLn "done."
|
||||||
|
Left err -> putStrLn err
|
||||||
|
Just (_, rest) -> putStrLn $ "Parse error at " ++ rest
|
||||||
|
Nothing -> putStrLn "Parse error"
|
81
Parser.hs
Normal file
81
Parser.hs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
|
||||||
|
module Parser
|
||||||
|
( module Parser
|
||||||
|
, module Control.Applicative
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
import Data.Char
|
||||||
|
import Debug.Trace
|
||||||
|
|
||||||
|
newtype Parser a = Parser { runParser :: String -> Maybe (a, String) }
|
||||||
|
instance Functor Parser where
|
||||||
|
fmap f (Parser p) = Parser $ \input -> do
|
||||||
|
(value, rest) <- p input
|
||||||
|
return (f value, rest)
|
||||||
|
|
||||||
|
instance Applicative Parser where
|
||||||
|
pure a = Parser $ \input -> Just (a, input)
|
||||||
|
(Parser f) <*> (Parser g) = Parser $ \input -> do
|
||||||
|
(fn, rest) <- f input
|
||||||
|
(value, rest') <- g rest
|
||||||
|
return (fn value, rest')
|
||||||
|
|
||||||
|
instance Monad Parser where
|
||||||
|
return = pure
|
||||||
|
(Parser a) >>= f = Parser $ \input -> do
|
||||||
|
(value, rest) <- a input
|
||||||
|
let Parser b = f value
|
||||||
|
in b rest
|
||||||
|
|
||||||
|
instance Alternative Parser where
|
||||||
|
empty = Parser $ const Nothing
|
||||||
|
(Parser a) <|> (Parser b) = Parser $ \input -> a input <|> b input
|
||||||
|
|
||||||
|
instance MonadFail Parser where
|
||||||
|
fail s = Parser $ const Nothing
|
||||||
|
|
||||||
|
charP :: Char -> Parser Char
|
||||||
|
charP c = Parser $ \case
|
||||||
|
[] -> Nothing
|
||||||
|
(x : xs)
|
||||||
|
| x == c -> Just (c, xs)
|
||||||
|
| otherwise -> Nothing
|
||||||
|
|
||||||
|
stringP :: String -> Parser String
|
||||||
|
stringP = sequence . map charP
|
||||||
|
|
||||||
|
predP :: (Char -> Bool) -> Parser Char
|
||||||
|
predP pred = Parser $ \case
|
||||||
|
[] -> Nothing
|
||||||
|
(x : xs)
|
||||||
|
| pred x -> Just (x, xs)
|
||||||
|
| otherwise -> Nothing
|
||||||
|
|
||||||
|
digitP :: Parser Char
|
||||||
|
digitP = predP isDigit
|
||||||
|
|
||||||
|
numberP :: Parser Int
|
||||||
|
numberP = read <$> some digitP
|
||||||
|
|
||||||
|
letterP :: Parser Char
|
||||||
|
letterP = predP (`notElem` [':','\\', 'λ', '.', ' ', '-', '>', '*', '(', ')', '=', '\n'])
|
||||||
|
|
||||||
|
wordP :: Parser String
|
||||||
|
wordP = some letterP
|
||||||
|
|
||||||
|
spaceP :: Parser ()
|
||||||
|
spaceP = () <$ predP isSpace
|
||||||
|
|
||||||
|
notFollowedBy :: Parser a -> Parser b -> Parser a
|
||||||
|
a `notFollowedBy` b = Parser $ \input -> do
|
||||||
|
(value, rest) <- runParser a input
|
||||||
|
case runParser b rest of
|
||||||
|
Just _ -> Nothing
|
||||||
|
Nothing -> Just (value, rest)
|
||||||
|
|
||||||
|
debug :: Show a => Parser a -> Parser a
|
||||||
|
debug p = Parser $ \input -> case runParser p input of
|
||||||
|
ret@(Just (value, rest)) -> trace ("Input: " ++ input ++ "\nParsed: " ++ show value ++ "\nRest: " ++ rest ++ " (" ++ show (length rest) ++ ")") ret
|
||||||
|
Nothing -> trace ("Input: " ++ input) Nothing
|
10
grammar.ebnf
Normal file
10
grammar.ebnf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
top-level ::= ident ':' expr
|
||||||
|
| ident '=' expr
|
||||||
|
| '--' text
|
||||||
|
|
||||||
|
expr ::= ('\' | 'λ') ident ':' expr '.' expr
|
||||||
|
| expr expr
|
||||||
|
| '(' ident ':' expr ')' '->' expr
|
||||||
|
| '(' expr ')' '->' expr
|
||||||
|
| '*'
|
||||||
|
| ident
|
Loading…
Reference in New Issue
Block a user