converted to Text

This commit is contained in:
William Ball 2024-11-14 22:02:04 -08:00
parent c73566d67f
commit 9ef9a8b6ba
6 changed files with 85 additions and 69 deletions

View file

@ -3,16 +3,17 @@ module Main where
import Check import Check
import Expr import Expr
import Parser import Parser
import qualified Data.Text.IO as T
import System.IO import System.IO
main :: IO () main :: IO ()
main = do main = do
_ <- putStr "> " _ <- T.putStr "> "
_ <- hFlush stdout _ <- hFlush stdout
input <- getLine input <- T.getLine
case pAll input of case pAll input of
Left err -> putStrLn err Left err -> T.putStrLn err
Right expr -> case findType [] expr of Right expr -> case findType [] expr of
Right ty -> putStrLn $ pretty expr ++ " : " ++ pretty ty Right ty -> T.putStrLn $ pretty expr <> " : " <> pretty ty
Left err -> print err Left err -> print err
main main

View file

@ -39,6 +39,8 @@ library dependent-lambda-lib
, parser-combinators , parser-combinators
, mtl , mtl
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings
, GADTs
common warnings common warnings
ghc-options: -Wall ghc-options: -Wall
@ -49,8 +51,11 @@ executable dependent-lambda
build-depends: base ^>=4.19.1.0 build-depends: base ^>=4.19.1.0
, dependent-lambda-lib , dependent-lambda-lib
, text
hs-source-dirs: app hs-source-dirs: app
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings
, GADTs
test-suite tests test-suite tests
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
@ -60,6 +65,9 @@ test-suite tests
, CheckTests , CheckTests
build-depends: base ^>=4.19.1.0 build-depends: base ^>=4.19.1.0
, HUnit , HUnit
, text
, dependent-lambda-lib , dependent-lambda-lib
hs-source-dirs: tests hs-source-dirs: tests
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings
, GADTs

View file

