Restructured

This commit is contained in:
Leon 2023-06-15 18:00:02 +02:00
parent 02d2cd82ac
commit 2974c046b9
17 changed files with 439 additions and 112 deletions

View file

@ -23,7 +23,7 @@
# The "main" project. You can have multiple projects, but this template
# has only one.
haskellProjects.main = {
packages.lambda.root = ./.; # Auto-discovered by haskell-flake
packages.typed-lambda.root = ./.; # Auto-discovered by haskell-flake
overrides = self: super: { };
devShell = {
tools = hp: {
@ -87,7 +87,7 @@
};
# Default package.
packages.default = self'.packages.main-lambda;
packages.default = self'.packages.main-typed-lambda;
# Default shell.
devShells.default =

View file

@ -0,0 +1,16 @@
module Compilation.Compiler where
import Compilation.Lift (lift)
import Language.Declarations (Declaration)
import Parsing.Parser (parseFile)
import System.IO (IOMode (ReadMode), hGetContents, openFile)
runCompiler :: String -> IO ()
runCompiler file = do
-- parse file
handle <- openFile file ReadMode
content <- hGetContents handle
case parseFile file content of
Left err -> print err
Right decls -> do
mapM_ print $ lift decls

53
src/Compilation/Lift.hs Normal file
View file

@ -0,0 +1,53 @@
module Compilation.Lift where
import Control.Monad.State (State, execState, gets, modify, runState)
import Language.Declarations (Declaration (..))
import Language.Terms
funNames :: [String]
funNames = ["anon$" ++ show n | n <- [0 ..]]
data LiftState = LiftState
{ anonCounter :: Int
, anonDeclarations :: [Declaration]
}
type LiftMonad = State LiftState
lift :: [Declaration] -> [Declaration]
lift decls = newDecls ++ anonDeclarations endState
where
(newDecls, endState) = runState liftDecls (LiftState 0 [])
liftDecls :: LiftMonad [Declaration]
liftDecls = mapM liftDecl decls
liftDecl :: Declaration -> LiftMonad Declaration
liftDecl (Declaration name typ (Abs x t)) = do
newTerm <- liftTerm t
return $ Declaration name typ (Abs x newTerm)
liftDecl (Declaration name typ term) = do
newTerm <- liftTerm term
return $ Declaration name typ newTerm
liftTerm :: Term -> LiftMonad Term
liftTerm t@(Var _) = return t
liftTerm (App t1 t2) = do
t1' <- liftTerm t1
t2' <- liftTerm t2
return $ App t1' t2'
liftTerm (Abs x t) = do
-- first lift all terms in t
t' <- liftTerm t
-- add global declaration for lambda
newName <- getAnonName
let decl = Declaration newName Nothing t'
anonDecls <- gets anonDeclarations
modify (\s -> s {anonDeclarations = decl : anonDecls})
return $ Var newName
getAnonName :: LiftMonad String
getAnonName = do
count <- gets anonCounter
modify (\s -> s {anonCounter = count + 1})
return $ "anon$" ++ show count

View file

@ -0,0 +1,56 @@
module Compilation.ToImmediate where
import Control.Monad.State
import Language.Declarations
import Language.Immediate
import Language.Terms
-- Transforms a list of declarations to a list of immediate statements
-- state for lambda lifting
data LiftState = LiftState
{ anonCounter :: Int
, anonDeclarations :: [ImmediateDeclaration]
}
type LiftMonad = State LiftState
lift :: [Declaration] -> [ImmediateDeclaration]
lift decls = newDecls ++ anonDeclarations endState
where
(newDecls, endState) = runState liftDecls (LiftState 0 [])
liftDecls :: LiftMonad [ImmediateDeclaration]
liftDecls = mapM liftDecl decls
liftDecl :: Declaration -> LiftMonad ImmediateDeclaration
liftDecl (Declaration name typ term) = do
newTerm <- liftTerm term
return $ ImmediateDeclaration name newTerm
buildFunction :: Term -> LiftMonad Immediate
buildFunction (Abs x t) = do
t' <- buildFunction t
return $ case t' of
(IVar v) -> IFunction [x] (IVar v)
(IApp i1 i2) -> IFunction [x] (IApp i1 i2)
(IFunction xs i) -> IFunction (x : xs) i
buildFunction t = liftTerm t
liftTerm :: Term -> LiftMonad Immediate
liftTerm (Abs x t) = do
-- first lift all terms in t
t' <- liftTerm t
-- add global declaration for lambda
newName <- getAnonName
-- TODO
let decl = Declaration newName Nothing t'
anonDecls <- gets anonDeclarations
modify (\s -> s {anonDeclarations = decl : anonDecls})
return $ Var newName
getAnonName :: LiftMonad String
getAnonName = do
count <- gets anonCounter
modify (\s -> s {anonCounter = count + 1})
return $ "anon$" ++ show count

View file

@ -1,4 +1,4 @@
module Reduction (reduce) where
module Interpretation.Reduction (reduce) where
import Control.Monad (when)
import Data.Char (toLower)
@ -9,7 +9,7 @@ import Data.Maybe (fromMaybe)
import Data.Maybe qualified as Maybe
import Data.Set (Set)
import Data.Set qualified as Set
import Syntax (Substitution, Term (..), Var)
import Language.Terms (Substitution, Term (..), Var)
-- a stock of variablenames that is used for getting fresh variables
vars :: [Var]
@ -73,24 +73,27 @@ showResult :: Map String Term -> Term -> IO ()
showResult globals t = do
putStr "result: "
print t
case parseChurchNumeral t of
Nothing -> return ()
Just x -> putStr "as church-numeral: " >> print x
case parseChurchBoolean t of
Nothing -> return ()
Just x -> putStr "as church-boolean: " >> putStrLn (map toLower $ show x)
case parseFoldList t of
Nothing -> return ()
Just x -> do
let numeralList = mapM parseChurchNumeral x
case numeralList of
Nothing -> return ()
Just x -> putStr "as fold-list (numeral):" >> print x
let booleanList = mapM parseChurchBoolean x
case booleanList of
Nothing -> return ()
Just x -> putStr "as fold-list (boolean):" >> print x
putStrLn ""
{-
case parseChurchNumeral t of
Nothing -> return ()
Just x -> putStr "as church-numeral: " >> print x
case parseChurchBoolean t of
Nothing -> return ()
Just x -> putStr "as church-boolean: " >> putStrLn (map toLower $ show x)
case parseFoldList t of
Nothing -> return ()
Just x -> do
let numeralList = mapM parseChurchNumeral x
case numeralList of
Nothing -> return ()
Just x -> putStr "as fold-list (numeral):" >> print x
let booleanList = mapM parseChurchBoolean x
case booleanList of
Nothing -> return ()
Just x -> putStr "as fold-list (boolean):" >> print x
putStrLn ""
-}
parseChurchNumeral :: Term -> Maybe Int
parseChurchNumeral (Abs f (Abs a (Var a'))) | a == a' = Just 0

View file

@ -0,0 +1,76 @@
module Interpretation.Repl (runInterpreter) where
import Control.Exception (SomeException (SomeException), catch)
import Control.Monad.State (
MonadIO (liftIO),
MonadTrans (lift),
StateT,
evalStateT,
gets,
lift,
modify,
)
import Data.Functor (($>))
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Maybe (fromJust)
import Interpretation.Reduction (reduce)
import Language.Declarations (Declaration (..))
import Language.Terms (Term)
import Parsing.Parser (parseLine, parsePrelude, readP)
import System.Console.Haskeline (
InputT,
defaultSettings,
getInputLine,
runInputT,
)
import System.IO (IOMode (ReadMode), hGetContents, openFile)
-- REPL
newtype ReplState = ReplState {declarations :: Map String Term}
insertDeclaration :: Declaration -> ReplM ()
insertDeclaration (Declaration name typ term) = modify (\state -> state {declarations = Map.insert name term (declarations state)})
type ReplM = StateT ReplState IO
initialState :: ReplState
initialState = ReplState . Map.fromList $ map (\(Declaration name typ term) -> (name, term)) []
runInterpreter :: IO ()
runInterpreter = evalStateT (readPrelude >> runInputT defaultSettings repl) initialState
where
repl :: InputT ReplM ()
repl = do
minput <- getInputLine "λ "
case minput of
Nothing -> return ()
Just line -> do
lift $ evalLine line
repl
-- Try reading prelude definitions from prelude.lmd
readPrelude :: ReplM ()
readPrelude = do
decls <- liftIO $ catch getDecls handleException
mapM_ insertDeclaration decls
where
getDecls :: IO [Declaration]
getDecls = do
handle <- liftIO $ openFile "prelude.lmd" ReadMode
content <- liftIO $ hGetContents handle
let declsE = parsePrelude content
case declsE of
Left err -> putStrLn ("Error while parsing prelude:\n" ++ show err) $> []
Right decls -> putStrLn "Prelude successfully read from prelude.lmd!" $> decls
handleException :: SomeException -> IO [Declaration]
handleException e = putStrLn "Couldn't read file prelude.lmd so no prelude was loaded." >> return []
-- evaluate a single line from stdin
evalLine :: String -> ReplM ()
evalLine "" = return ()
evalLine line = either (liftIO . print) computeLine $ parseLine line
-- do computations for a single line
computeLine :: Either Declaration Term -> ReplM ()
computeLine (Left decl) = insertDeclaration decl
computeLine (Right term) = do
decls <- gets declarations
liftIO $ reduce 0 False decls term

View file

@ -0,0 +1,10 @@
module Language.Declarations where
import Language.Terms (Term)
import Language.Types (Type)
data Declaration = Declaration String (Maybe Type) Term
instance Show Declaration where
show :: Declaration -> String
show (Declaration name (Just typ) term) = name ++ " :: " ++ show typ ++ " := " ++ show term
show (Declaration name Nothing term) = name ++ " := " ++ show term

21
src/Language/Immediate.hs Normal file
View file

@ -0,0 +1,21 @@
module Language.Immediate where
data Immediate
= IVar String
| IApp Immediate Immediate
| IFunction [String] Immediate
deriving (Eq, Ord)
instance Show Immediate where
show :: Immediate -> String
show (IVar v) = v
show (IApp i1 i2) = showHelper i1 False ++ " " ++ showHelper i2 True
where
showHelper :: Immediate -> Bool -> String
showHelper (IVar v) True = v
showHelper (IApp i1 i2) True = "(" ++ showHelper i1 False ++ " " ++ showHelper i2 True ++ ")"
showHelper (IFunction params i) True = "(" ++ show params ++ " -> " ++ show i ++ ")"
show (IFunction params i) = show params ++ " -> " ++ show i
data ImmediateDeclaration = ImmediateDeclaration String Immediate
deriving (Eq, Ord, Show)

View file

@ -1,10 +1,8 @@
module Syntax where
module Language.Terms where
type Var = String
type Substitution = Var -> Term
data Declaration = Declaration String Term
data Term
= Var Var
| App Term Term
@ -27,7 +25,3 @@ instance Show Term where
absHelper (Abs v t) True = "(\\" ++ v ++ "." ++ absHelper t False ++ ")"
absHelper (Abs v t) False = "\\" ++ v ++ "." ++ absHelper t False
absHelper t _ = show t
instance Show Declaration where
show :: Declaration -> String
show (Declaration name term) = name ++ " = " ++ show term

50
src/Language/Types.hs Normal file
View file

@ -0,0 +1,50 @@
{-# LANGUAGE InstanceSigs #-}
module Language.Types where
import Data.List (intercalate)
import Data.Map (Map)
import Data.Map.Strict qualified as Map
import Language.Terms (Term)
-- Types
data Type
= TypeVar String
| BaseType String
| Arrow Type Type
deriving (Eq, Ord)
instance Show Type where
show :: Type -> String
show (TypeVar v) = v
show (BaseType b) = b
show (Arrow t1 t2) = go t1 ++ " -> " ++ show t2
where
go (Arrow t3 t4) = "(" ++ go t3 ++ " -> " ++ show t4 ++ ")"
go t = show t
-- type variables occuring in type
typeVars :: Type -> [String]
typeVars (TypeVar v) = [v]
typeVars (BaseType _) = []
typeVars (Arrow t1 t2) = typeVars t1 ++ typeVars t2
-- substitute a type for a type variable
typeSubst :: Type -> String -> Type -> Type
typeSubst tp@(TypeVar x) y sp = if x == y then sp else tp
typeSubst tp@(BaseType _) _ _ = tp
typeSubst (Arrow tp1 tp2) y tp3 = Arrow (typeSubst tp1 y tp3) (typeSubst tp2 y tp3)
-- Contexts
type Context = Map String Type
showContext :: Context -> String
showContext ctx | Map.null ctx = ""
showContext ctx = "[" ++ intercalate ", " (map (\(name, tp) -> name ++ " : " ++ show tp) $ Map.toList ctx) ++ "]"
emptyContext :: Context
emptyContext = Map.empty
-- Term in Context
data TermInContext = TermInContext Context Term Type
instance Show TermInContext where
show :: TermInContext -> String
show (TermInContext ctx term tp) = showContext ctx ++ " |- " ++ show term ++ " : " ++ show tp

View file

@ -1,75 +1,7 @@
module Main where
import Control.Exception (SomeException (SomeException), catch)
import Control.Monad.State (
MonadIO (liftIO),
MonadTrans (lift),
StateT,
evalStateT,
gets,
lift,
modify,
)
import Data.Functor (($>))
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Maybe (fromJust)
import Parser (parseLine, parsePrelude, readP)
import Reduction (reduce)
import Syntax (Declaration (..), Term)
import System.Console.Haskeline (
InputT,
defaultSettings,
getInputLine,
runInputT,
)
import System.IO (IOMode (ReadMode), hGetContents, openFile)
-- REPL
newtype ReplState = ReplState {declarations :: Map String Term}
insertDeclaration :: Declaration -> ReplM ()
insertDeclaration (Declaration name term) = modify (\state -> state {declarations = Map.insert name term (declarations state)})
type ReplM = StateT ReplState IO
initialState :: ReplState
initialState = ReplState . Map.fromList $ map (\(Declaration name term) -> (name, term)) []
import Compilation.Compiler (runCompiler)
import Interpretation.Repl (runInterpreter)
main :: IO ()
main = evalStateT (readPrelude >> runInputT defaultSettings repl) initialState
where
repl :: InputT ReplM ()
repl = do
minput <- getInputLine "λ "
case minput of
Nothing -> return ()
Just line -> do
lift $ evalLine line
repl
-- Try reading prelude definitions from prelude.lmd
readPrelude :: ReplM ()
readPrelude = do
decls <- liftIO $ catch getDecls handleException
mapM_ insertDeclaration decls
where
getDecls :: IO [Declaration]
getDecls = do
handle <- liftIO $ openFile "prelude.lmd" ReadMode
content <- liftIO $ hGetContents handle
let declsE = parsePrelude content
case declsE of
Left err -> putStrLn ("Error while parsing prelude:\n" ++ show err) $> []
Right decls -> putStrLn "Prelude successfully read from prelude.lmd!" $> decls
handleException :: SomeException -> IO [Declaration]
handleException e = putStrLn "Couldn't read file prelude.lmd so no prelude was loaded." >> return []
-- evaluate a single line from stdin
evalLine :: String -> ReplM ()
evalLine "" = return ()
evalLine line = either (liftIO . print) computeLine $ parseLine line
-- do computations for a single line
computeLine :: Either Declaration Term -> ReplM ()
computeLine (Left decl) = insertDeclaration decl
computeLine (Right term) = do
decls <- gets declarations
liftIO $ reduce 0 False decls term
main = runCompiler "test.lmd"

View file

@ -1,6 +1,6 @@
{-# LANGUAGE ImportQualifiedPost #-}
module Lexer where
module Parsing.Lexer where
import Text.Parsec.Char (alphaNum, letter, oneOf)
import Text.Parsec.Language (emptyDef)
@ -10,14 +10,15 @@ import Text.Parsec.Token qualified as Tok
lexer :: Tok.TokenParser ()
lexer = Tok.makeTokenParser style
where
ops = ["\\", "="]
ops = ["\\", ":=", "::", "->"]
names = []
style =
emptyDef
{ Tok.commentLine = "#"
, Tok.reservedOpNames = ops
, Tok.reservedNames = names
, Tok.opLetter = oneOf "\\=."
, Tok.opStart = oneOf "\\=:-"
, Tok.opLetter = oneOf "\\=>:"
, Tok.identStart = letter
, Tok.identLetter = alphaNum
}

View file

@ -1,8 +1,11 @@
module Parser (parseLine, readP, parsePrelude) where
module Parsing.Parser (parseLine, readP, parsePrelude, parseFile) where
import Data.Char (isUpper)
import Data.Either.Extra (eitherToMaybe)
import Lexer (digit, dot, identifier, parens, reservedOp, semi, whiteSpace)
import Syntax (Declaration (..), Term (..))
import Language.Declarations (Declaration (..))
import Language.Terms (Term (..))
import Language.Types (Type (Arrow, BaseType, TypeVar))
import Parsing.Lexer (digit, dot, identifier, parens, reservedOp, semi, whiteSpace)
import Text.Parsec (
ParseError,
endBy,
@ -10,8 +13,10 @@ import Text.Parsec (
many,
many1,
newline,
optionMaybe,
parse,
sepBy,
sepBy1,
try,
(<?>),
(<|>),
@ -26,7 +31,8 @@ declaration :: Parser Declaration
declaration =
Declaration
<$> identifier
<*> (reservedOp "=" *> term)
<*> optionMaybe (reservedOp "::" *> typ)
<*> (reservedOp ":=" *> term)
<?> "declaration"
term :: Parser Term
@ -61,6 +67,20 @@ abs =
<*> (dot *> term)
<?> "lambda abstraction"
typ :: Parser Type
typ = arrow <?> "type"
arrow :: Parser Type
arrow = foldr1 Arrow <$> sepBy1 atomicType (reservedOp "->")
atomicType :: Parser Type
atomicType = do
ident <- identifier
return $
if isUpper (head ident)
then BaseType ident
else TypeVar ident
-- runners
contents :: Parser a -> Parser a
contents p = do
@ -75,5 +95,8 @@ parseLine = Text.Parsec.parse (contents line) "stdin"
parsePrelude :: String -> Either ParseError [Declaration]
parsePrelude = Text.Parsec.parse (contents (declaration `endBy` semi)) "prelude.lmd"
parseFile :: String -> String -> Either ParseError [Declaration]
parseFile = Text.Parsec.parse (contents (declaration `endBy` semi))
readP :: String -> Maybe Term
readP line = eitherToMaybe (Text.Parsec.parse (contents term) "sourcefile" line)

View file

@ -0,0 +1,48 @@
module Typechecking.TypeInference where
import Control.Monad.State.Strict (
State,
evalState,
gets,
modify,
)
import Data.Map.Strict qualified as Map
import Language.Terms (Term (..))
import Language.Types (Context, TermInContext (..), Type (Arrow, TypeVar))
import Typechecking.Unification (Unifier, applyMgu, unify)
-- state needed for algorithm W
newtype PTState = PTState
{ varCount :: Int
}
type PTMonad = State PTState
-- returns a fresh type var in algorithm W
getNewTypeVar :: PTMonad String
getNewTypeVar = do
count <- gets varCount
modify (\s -> s {varCount = count + 1})
return $ 'a' : show count
-- implementation of algorithm W, this builds a list of terms to be unified in the next step
pt :: Context -> Term -> Type -> PTMonad Unifier
pt gamma (Var x) alpha = return [(alpha, gamma Map.! x)]
pt gamma (Abs x t) alpha = do
a <- TypeVar <$> getNewTypeVar
b <- TypeVar <$> getNewTypeVar
result <- pt (Map.insert x a gamma) t b
return $ result ++ [(Arrow a b, alpha)]
pt gamma (App t s) alpha = do
a <- TypeVar <$> getNewTypeVar
result1 <- pt gamma t (Arrow a alpha)
result2 <- pt gamma s a
return $ result1 ++ result2
-- infer the type of a term in context (if any)
inferType :: Context -> Term -> Maybe TermInContext
inferType ctx term = do
let unificationProblem = evalState (pt ctx term (TypeVar "a0")) (PTState 1)
mgu <- unify unificationProblem
let principalType = applyMgu (TypeVar "a0") mgu
return $ TermInContext ctx term principalType

View file

@ -0,0 +1,35 @@
module Typechecking.Unification where
import Language.Types (Type (..), typeSubst, typeVars)
type Unifier = [(Type, Type)]
-- unification algorithm of martelli montanari
unify :: Unifier -> Maybe Unifier
unify [] = Just []
-- (delete)
unify ((TypeVar x, TypeVar y) : rest) | x == y = unify rest
-- (decomp)
unify ((Arrow t1 t2, Arrow t1' t2') : rest) = unify $ [(t1, t1'), (t2, t2')] ++ rest
unify ((BaseType b1, BaseType b2) : rest) | b1 == b2 = unify rest
-- (conflict)
unify ((BaseType b1, BaseType b2) : _) | b1 /= b2 = Nothing
-- (orient)
unify ((Arrow t1 t2, TypeVar x) : rest) = unify $ (TypeVar x, Arrow t1 t2) : rest
unify ((BaseType b, TypeVar x) : rest) = unify $ (TypeVar x, BaseType b) : rest
-- (occurs)
unify ((TypeVar x, t) : _) | x `elem` typeVars t && TypeVar x /= t = Nothing
-- (elim)
unify ((TypeVar x, t) : rest) | notElem x (typeVars t) && x `elem` concatMap (\(t1, t2) -> typeVars t1 ++ typeVars t2) rest = unify $ (TypeVar x, t) : map (\(t1, t2) -> (typeSubst t1 x t, typeSubst t2 x t)) rest
-- descent
unify ((t, s) : rest) = do
rest' <- unify rest
return $ (t, s) : rest'
-- apply a mgu to some type
applyMgu :: Type -> Unifier -> Type
applyMgu (TypeVar x) [] = TypeVar x
applyMgu (TypeVar x) ((TypeVar y, tp) : rest) = if x == y then tp else applyMgu (TypeVar x) rest
applyMgu (BaseType b) _ = BaseType b
applyMgu (Arrow t1 t2) mgu = Arrow (applyMgu t1 mgu) (applyMgu t2 mgu)
applyMgu _ (tp : _) = error $ "Non primitive type '" ++ show tp ++ "' on left side of mgu"

1
test.lmd Normal file
View file

@ -0,0 +1 @@
k :: Nat -> Nat -> Nat := \x.\y.x;

View file

@ -1,5 +1,5 @@
cabal-version: 2.4
name: lambda
name: typed-lambda
version: 0.1.0.0
license: MIT
author: Leon Vatthauer
@ -28,7 +28,15 @@ executable lambda
import: shared
main-is: Main.hs
other-modules:
Lexer
Parser
Reduction
Syntax
Parsing.Lexer
Parsing.Parser
Interpretation.Reduction
Interpretation.Repl
Language.Terms
Language.Types
Language.Declarations
Language.Immediate
Typechecking.Unification
Typechecking.TypeInference
Compilation.Lift
Compilation.Compiler