diff --git a/CHANGELOG.md b/CHANGELOG.md index be25259..c1f2b4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changelog for `purist` +# Changelog for `pure` All notable changes to this project will be documented in this file. diff --git a/README.md b/README.md index a6a22cc..cf898e4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# `purist` - Pure Dev Suite 🧰 +# `pure` - Pure Dev Suite 🧰 Pure is a @@ -27,15 +27,15 @@ We hope that Pure will be able to target the following use cases: You may use the installation script: ```bash -source <(curl -s https://raw.githubusercontent.com/prog-lang/purist/main/install.sh) +source <(curl -s https://raw.githubusercontent.com/prog-lang/pure/main/install.sh) ``` Or execute these commands manually (the script does the same as the following code snippet): ```bash -git clone git@github.com:prog-lang/purist.git -cd purist +git clone git@github.com:prog-lang/pure.git +cd pure stack install ``` @@ -49,7 +49,7 @@ on UNIX systems and probably something similar on Windows. Therefore, uninstalling is extremely straightforward: ```bash -rm $(which purist) +rm $(which pure) ``` ## Usage Basics 👷‍♀️ @@ -57,10 +57,10 @@ rm $(which purist) The following command will transpile `someModule.pure` into `someModule.js`. ```bash -purist c < someModule.pure > someModule.js +pure compile someModule.pure > someModule.js ``` -**NOTE:** Executing `purist` with no arguments displays the _help_ message. Use +**NOTE:** Executing `pure` with no arguments displays the _help_ message. Use it to get acquainted with its capabilities. ## Develop 👨‍💻 @@ -73,10 +73,10 @@ We are working hard to give you a tool you can be excited about. ### Progress - [x] [Parser](./src/Pure/Parser.hs) -- [x] [Module (AST)](./src/Pure.hs) -- [ ] Type Checker with Inference -- [ ] [Transpiler $\to$ Node.js](./src/Node/Transpiler.hs) -- [ ] Transpiler $\to$ Go +- [x] [Type Checker with Inference](./src/Pure/Typing/) +- [x] [Transpiler to Node.js](./src/Node/Transpiler.hs) +- [ ] `when x is` expressions +- [ ] `trait T a can` and `proof T R can` typing ### Vision @@ -90,9 +90,14 @@ ever touched Elm or Haskell, you will know what I'm on about. ### Inspiration +- [Elm][elm] - a delightful language for reliable web applications - [Duet][duet] - a subset of Haskell aimed at aiding teachers teach Haskell - [PureScript][ps] - a strongly-typed language that compiles to JavaScript +[elm]: https://elm-lang.org/ +[duet]: https://github.com/chrisdone/duet +[ps]: https://github.com/purescript/purescript + ### Syntactic Analysis - [Parsec tutorial][parsecTutorial] and some conveniences like @@ -111,8 +116,6 @@ ever touched Elm or Haskell, you will know what I'm on about. - [Programming Languages: Application and Interpretation][langs-ch.15] - chapter 15: Checking Program Invariants Statically: Types -[duet]: https://github.com/chrisdone/duet -[ps]: https://github.com/purescript/purescript [thih]: https://github.com/ocramz/thih [thih-pdf]: https://web.cecs.pdx.edu/~mpj/thih/thih.pdf [warn]: http://moscova.inria.fr/~maranget/papers/warn/warn.pdf diff --git a/app/Main.hs b/app/Main.hs index ce9268b..fdb43bc 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -18,7 +18,7 @@ main = runIO app app :: Application app = application - "purist" + "pure" (showVersion version) "Development suite for the Pure programming language" ["Viktor A. Rozenko Voitenko "] diff --git a/install.sh b/install.sh index 393d0d0..868624f 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -git clone git@github.com:prog-lang/purist.git -cd purist +git clone git@github.com:prog-lang/pure.git +cd pure stack install cd .. -rm -rf purist +rm -rf pure diff --git a/src/Pure/Expr.hs b/src/Pure/Expr.hs index 79ca3ca..92ba1cf 100644 --- a/src/Pure/Expr.hs +++ b/src/Pure/Expr.hs @@ -1,55 +1,85 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + module Pure.Expr (Expr (..), positionOf) where +import Data.Char (isUpperCase) +import Data.Set (Set) +import qualified Data.Set as Set import qualified Pure.Sacred as S +import Pure.Typing.Free (Free (..)) import Text.Parsec (SourcePos) import Utility.Common (Id) -import Utility.Strings (Parens (..), list, parenthesised, (+-+)) +import Utility.Strings (Parens (..), parenthesised, (+-+)) -- EXPRESSION ------------------------------------------------------------------ data Expr = Lam Id Expr SourcePos + | XLam Expr Expr SourcePos | If Expr Expr Expr SourcePos | App Expr Expr SourcePos - | List [Expr] SourcePos | Id Id SourcePos | Str String SourcePos | Float Double SourcePos | Int Integer SourcePos | Bool Bool SourcePos + | -- XLam + When Expr [Expr] SourcePos deriving (Eq) +-- FREE ------------------------------------------------------------------------ + +instance Free Expr where + free (Lam i e _) = Set.union (freeVar i) (free e) + free (XLam e1 e2 _) = Set.unions $ map free [e1, e2] + free (If e1 e2 e3 _) = Set.unions $ map free [e1, e2, e3] + free (App e1 e2 _) = Set.unions $ map free [e1, e2] + free (Id i _) = freeVar i + free _ = Set.empty + +freeVar :: Id -> Set Id +freeVar i@(l : _) = + if l == S.underscore || isUpperCase l + then Set.empty + else Set.singleton i +freeVar "" = undefined + -- POSITION -------------------------------------------------------------------- -positionOf :: Expr -> SourcePos -positionOf (Lam _ _ pos) = pos -positionOf (If _ _ _ pos) = pos -positionOf (App _ _ pos) = pos -positionOf (List _ pos) = pos -positionOf (Id _ pos) = pos -positionOf (Str _ pos) = pos -positionOf (Float _ pos) = pos -positionOf (Int _ pos) = pos -positionOf (Bool _ pos) = pos +class Position a where + positionOf :: a -> SourcePos + +instance Position Expr where + positionOf (Lam _ _ pos) = pos + positionOf (When _ _ pos) = pos + positionOf (XLam _ _ pos) = pos + positionOf (If _ _ _ pos) = pos + positionOf (App _ _ pos) = pos + positionOf (Id _ pos) = pos + positionOf (Str _ pos) = pos + positionOf (Float _ pos) = pos + positionOf (Int _ pos) = pos + positionOf (Bool _ pos) = pos -- SHOW ------------------------------------------------------------------------ instance Parens Expr where - parens b@(Bool _ _) = show b - parens i@(Int _ _) = show i - parens f@(Float _ _) = show f - parens s@(Str _ _) = show s - parens i@(Id _ _) = show i - parens l@(List _ _) = show l + parens literal@(Id _ _) = show literal + parens literal@(Str _ _) = show literal + parens literal@(Float _ _) = show literal + parens literal@(Int _ _) = show literal + parens literal@(Bool _ _) = show literal parens ex = parenthesised $ show ex instance Show Expr where - show (Bool bool _) = show bool - show (Int int _) = show int - show (Float number _) = show number + show (Id i _) = i show (Str str _) = show str - show (Id ident _) = ident - show (List l _) = list (map show l) + show (Float f _) = show f + show (Int int _) = show int + show (Bool bool _) = show bool show (App f arg _) = show f +-+ parens arg show (If x y z _) = S.if_ +-+ show x +-+ S.then_ +-+ show y +-+ S.else_ +-+ show z - show (Lam p ex _) = p +-+ S.arrow +-+ show ex \ No newline at end of file + show (Lam from to _) = from +-+ S.arrow +-+ show to + show (XLam from to _) = parens from +-+ S.arrow +-+ show to + show (When x opts _) = S.when +-+ show x +-+ S.is +-+ unwords (map show opts) diff --git a/src/Pure/Parser.hs b/src/Pure/Parser.hs index 84fdba0..d81d216 100644 --- a/src/Pure/Parser.hs +++ b/src/Pure/Parser.hs @@ -4,7 +4,6 @@ module Pure.Parser ( Module (..), - Id, moduleNames, assignmentNames, typeHintNames, @@ -189,11 +188,9 @@ definitionP = Def <$> (typeDefP <|> try typeHintP <|> defP) typeDefP :: Parser Def typeDefP = do - _ <- reservedP S.type_ - name <- upperNameP + name <- reservedP S.type_ >> upperNameP params <- many lowerNameP - _ <- reservedP S.is >> barP - cons <- sepBy1 typeConsP barP + cons <- reservedP S.is >> barP >> sepBy1 typeConsP barP return $ TypeDef name params cons where barP = reservedOp parser [S.bar] @@ -201,8 +198,7 @@ typeDefP = do typeHintP :: Parser Def typeHintP = do name <- nameP - _ <- reservedOp parser S.typed - ty <- typeP + ty <- reservedOp parser S.typed >> typeP return $ TypeHint name ty typeConsP :: Parser Type @@ -243,12 +239,30 @@ typeVarP = do defP :: Parser Def defP = do name <- nameP - _ <- reservedOp parser S.walrus - expr <- exprP + expr <- reservedOp parser S.walrus >> exprP return $ ValueDef name expr exprP :: Parser Expr -exprP = try ifP <|> try lambdaP <|> try appP <|> literalP "an expression" +exprP = + -- try whenP + try ifP + <|> try lambdaP + <|> try appP + <|> literalP + "an expression" + +-- whenP :: Parser Expr +-- whenP = do +-- pos <- sourcePos +-- e <- between (reservedP S.when) (reservedP S.is) notIfP +-- brs <- barP >> sepBy1 branchP barP +-- return $ When e brs pos +-- where +-- barP = reservedOp parser [S.bar] +-- branchP = do +-- pat <- litP <* reservedP S.then_ +-- result <- exprP +-- return (pat, result) ifP :: Parser Expr ifP = do @@ -265,7 +279,7 @@ notIfP = try lambdaP <|> try appP <|> literalP "a condition" lambdaP :: Parser Expr lambdaP = do pos <- sourcePos - param <- nameP <* reservedOp parser S.arrow "a named parameter" + param <- lowerNameP <* reservedOp parser S.arrow "a named parameter" expr <- exprP return $ Lam param expr pos @@ -281,21 +295,22 @@ callerP :: Parser Expr callerP = parensP exprP <|> try qualifiedP <|> idP literalP :: Parser Expr -literalP = - parensP exprP - <|> listP - <|> strP +literalP = parensP exprP <|> litP + +litP :: Parser Expr +litP = + strP <|> try boolP <|> try qualifiedP <|> try idP <|> try floatP <|> intP -listP :: Parser Expr -listP = do - pos <- sourcePos - list <- brackets parser $ commaSep1 parser exprP - return $ List list pos +-- listP :: Parser Expr +-- listP = do +-- pos <- sourcePos +-- list <- brackets parser $ commaSep1 parser exprP +-- return $ List list pos qualifiedP :: Parser Expr qualifiedP = do diff --git a/src/Pure/Sacred.hs b/src/Pure/Sacred.hs index e2a3e03..3051590 100644 --- a/src/Pure/Sacred.hs +++ b/src/Pure/Sacred.hs @@ -10,7 +10,7 @@ isKeyword :: String -> Bool isKeyword = flip List.elem keywords keywords :: [String] -keywords = [export, if_, then_, else_, type_, is] +keywords = [export, if_, then_, else_, type_, when, is] export :: String export = "export" @@ -27,6 +27,9 @@ else_ = "else" type_ :: String type_ = "type" +when :: String +when = "when" + is :: String is = "is" diff --git a/src/Pure/Typing/Env.hs b/src/Pure/Typing/Env.hs index 308e290..5dc5599 100644 --- a/src/Pure/Typing/Env.hs +++ b/src/Pure/Typing/Env.hs @@ -10,7 +10,11 @@ module Pure.Typing.Env member, bind, typeOf, + members, insert, + delete, + without, + unions, Apply (..), (<:>), ) @@ -19,8 +23,10 @@ where import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe) +import Data.Set (Set) import Pure.Typing.Type (Scheme (..), Type (..)) import Utility.Common (Id) +import Utility.Strings (li, (+\+)) -- ENVIRONMENT ----------------------------------------------------------------- @@ -55,11 +61,20 @@ bind k = Env . Map.singleton k s1@(Env m1) <:> (Env m2) = Env $ Map.union (Map.map (s1 +->) m2) m1 -- ^ The union is left biased. +unions :: (Foldable t) => t (Env Type) -> Env Type +unions = foldl (<:>) empty + -- UPDATE ---------------------------------------------------------------------- insert :: Id -> a -> Env a -> Env a insert i a (Env m) = Env $ Map.insert i a m +delete :: Id -> Env a -> Env a +delete i (Env m) = Env $ Map.delete i m + +without :: (Foldable t) => t Id -> Env a -> Env a +without is env = foldr delete env is + -- QUERY ----------------------------------------------------------------------- member :: Id -> Env a -> Bool @@ -68,6 +83,9 @@ member k (Env m) = Map.member k m typeOf :: Id -> Env a -> Maybe a typeOf i (Env m) = Map.lookup i m +members :: Env a -> Set Id +members (Env m) = Map.keysSet m + -- APPLY ----------------------------------------------------------------------- class Apply a where @@ -88,3 +106,10 @@ instance Apply Scheme where instance (Apply a) => Apply (Env a) where subst +-> ctx = fmap (subst +->) ctx + +-- SHOW ------------------------------------------------------------------------ + +instance (Show a) => Show (Env a) where + show (Env m) = "{" +\+ unlines (map showPair $ Map.toList m) ++ "}" + where + showPair (k, v) = li k ++ ": " ++ show v \ No newline at end of file diff --git a/src/Pure/Typing/Infer.hs b/src/Pure/Typing/Infer.hs index 97266c2..207dfb9 100644 --- a/src/Pure/Typing/Infer.hs +++ b/src/Pure/Typing/Infer.hs @@ -11,12 +11,14 @@ module Pure.Typing.Infer ) where +import Control.Monad (mapAndUnzipM) import Control.Monad.Except (ExceptT, runExceptT, throwError, withError) import Control.Monad.State (State, evalState, get, put, runState) import Data.Functor ((<&>)) +import Data.Set ((\\)) import qualified Data.Set as Set import Pure.Expr (Expr (..)) -import Pure.Typing.Env (Apply (..), Env (..), (<:>)) +import Pure.Typing.Env (Apply (..), Context, Subst, (<:>)) import qualified Pure.Typing.Env as Env import Pure.Typing.Error (Error (..)) import Pure.Typing.Free (Free (..)) @@ -28,11 +30,7 @@ import Utility.Result (Result (..)) import qualified Utility.Result as Result import Utility.Strings (base26, (+-+), (+\\+)) --- TYPES ----------------------------------------------------------------------- - -type Subst = Env Type - -type Context = Env Scheme +-- TYPE INFERENCE MONAD -------------------------------------------------------- type TI a = ExceptT Error (State Int) a @@ -80,18 +78,12 @@ infer ctx (Id i _) = case Env.typeOf i ctx of Nothing -> throwUnboundVariableError i Just scheme -> instantiate scheme <&> (,) Env.empty -infer _ (List [] _) = var <&> (,) Env.empty -infer ctx (List (x : xs) pos) = do - (s1, tx) <- infer ctx x - (s2, txs) <- infer (s1 +-> ctx) (List xs pos) - s3 <- unify (tList tx) txs - return (s3 <:> s2 <:> s1, s3 +-> txs) infer ctx (App fun arg _) = do (s1, tyFun) <- infer ctx fun (s2, tyArg) <- infer (s1 +-> ctx) arg - tyRes <- var - s3 <- unify (tyArg :-> tyRes) (s2 +-> tyFun) - return (s3 <:> s2 <:> s1, s3 +-> tyRes) + tyResult <- var + s3 <- unify (tyArg :-> tyResult) (s2 +-> tyFun) + return (s3 <:> s2 <:> s1, s3 +-> tyResult) infer ctx (If condition e1 e2 _) = do (s1, t1) <- infer ctx condition (s2, t2) <- infer (s1 +-> ctx) e1 @@ -103,12 +95,38 @@ infer ctx (If condition e1 e2 _) = do return (s, t) infer ctx (Lam binder body _) = do tyBinder <- var - let tmpCtx = Env.insert binder ([] :. tyBinder) ctx - (s1, tyBody) <- infer tmpCtx body - return (s1, (s1 +-> tyBinder) :-> tyBody) + let ctx1 = Env.insert binder ([] :. tyBinder) ctx + (s, tyBody) <- infer ctx1 body + return (s, (s +-> tyBinder) :-> tyBody) +infer ctx (XLam pattern body _) = do + let fvs = Set.toList $ free pattern \\ Env.members ctx + vts <- mapM (const var) fvs <&> zip fvs + let ctx1 = foldl (\ctx' (v, t) -> Env.insert v ([] :. t) ctx') ctx vts + (s1, tyPat) <- infer ctx1 pattern + (s2, tyBody) <- infer (s1 +-> ctx1) body + let s = Env.without fvs (s2 <:> s1) + -- ! ^^^^^^^^^^^^^^^ + -- ? Deleting these because patterns of the same `when` expression may + -- ? redeclare variables. + return (s, (s +-> tyPat) :-> tyBody) +infer ctx (When x opts _) = do + (s1, tyCorpse) <- infer ctx x + (s2, tyOpts) <- mapAndUnzipM (infer $ s1 +-> ctx) opts + tyResult <- var + s3 <- unifyList $ (tyCorpse :-> tyResult) : tyOpts + let s = s3 <:> Env.unions s2 <:> s1 + return (s, s +-> tyResult) -- HELPERS --------------------------------------------------------------------- +unifyList :: [Type] -> TI Subst +unifyList [] = return Env.empty +unifyList [_] = return Env.empty +unifyList (t : r : ts) = do + s1 <- unify t r + s2 <- unifyList ts + return $ s2 <:> s1 + unify :: Type -> Type -> TI Subst unify (Var u) t = varBind u t unify t (Var u) = varBind u t @@ -150,7 +168,7 @@ varBind v ty | otherwise = return $ Env.bind v ty generalize :: Context -> Type -> Scheme -generalize ctx t = Set.toList (Set.difference (free t) (free ctx)) :. t +generalize ctx t = Set.toList (free t \\ free ctx) :. t instantiate :: Scheme -> TI Type instantiate (vars :. ty) = do @@ -187,6 +205,21 @@ primitives = [ ("id", ["a"] :. Var "a" :-> Var "a"), ("always", ["a", "b"] :. Var "a" :-> Var "b" :-> Var "a"), ("(+)", [] :. tInt :-> tInt :-> tInt), - ("(:)", ["a"] :. Var "a" :-> tList (Var "a") :-> tList (Var "a")), - ("null", ["a"] :. tList (Var "a")) + ("Cons", ["a"] :. Var "a" :-> tList (Var "a") :-> tList (Var "a")), + ("Null", ["a"] :. tList (Var "a")) ] + +-- pos = initialPos "main.pure" +-- testTI $ +-- XLam +-- ( App +-- (App (Literal $ Id "Cons" pos) (Literal $ Id "x" pos) pos) +-- (Literal $ Id "xs" pos) +-- pos +-- ) +-- ( App +-- (App (Literal $ Id "(+)" pos) (Literal $ Id "x" pos) pos) +-- (Literal $ Int 1 pos) +-- pos +-- ) +-- pos \ No newline at end of file diff --git a/vim/ftdetect/pure.vim b/vim/ftdetect/pure.vim index 000ea7e..b0be9fb 100644 --- a/vim/ftdetect/pure.vim +++ b/vim/ftdetect/pure.vim @@ -1,2 +1,2 @@ -" directive for Pure (github.com/prog-lang/purist) +" directive for Pure (github.com/prog-lang/pure) autocmd BufRead,BufNewFile *.pure set filetype=pure \ No newline at end of file diff --git a/vim/syntax/pure.vim b/vim/syntax/pure.vim index 8024101..bd8f819 100644 --- a/vim/syntax/pure.vim +++ b/vim/syntax/pure.vim @@ -1,4 +1,4 @@ -" syntax highlighting for Pure (github.com/prog-lang/purist) +" syntax highlighting for Pure (github.com/prog-lang/pure) if exists('b:current_syntax') finish