@ -2,20 +2,22 @@ module Check (TypeCheckError (..), CheckResult (..), findType) where
import Control.Monad.Except (MonadError (throwError)) import Control.Monad.Except (MonadError (throwError))
import Data.List (intercalate, (!?)) import Data.List (intercalate, (!?))
import Data.Text (Text)
import qualified Data.Text as T
import Control.Monad (unless) import Control.Monad (unless)
import Expr import Expr
type Context = [Expr] type Context = [Expr]
data TypeCheckError = SquareUntyped | UnboundVariable String | NotASort Expr Expr | ExpectedPiType Expr Expr | NotEquivalent Expr Expr Expr data TypeCheckError = SquareUntyped | UnboundVariable Text | NotASort Expr Expr | ExpectedPiType Expr Expr | NotEquivalent Expr Expr Expr deriving (Eq)
instance Show TypeCheckError where instance Show TypeCheckError where
show SquareUntyped = "□ does not have a type" show SquareUntyped = "□ does not have a type"
show (UnboundVariable x) = "Unbound variable: " ++ x show (UnboundVariable x) = "Unbound variable: " ++ T.unpack x
show (NotASort x t) = "Expected " ++ pretty x ++ " to have type * or □, instead found " ++ pretty t show (NotASort x t) = "Expected " ++ prettyS x ++ " to have type * or □, instead found " ++ prettyS t
show (ExpectedPiType m a) = pretty m ++ " : " ++ pretty a ++ " is not a function" show (ExpectedPiType m a) = prettyS m ++ " : " ++ prettyS a ++ " is not a function"
show (NotEquivalent a a' e) = "Cannot unify " ++ pretty a ++ " with " ++ pretty a' ++ " when evaluating " ++ pretty e show (NotEquivalent a a' e) = "Cannot unify " ++ prettyS a ++ " with " ++ prettyS a' ++ " when evaluating " ++ prettyS e
type CheckResult = Either TypeCheckError type CheckResult = Either TypeCheckError

View file

@ -1,16 +1,16 @@
{-# LANGUAGE GADTs #-}
module Expr where module Expr where
import Data.Function (on) import Data.Function (on)
import Data.Text (Text)
import qualified Data.Text as T
data Expr where data Expr where
Var :: Integer -> String -> Expr Var :: Integer -> Text -> Expr
Star :: Expr Star :: Expr
Square :: Expr Square :: Expr
App :: Expr -> Expr -> Expr App :: Expr -> Expr -> Expr
Abs :: String -> Expr -> Expr -> Expr Abs :: Text -> Expr -> Expr -> Expr
Pi :: String -> Expr -> Expr -> Expr Pi :: Text -> Expr -> Expr -> Expr
deriving (Show) deriving (Show)
instance Eq Expr where instance Eq Expr where
@ -32,58 +32,61 @@ occursFree n (Pi _ a b) = occursFree n a || occursFree (n + 1) b
{- --------------------- PRETTY PRINTING ----------------------------- -} {- --------------------- PRETTY PRINTING ----------------------------- -}
parenthesize :: String -> String parenthesize :: Text -> Text
parenthesize s = "(" ++ s ++ ")" parenthesize s = T.concat ["(", s, ")"]
collectLambdas :: Expr -> ([(String, Expr)], Expr) collectLambdas :: Expr -> ([(Text, Expr)], Expr)
collectLambdas (Abs x ty body) = ((x, ty) : params, final) collectLambdas (Abs x ty body) = ((x, ty) : params, final)
where where
(params, final) = collectLambdas body (params, final) = collectLambdas body
collectLambdas e = ([], e) collectLambdas e = ([], e)
collectPis :: Expr -> ([(String, Expr)], Expr) collectPis :: Expr -> ([(Text, Expr)], Expr)
collectPis p@(Pi "" _ _) = ([], p) collectPis p@(Pi "" _ _) = ([], p)
collectPis (Pi x ty body) = ((x, ty) : params, final) collectPis (Pi x ty body) = ((x, ty) : params, final)
where where
(params, final) = collectPis body (params, final) = collectPis body
collectPis e = ([], e) collectPis e = ([], e)
groupParams :: [(String, Expr)] -> [([String], Expr)] groupParams :: [(Text, Expr)] -> [([Text], Expr)]
groupParams = foldr addParam [] groupParams = foldr addParam []
where where
addParam :: (String, Expr) -> [([String], Expr)] -> [([String], Expr)] addParam :: (Text, Expr) -> [([Text], Expr)] -> [([Text], Expr)]
addParam (x, t) [] = [([x], t)] addParam (x, t) [] = [([x], t)]
addParam (x, t) l@((xs, s) : rest) addParam (x, t) l@((xs, s) : rest)
| incIndices t == s = (x : xs, t) : rest | incIndices t == s = (x : xs, t) : rest
| otherwise = ([x], t) : l | otherwise = ([x], t) : l
showParamGroup :: ([String], Expr) -> String showParamGroup :: ([Text], Expr) -> Text
showParamGroup (ids, ty) = parenthesize $ unwords ids ++ " : " ++ pretty ty showParamGroup (ids, ty) = parenthesize $ T.unwords ids <> " : " <> pretty ty
helper :: Integer -> Expr -> String helper :: Integer -> Expr -> Text
helper _ (Var _ s) = s helper _ (Var _ s) = s
helper _ Star = "*" helper _ Star = "*"
helper _ Square = "" helper _ Square = ""
helper k (App e1 e2) = if k > 3 then parenthesize res else res helper k (App e1 e2) = if k > 3 then parenthesize res else res
where where
res = helper 3 e1 ++ " " ++ helper 4 e2 res = helper 3 e1 <> " " <> helper 4 e2
helper k (Pi "" t1 t2) = if k > 2 then parenthesize res else res helper k (Pi "" t1 t2) = if k > 2 then parenthesize res else res
where where
res = helper 3 t1 ++ " -> " ++ helper 2 t2 res = helper 3 t1 <> " -> " <> helper 2 t2
helper k e@(Pi{}) = if k > 2 then parenthesize res else res helper k e@(Pi{}) = if k > 2 then parenthesize res else res
where where
(params, body) = collectPis e (params, body) = collectPis e
grouped = showParamGroup <$> groupParams params grouped = showParamGroup <$> groupParams params
res = "" ++ unwords grouped ++ " . " ++ pretty body res = "" <> T.unwords grouped <> " . " <> pretty body
helper k e@(Abs{}) = if k >= 1 then parenthesize res else res helper k e@(Abs{}) = if k >= 1 then parenthesize res else res
where where
(params, body) = collectLambdas e (params, body) = collectLambdas e
grouped = showParamGroup <$> groupParams params grouped = showParamGroup <$> groupParams params
res = "λ " ++ unwords grouped ++ " . " ++ pretty body res = "λ " <> T.unwords grouped <> " . " <> pretty body
pretty :: Expr -> String pretty :: Expr -> Text
pretty = helper 0 pretty = helper 0
prettyS :: Expr -> String
prettyS = T.unpack . pretty
{- --------------- ACTUAL MATH STUFF ---------------- -} {- --------------- ACTUAL MATH STUFF ---------------- -}
isSort :: Expr -> Bool isSort :: Expr -> Bool

View file

@ -7,20 +7,22 @@ import Data.Functor.Identity
import Data.List (elemIndex) import Data.List (elemIndex)
import Data.List.NonEmpty (NonEmpty ((:|))) import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import Data.Text (Text)
import qualified Data.Text as T
import Expr (Expr (..), incIndices) import Expr (Expr (..), incIndices)
import Text.Megaparsec hiding (State) import Text.Megaparsec hiding (State)
import Text.Megaparsec.Char import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L import qualified Text.Megaparsec.Char.Lexer as L
type InnerState = [String] type InnerState = [Text]
data CustomErrors = UnboundVariable String [String] deriving (Eq, Ord, Show) data CustomErrors = UnboundVariable Text [Text] deriving (Eq, Ord, Show)
instance ShowErrorComponent CustomErrors where instance ShowErrorComponent CustomErrors where
showErrorComponent (UnboundVariable var bound) = showErrorComponent (UnboundVariable var bound) =
"Unbound variable: " ++ var ++ ". Did you mean one of: " ++ unwords bound ++ "?" "Unbound variable: " ++ T.unpack var ++ ". Did you mean one of: " ++ T.unpack (T.unwords bound) ++ "?"
type Parser = ParsecT CustomErrors String (State InnerState) type Parser = ParsecT CustomErrors Text (State InnerState)
skipSpace :: Parser () skipSpace :: Parser ()
skipSpace = skipSpace =
@ -32,11 +34,11 @@ skipSpace =
lexeme :: Parser a -> Parser a lexeme :: Parser a -> Parser a
lexeme = L.lexeme skipSpace lexeme = L.lexeme skipSpace
pIdentifier :: Parser String pIdentifier :: Parser Text
pIdentifier = label "identifier" $ lexeme $ do pIdentifier = label "identifier" $ lexeme $ do
firstChar <- letterChar <|> char '_' firstChar <- letterChar <|> char '_'
rest <- many $ alphaNumChar <|> char '_' rest <- many $ alphaNumChar <|> char '_'
return $ firstChar : rest return $ T.pack (firstChar : rest) -- Still need T.pack here as we're building from chars
pVar :: Parser Expr pVar :: Parser Expr
pVar = label "variable" $ lexeme $ do pVar = label "variable" $ lexeme $ do
@ -46,12 +48,10 @@ pVar = label "variable" $ lexeme $ do
Just i -> return $ Var (fromIntegral i) var Just i -> return $ Var (fromIntegral i) var
Nothing -> customFailure $ UnboundVariable var binders Nothing -> customFailure $ UnboundVariable var binders
defChoice :: NE.NonEmpty String -> Parser () defChoice :: NE.NonEmpty Text -> Parser ()
defChoice options = lexeme $ label labelText $ void $ choice $ NE.map string options defChoice options = lexeme $ label (T.unpack $ NE.head options) $ void $ choice $ NE.map chunk options
where
labelText = NE.head options
pParamGroup :: Parser [(String, Expr)] pParamGroup :: Parser [(Text, Expr)]
pParamGroup = lexeme $ label "parameter group" $ between (char '(') (char ')') $ do pParamGroup = lexeme $ label "parameter group" $ between (char '(') (char ')') $ do
idents <- some pIdentifier idents <- some pIdentifier
_ <- defChoice $ ":" :| [] _ <- defChoice $ ":" :| []
@ -59,7 +59,7 @@ pParamGroup = lexeme $ label "parameter group" $ between (char '(') (char ')') $
modify (flip (foldl $ flip (:)) idents) modify (flip (foldl $ flip (:)) idents)
pure $ zip idents (iterate incIndices ty) pure $ zip idents (iterate incIndices ty)
pParams :: Parser [(String, Expr)] pParams :: Parser [(Text, Expr)]
pParams = concat <$> some pParamGroup pParams = concat <$> some pParamGroup
pLAbs :: Parser Expr pLAbs :: Parser Expr
@ -112,5 +112,5 @@ pAppTerm = lexeme $ pLAbs <|> pPAbs <|> pApp
pExpr :: Parser Expr pExpr :: Parser Expr
pExpr = lexeme $ try pArrow <|> pAppTerm pExpr = lexeme $ try pArrow <|> pAppTerm
pAll :: String -> Either String Expr pAll :: Text -> Either Text Expr
pAll input = first errorBundlePretty $ fst $ runIdentity $ runStateT (runParserT pExpr "" input) [] pAll input = first (T.pack . errorBundlePretty) $ fst $ runIdentity $ runStateT (runParserT pExpr "" input) []

View file

@ -1,5 +1,7 @@
module ParserTests (tests) where module ParserTests (tests) where
import Data.Text (Text)
import qualified Data.Text as T
import Expr (Expr (..)) import Expr (Expr (..))
import Parser (pAll) import Parser (pAll)
import Test.HUnit import Test.HUnit