From 1fdc3674ba19363b77823e894e8e4ee83c41198b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Czajka?= <62751+lukaszcz@users.noreply.github.com> Date: Tue, 6 Sep 2022 12:28:09 +0200 Subject: [PATCH] LetRec in Core (#1507) --- package.yaml | 2 +- src/Juvix/Compiler/Core/Data/InfoTable.hs | 4 +- .../Compiler/Core/Data/InfoTableBuilder.hs | 13 +-- src/Juvix/Compiler/Core/Evaluator.hs | 14 ++- src/Juvix/Compiler/Core/Extra/Base.hs | 15 +++ src/Juvix/Compiler/Core/Info/BinderInfo.hs | 21 +++- src/Juvix/Compiler/Core/Info/NameInfo.hs | 3 + src/Juvix/Compiler/Core/Info/TypeInfo.hs | 3 + src/Juvix/Compiler/Core/Language.hs | 20 +++- src/Juvix/Compiler/Core/Pretty/Base.hs | 35 ++++++- .../Compiler/Core/Translation/FromSource.hs | 98 ++++++++++++++++--- .../Core/Translation/FromSource/Lexer.hs | 4 + src/Juvix/Extra/Strings.hs | 6 ++ test/Core/Negative.hs | 6 +- test/Core/Positive.hs | 7 +- tests/Core/negative/test010.jvc | 1 + tests/Core/positive/out/test025.out | 5 +- tests/Core/positive/out/test040.out | 14 +++ tests/Core/positive/reference/test025.hs | 17 ++++ tests/Core/positive/test025.jvc | 7 +- tests/Core/positive/test040.jvc | 53 ++++++++++ 21 files changed, 310 insertions(+), 38 deletions(-) create mode 100644 tests/Core/negative/test010.jvc create mode 100644 tests/Core/positive/out/test040.out create mode 100644 tests/Core/positive/reference/test025.hs create mode 100644 tests/Core/positive/test040.jvc diff --git a/package.yaml b/package.yaml index c19c67b7fc..dc14d371ef 100644 --- a/package.yaml +++ b/package.yaml @@ -4,7 +4,7 @@ license: GPL-3.0-only license-file: LICENSE copyright: (c) 2022- Heliax AG. maintainer: The PLT Team at Heliax AG -author: [ Jonathan Prieto-Cubides , Jan Mas Rovira , Paul Cadman , Github's contributors ] +author: [ Jonathan Prieto-Cubides , Jan Mas Rovira , Paul Cadman , Lukasz Czajka , Github's contributors ] tested-with: ghc == 9.2.4 homepage: https://juvix.org bug-reports: https://github.com/anoma/juvix/issues diff --git a/src/Juvix/Compiler/Core/Data/InfoTable.hs b/src/Juvix/Compiler/Core/Data/InfoTable.hs index 2aa42a7887..bd1772d757 100644 --- a/src/Juvix/Compiler/Core/Data/InfoTable.hs +++ b/src/Juvix/Compiler/Core/Data/InfoTable.hs @@ -7,7 +7,7 @@ type IdentContext = HashMap Symbol Node data InfoTable = InfoTable { _identContext :: IdentContext, -- `_identMap` is needed only for REPL - _identMap :: HashMap Text (Either Symbol Tag), + _identMap :: HashMap Text IdentKind, _infoMain :: Maybe Symbol, _infoIdentifiers :: HashMap Symbol IdentifierInfo, _infoInductives :: HashMap Name InductiveInfo, @@ -27,6 +27,8 @@ emptyInfoTable = _infoAxioms = mempty } +data IdentKind = IdentSym Symbol | IdentTag Tag + data IdentifierInfo = IdentifierInfo { _identifierName :: Name, _identifierSymbol :: Symbol, diff --git a/src/Juvix/Compiler/Core/Data/InfoTableBuilder.hs b/src/Juvix/Compiler/Core/Data/InfoTableBuilder.hs index 063961052a..fb8d41d4f9 100644 --- a/src/Juvix/Compiler/Core/Data/InfoTableBuilder.hs +++ b/src/Juvix/Compiler/Core/Data/InfoTableBuilder.hs @@ -11,18 +11,11 @@ data InfoTableBuilder m a where RegisterConstructor :: ConstructorInfo -> InfoTableBuilder m () RegisterIdentNode :: Symbol -> Node -> InfoTableBuilder m () SetIdentArgsInfo :: Symbol -> [ArgumentInfo] -> InfoTableBuilder m () - GetIdent :: Text -> InfoTableBuilder m (Maybe (Either Symbol Tag)) + GetIdent :: Text -> InfoTableBuilder m (Maybe IdentKind) GetInfoTable :: InfoTableBuilder m InfoTable makeSem ''InfoTableBuilder -hasIdent :: Member InfoTableBuilder r => Text -> Sem r Bool -hasIdent txt = do - i <- getIdent txt - case i of - Just _ -> return True - Nothing -> return False - getConstructorInfo :: Member InfoTableBuilder r => Tag -> Sem r ConstructorInfo getConstructorInfo tag = do tab <- getInfoTable @@ -67,10 +60,10 @@ runInfoTableBuilder tab = return (UserTag (s ^. stateNextUserTag - 1)) RegisterIdent ii -> do modify' (over stateInfoTable (over infoIdentifiers (HashMap.insert (ii ^. identifierSymbol) ii))) - modify' (over stateInfoTable (over identMap (HashMap.insert (ii ^. (identifierName . nameText)) (Left (ii ^. identifierSymbol))))) + modify' (over stateInfoTable (over identMap (HashMap.insert (ii ^. (identifierName . nameText)) (IdentSym (ii ^. identifierSymbol))))) RegisterConstructor ci -> do modify' (over stateInfoTable (over infoConstructors (HashMap.insert (ci ^. constructorTag) ci))) - modify' (over stateInfoTable (over identMap (HashMap.insert (ci ^. (constructorName . nameText)) (Right (ci ^. constructorTag))))) + modify' (over stateInfoTable (over identMap (HashMap.insert (ci ^. (constructorName . nameText)) (IdentTag (ci ^. constructorTag))))) RegisterIdentNode sym node -> modify' (over stateInfoTable (over identContext (HashMap.insert sym node))) SetIdentArgsInfo sym argsInfo -> do diff --git a/src/Juvix/Compiler/Core/Evaluator.hs b/src/Juvix/Compiler/Core/Evaluator.hs index cc711010cb..f882fa4290 100644 --- a/src/Juvix/Compiler/Core/Evaluator.hs +++ b/src/Juvix/Compiler/Core/Evaluator.hs @@ -9,9 +9,10 @@ module Juvix.Compiler.Core.Evaluator where import Control.Exception qualified as Exception import Data.HashMap.Strict qualified as HashMap import Debug.Trace qualified as Debug +import GHC.Conc qualified as GHC import GHC.Show as S import Juvix.Compiler.Core.Data.InfoTable -import Juvix.Compiler.Core.Error +import Juvix.Compiler.Core.Error (CoreError (..)) import Juvix.Compiler.Core.Extra import Juvix.Compiler.Core.Info qualified as Info import Juvix.Compiler.Core.Info.NoDisplayInfo @@ -50,7 +51,7 @@ eval :: IdentContext -> Env -> Node -> Node eval !ctx !env0 = convertRuntimeNodes . eval' env0 where evalError :: Text -> Node -> a - evalError !msg !node = Exception.throw (EvalError msg (Just node)) + evalError msg node = Exception.throw (EvalError msg (Just node)) eval' :: Env -> Node -> Node eval' !env !n = case n of @@ -65,6 +66,10 @@ eval !ctx !env0 = convertRuntimeNodes . eval' env0 NCtr (Constr i tag args) -> mkConstr i tag (map' (eval' env) args) NLam l@Lambda {} -> Closure env l NLet (Let _ v b) -> let !v' = eval' env v in eval' (v' : env) b + NRec (LetRec _ vs b) -> + let !vs' = map (eval' env') (toList vs) + !env' = revAppend vs' env + in foldr GHC.pseq (eval' env' b) vs' NCase (Case i v bs def) -> case eval' env v of NCtr (Constr _ tag args) -> branch n env args tag def bs @@ -77,7 +82,10 @@ eval !ctx !env0 = convertRuntimeNodes . eval' env0 branch :: Node -> Env -> [Node] -> Tag -> Maybe Node -> [CaseBranch] -> Node branch n !env !args !tag !def = \case - (CaseBranch tag' _ b) : _ | tag' == tag -> eval' (revAppend args env) b + (CaseBranch tag' _ b) : _ + | tag' == tag -> + let !env' = revAppend args env + in eval' env' b _ : bs' -> branch n env args tag def bs' [] -> case def of Just b -> eval' env b diff --git a/src/Juvix/Compiler/Core/Extra/Base.hs b/src/Juvix/Compiler/Core/Extra/Base.hs index 31b721425f..ac1841b3d7 100644 --- a/src/Juvix/Compiler/Core/Extra/Base.hs +++ b/src/Juvix/Compiler/Core/Extra/Base.hs @@ -2,6 +2,7 @@ module Juvix.Compiler.Core.Extra.Base where import Data.Functor.Identity import Data.List qualified as List +import Data.List.NonEmpty (fromList) import Juvix.Compiler.Core.Info qualified as Info import Juvix.Compiler.Core.Info.BinderInfo import Juvix.Compiler.Core.Language @@ -57,6 +58,12 @@ mkLet i v b = NLet (Let i v b) mkLet' :: Node -> Node -> Node mkLet' = mkLet Info.empty +mkLetRec :: Info -> NonEmpty Node -> Node -> Node +mkLetRec i vs b = NRec (LetRec i vs b) + +mkLetRec' :: NonEmpty Node -> Node -> Node +mkLetRec' = mkLetRec Info.empty + mkCase :: Info -> Node -> [CaseBranch] -> Maybe Node -> Node mkCase i v bs def = NCase (Case i v bs def) @@ -178,6 +185,14 @@ destruct = \case NCtr (Constr i tag args) -> NodeDetails i args (map (const 0) args) (map (const []) args) (`mkConstr` tag) NLam (Lambda i b) -> NodeDetails i [b] [1] [fetchBinderInfo i] (\i' args' -> mkLambda i' (hd args')) NLet (Let i v b) -> NodeDetails i [v, b] [0, 1] [[], fetchBinderInfo i] (\i' args' -> mkLet i' (hd args') (args' !! 1)) + NRec (LetRec i vs b) -> + let n = length vs + in NodeDetails + i + (b : toList vs) + (replicate (n + 1) n) + (replicate (n + 1) (getInfoBinders n i)) + (\i' args' -> mkLetRec i' (fromList (tl args')) (hd args')) NCase (Case i v bs Nothing) -> let branchBinderNums = map (\(CaseBranch _ k _) -> k) bs in NodeDetails diff --git a/src/Juvix/Compiler/Core/Info/BinderInfo.hs b/src/Juvix/Compiler/Core/Info/BinderInfo.hs index db59827917..198adf9ec8 100644 --- a/src/Juvix/Compiler/Core/Info/BinderInfo.hs +++ b/src/Juvix/Compiler/Core/Info/BinderInfo.hs @@ -3,12 +3,21 @@ module Juvix.Compiler.Core.Info.BinderInfo where import Juvix.Compiler.Core.Info qualified as Info import Juvix.Compiler.Core.Language +-- | Info about a single binder. Associated with Lambda and Pi. newtype BinderInfo = BinderInfo {_infoBinder :: Info} instance IsInfo BinderInfo -kBinderInfoData :: Key BinderInfo -kBinderInfoData = Proxy +kBinderInfo :: Key BinderInfo +kBinderInfo = Proxy + +-- | Info about multiple binders. Associated with LetRec. +newtype BindersInfo = BindersInfo {_infoBinders :: [Info]} + +instance IsInfo BindersInfo + +kBindersInfo :: Key BindersInfo +kBindersInfo = Proxy newtype CaseBinderInfo = CaseBinderInfo { _infoBranchBinders :: [[Info]] @@ -24,6 +33,12 @@ makeLenses ''CaseBinderInfo getInfoBinder :: Info -> Info getInfoBinder i = - case Info.lookup kBinderInfoData i of + case Info.lookup kBinderInfo i of Just (BinderInfo {..}) -> _infoBinder Nothing -> Info.empty + +getInfoBinders :: Int -> Info -> [Info] +getInfoBinders n i = + case Info.lookup kBindersInfo i of + Just (BindersInfo {..}) -> _infoBinders + Nothing -> replicate n Info.empty diff --git a/src/Juvix/Compiler/Core/Info/NameInfo.hs b/src/Juvix/Compiler/Core/Info/NameInfo.hs index bde8ae1d1e..d1ba92308c 100644 --- a/src/Juvix/Compiler/Core/Info/NameInfo.hs +++ b/src/Juvix/Compiler/Core/Info/NameInfo.hs @@ -17,3 +17,6 @@ getInfoName i = case Info.lookup kNameInfo i of Just (NameInfo {..}) -> Just _infoName Nothing -> Nothing + +setInfoName :: Name -> Info -> Info +setInfoName = Info.insert . NameInfo diff --git a/src/Juvix/Compiler/Core/Info/TypeInfo.hs b/src/Juvix/Compiler/Core/Info/TypeInfo.hs index 9ca99e3bf7..6558ad85b5 100644 --- a/src/Juvix/Compiler/Core/Info/TypeInfo.hs +++ b/src/Juvix/Compiler/Core/Info/TypeInfo.hs @@ -17,3 +17,6 @@ getInfoType i = case Info.lookup kTypeInfo i of Just (TypeInfo {..}) -> Just _infoType Nothing -> Nothing + +setInfoType :: Type -> Info -> Info +setInfoType = Info.insert . TypeInfo diff --git a/src/Juvix/Compiler/Core/Language.hs b/src/Juvix/Compiler/Core/Language.hs index 28c4ce18ef..d0af60ffec 100644 --- a/src/Juvix/Compiler/Core/Language.hs +++ b/src/Juvix/Compiler/Core/Language.hs @@ -56,6 +56,15 @@ data Lambda = Lambda {_lambdaInfo :: !Info, _lambdaBody :: !Node} -- purposes of ML-polymorphic / dependent type checking or code generation! data Let = Let {_letInfo :: !Info, _letValue :: !Node, _letBody :: !Node} +-- | Represents a block of mutually recursive local definitions. Both in the +-- body and in the values `length _letRecValues` implicit binders are introduced +-- which hold the functions/values being defined. +data LetRec = LetRec + { _letRecInfo :: !Info, + _letRecValues :: !(NonEmpty Node), + _letRecBody :: !Node + } + -- | One-level case matching on the tag of a data constructor: `Case value -- branches default`. `Case` is lazy: only the selected branch is evaluated. data Case = Case @@ -100,12 +109,13 @@ data Node | NCtr {-# UNPACK #-} !Constr | NLam {-# UNPACK #-} !Lambda | NLet {-# UNPACK #-} !Let + | NRec {-# UNPACK #-} !LetRec | NCase {-# UNPACK #-} !Case | NPi {-# UNPACK #-} !Pi | NUniv {-# UNPACK #-} !Univ | NTyp {-# UNPACK #-} !TypeConstr | NDyn !Dynamic -- Dynamic is already a newtype, so it's unpacked. - | -- Evaluation only: `Closure env body` + | -- Evaluation only: `Closure env body`. Closure { _closureEnv :: !Env, _closureLambda :: {-# UNPACK #-} !Lambda @@ -174,6 +184,9 @@ instance HasAtomicity Lambda where instance HasAtomicity Let where atomicity _ = Aggregate lambdaFixity +instance HasAtomicity LetRec where + atomicity _ = Aggregate lambdaFixity + instance HasAtomicity Case where atomicity _ = Aggregate lambdaFixity @@ -199,6 +212,7 @@ instance HasAtomicity Node where NCtr x -> atomicity x NLam x -> atomicity x NLet x -> atomicity x + NRec x -> atomicity x NCase x -> atomicity x NPi x -> atomicity x NUniv x -> atomicity x @@ -233,6 +247,9 @@ instance Eq Lambda where instance Eq Let where (Let _ v1 b1) == (Let _ v2 b2) = v1 == v2 && b1 == b2 +instance Eq LetRec where + (LetRec _ vs1 b1) == (LetRec _ vs2 b2) = vs1 == vs2 && b1 == b2 + instance Eq Case where (Case _ v1 bs1 def1) == (Case _ v2 bs2 def2) = v1 == v2 && bs1 == bs2 && def1 == def2 @@ -255,6 +272,7 @@ makeLenses ''App makeLenses ''BuiltinApp makeLenses ''Constr makeLenses ''Let +makeLenses ''LetRec makeLenses ''Case makeLenses ''Pi makeLenses ''Univ diff --git a/src/Juvix/Compiler/Core/Pretty/Base.hs b/src/Juvix/Compiler/Core/Pretty/Base.hs index 4230998dd8..a1d15949e3 100644 --- a/src/Juvix/Compiler/Core/Pretty/Base.hs +++ b/src/Juvix/Compiler/Core/Pretty/Base.hs @@ -5,6 +5,7 @@ module Juvix.Compiler.Core.Pretty.Base ) where +import Data.List qualified as List import Juvix.Compiler.Core.Extra import Juvix.Compiler.Core.Info qualified as Info import Juvix.Compiler.Core.Info.BinderInfo as BinderInfo @@ -63,6 +64,7 @@ instance PrettyCode Tag where UserTag tag -> return $ kwUnnamedConstr <> pretty tag instance PrettyCode Node where + ppCode :: forall r. Member (Reader Options) r => Node -> Sem r (Doc Ann) ppCode node = case node of NVar Var {..} -> case Info.lookup kNameInfo _varInfo of @@ -102,7 +104,7 @@ instance PrettyCode Node where b <- ppCode body return $ foldl' (flip (<+>)) b pplams where - ppLam :: Member (Reader Options) r => Info -> Sem r (Doc Ann) + ppLam :: Info -> Sem r (Doc Ann) ppLam i = case getInfoName (getInfoBinder i) of Just name -> do @@ -117,6 +119,28 @@ instance PrettyCode Node where v' <- ppCode _letValue b' <- ppCode _letBody return $ kwLet <+> n' <+> kwAssign <+> v' <+> kwIn <+> b' + NRec LetRec {..} -> do + let n = length _letRecValues + ns <- mapM getName (getInfoBinders n _letRecInfo) + vs <- mapM ppCode _letRecValues + b' <- ppCode _letRecBody + if + | length ns == 1 -> + return $ kwLetRec <+> List.head ns <+> kwAssign <+> head vs <+> kwIn <+> b' + | otherwise -> + let bss = + indent' $ + align $ + concatWith (\a b -> a <> kwSemicolon <> line <> b) $ + zipWithExact (\name val -> name <+> kwAssign <+> val) ns (toList vs) + nss = enclose kwSquareL kwSquareR (concatWith (<+>) ns) + in return $ kwLetRec <> nss <> line <> bss <> line <> kwIn <> line <> b' + where + getName :: Info -> Sem r (Doc Ann) + getName i = + case getInfoName i of + Just name -> ppCode name + Nothing -> return kwQuestion NCase Case {..} -> do bns <- case Info.lookup kCaseBinderInfo _caseInfo of @@ -204,6 +228,12 @@ ppLRExpression associates fixlr e = {--------------------------------------------------------------------------------} {- keywords -} +kwSquareL :: Doc Ann +kwSquareL = delimiter "[" + +kwSquareR :: Doc Ann +kwSquareR = delimiter "]" + kwDeBruijnVar :: Doc Ann kwDeBruijnVar = keyword Str.deBruijnVar @@ -237,6 +267,9 @@ kwDiv = keyword Str.div kwMod :: Doc Ann kwMod = keyword Str.mod +kwLetRec :: Doc Ann +kwLetRec = keyword Str.letrec_ + kwCase :: Doc Ann kwCase = keyword Str.case_ diff --git a/src/Juvix/Compiler/Core/Translation/FromSource.hs b/src/Juvix/Compiler/Core/Translation/FromSource.hs index 1c70e145b7..ff3c6c9130 100644 --- a/src/Juvix/Compiler/Core/Translation/FromSource.hs +++ b/src/Juvix/Compiler/Core/Translation/FromSource.hs @@ -7,9 +7,10 @@ where import Control.Monad.Trans.Class (lift) import Data.HashMap.Strict qualified as HashMap import Data.List qualified as List +import Data.List.NonEmpty (fromList) import Juvix.Compiler.Core.Data.InfoTable import Juvix.Compiler.Core.Data.InfoTableBuilder -import Juvix.Compiler.Core.Extra.Base +import Juvix.Compiler.Core.Extra import Juvix.Compiler.Core.Info qualified as Info import Juvix.Compiler.Core.Info.BinderInfo as BinderInfo import Juvix.Compiler.Core.Info.BranchInfo as BranchInfo @@ -126,12 +127,12 @@ statementDef = do (txt, i) <- identifierL r <- lift (getIdent txt) case r of - Just (Left sym) -> do + Just (IdentSym sym) -> do guardSymbolNotDefined sym (parseFailure off ("duplicate definition of: " ++ fromText txt)) parseDefinition sym - Just (Right {}) -> + Just (IdentTag {}) -> parseFailure off ("duplicate identifier: " ++ fromText txt) Nothing -> do sym <- lift freshSymbol @@ -177,10 +178,14 @@ statementConstr = do off <- P.getOffset (txt, i) <- identifierL (argsNum, _) <- number 0 128 - dupl <- lift (hasIdent txt) - when - dupl - (parseFailure off ("duplicate identifier: " ++ fromText txt)) + r <- lift (getIdent txt) + case r of + Just (IdentSym _) -> + parseFailure off ("duplicate identifier: " ++ fromText txt) + Just (IdentTag _) -> + parseFailure off ("duplicate identifier: " ++ fromText txt) + Nothing -> + return () tag <- lift freshTag name <- lift $ freshName KNameConstructor txt i let info = @@ -467,6 +472,8 @@ atom varsNum vars = <|> exprConstInt <|> exprConstString <|> exprLambda varsNum vars + <|> exprLetrecMany varsNum vars + <|> exprLetrecOne varsNum vars <|> exprLet varsNum vars <|> exprCase varsNum vars <|> exprIf varsNum vars @@ -488,10 +495,10 @@ exprNamed varsNum vars = do Nothing -> do r <- lift (getIdent txt) case r of - Just (Left sym) -> do + Just (IdentSym sym) -> do name <- lift $ freshName KNameFunction txt i return $ mkIdent (Info.singleton (NameInfo name)) sym - Just (Right tag) -> do + Just (IdentTag tag) -> do name <- lift $ freshName KNameConstructor txt i return $ mkConstr (Info.singleton (NameInfo name)) tag [] Nothing -> @@ -539,6 +546,75 @@ exprLambda varsNum vars = do body <- expr (varsNum + 1) vars' return $ mkLambda (binderNameInfo name) body +exprLetrecOne :: + Members '[Reader ParserParams, InfoTableBuilder, NameIdGen] r => + Index -> + HashMap Text Index -> + ParsecS r Node +exprLetrecOne varsNum vars = do + kwLetRec + name <- parseLocalName + kwAssignment + let vars' = HashMap.insert (name ^. nameText) varsNum vars + value <- expr (varsNum + 1) vars' + kwIn + body <- expr (varsNum + 1) vars' + return $ mkLetRec (Info.singleton (BindersInfo [Info.singleton (NameInfo name)])) (fromList [value]) body + +exprLetrecMany :: + Members '[Reader ParserParams, InfoTableBuilder, NameIdGen] r => + Index -> + HashMap Text Index -> + ParsecS r Node +exprLetrecMany varsNum vars = do + off <- P.getOffset + defNames <- P.try (kwLetRec >> letrecNames) + when (null defNames) $ + parseFailure off "expected at least one identifier name in letrec signature" + let (vars', varsNum') = foldl' (\(vs, k) txt -> (HashMap.insert txt k vs, k + 1)) (vars, varsNum) defNames + defs <- letrecDefs defNames varsNum' vars' + body <- expr varsNum' vars' + let infos = map (Info.singleton . NameInfo . fst) defs + let values = map snd defs + return $ mkLetRec (Info.singleton (BindersInfo infos)) (fromList values) body + +letrecNames :: ParsecS r [Text] +letrecNames = P.between (symbol "[") (symbol "]") (P.many identifier) + +letrecDefs :: + Members '[Reader ParserParams, InfoTableBuilder, NameIdGen] r => + [Text] -> + Index -> + HashMap Text Index -> + ParsecS r [(Name, Node)] +letrecDefs names varsNum vars = case names of + [] -> return [] + n : names' -> do + off <- P.getOffset + (txt, i) <- identifierL + when (n /= txt) $ + parseFailure off "identifier name doesn't match letrec signature" + name <- lift $ freshName KNameLocal txt i + kwAssignment + v <- expr varsNum vars + if + | null names' -> optional kwSemicolon >> kwIn + | otherwise -> kwSemicolon + rest <- letrecDefs names' varsNum vars + return $ (name, v) : rest + +letrecDef :: + Members '[Reader ParserParams, InfoTableBuilder, NameIdGen] r => + Index -> + HashMap Text Index -> + ParsecS r (Name, Node) +letrecDef varsNum vars = do + (txt, i) <- identifierL + name <- lift $ freshName KNameLocal txt i + kwAssignment + v <- expr varsNum vars + return (name, v) + exprLet :: Members '[Reader ParserParams, InfoTableBuilder, NameIdGen] r => Index -> @@ -627,9 +703,9 @@ matchingBranch varsNum vars = do txt <- identifier r <- lift (getIdent txt) case r of - Just (Left {}) -> + Just (IdentSym {}) -> parseFailure off ("not a constructor: " ++ fromText txt) - Just (Right tag) -> do + Just (IdentTag tag) -> do ns <- P.many parseLocalName let bindersNum = length ns ci <- lift $ getConstructorInfo tag diff --git a/src/Juvix/Compiler/Core/Translation/FromSource/Lexer.hs b/src/Juvix/Compiler/Core/Translation/FromSource/Lexer.hs index 9c8aab6fc1..9fc6a2e0c2 100644 --- a/src/Juvix/Compiler/Core/Translation/FromSource/Lexer.hs +++ b/src/Juvix/Compiler/Core/Translation/FromSource/Lexer.hs @@ -56,6 +56,7 @@ allKeywords = kwColon, kwLambda, kwLet, + kwLetRec, kwIn, kwConstr, kwCase, @@ -117,6 +118,9 @@ kwLambda = rawKeyword Str.lambdaUnicode <|> rawKeyword Str.lambdaAscii kwLet :: ParsecS r () kwLet = keyword Str.let_ +kwLetRec :: ParsecS r () +kwLetRec = keyword Str.letrec_ + kwIn :: ParsecS r () kwIn = keyword Str.in_ diff --git a/src/Juvix/Extra/Strings.hs b/src/Juvix/Extra/Strings.hs index 8e1fdcea75..76b2729e63 100644 --- a/src/Juvix/Extra/Strings.hs +++ b/src/Juvix/Extra/Strings.hs @@ -71,6 +71,9 @@ print = "print" let_ :: IsString s => s let_ = "let" +letrec_ :: IsString s => s +letrec_ = "letrec" + public :: IsString s => s public = "public" @@ -188,6 +191,9 @@ foreign_ = "foreign" compile :: IsString s => s compile = "compile" +comma :: IsString s => s +comma = "," + semicolon :: IsString s => s semicolon = ";" diff --git a/test/Core/Negative.hs b/test/Core/Negative.hs index ddffc58b04..219725adb2 100644 --- a/test/Core/Negative.hs +++ b/test/Core/Negative.hs @@ -64,5 +64,9 @@ tests = NegTest "Erroneous Church numerals" "." - "test009.jvc" + "test009.jvc", + NegTest + "Empty letrec" + "." + "test010.jvc" ] diff --git a/test/Core/Positive.hs b/test/Core/Positive.hs index 43a8f18ee3..dda2582349 100644 --- a/test/Core/Positive.hs +++ b/test/Core/Positive.hs @@ -224,5 +224,10 @@ tests = "Eta-expansion of builtins and constructors" "." "test039.jvc" - "out/test039.out" + "out/test039.out", + PosTest + "Letrec" + "." + "test040.jvc" + "out/test040.out" ] diff --git a/tests/Core/negative/test010.jvc b/tests/Core/negative/test010.jvc new file mode 100644 index 0000000000..538f239b12 --- /dev/null +++ b/tests/Core/negative/test010.jvc @@ -0,0 +1 @@ +letrec { } in 0 diff --git a/tests/Core/positive/out/test025.out b/tests/Core/positive/out/test025.out index b2058379a3..784a57611b 100644 --- a/tests/Core/positive/out/test025.out +++ b/tests/Core/positive/out/test025.out @@ -1,2 +1,3 @@ -120 -3628800 +32 +869 +120841341761971907847955774644591458595374466108868229 diff --git a/tests/Core/positive/out/test040.out b/tests/Core/positive/out/test040.out new file mode 100644 index 0000000000..6d3f9533d4 --- /dev/null +++ b/tests/Core/positive/out/test040.out @@ -0,0 +1,14 @@ +3 +50005000 +120 +3628800 +93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 +55 +354224848179261915075 +43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875 +32 +869 +120841341761971907847955774644591458595374466108868229 +41 +85 +40 diff --git a/tests/Core/positive/reference/test025.hs b/tests/Core/positive/reference/test025.hs new file mode 100644 index 0000000000..e86441166d --- /dev/null +++ b/tests/Core/positive/reference/test025.hs @@ -0,0 +1,17 @@ +f :: Integer -> Integer +f x = + if x < 1 + then 1 + else 2 * x + g (x - 1) + +g :: Integer -> Integer +g x = + if x < 1 + then 1 + else x + h (x - 1) + +h :: Integer -> Integer +h x = + if x < 1 + then 1 + else x * f (x - 1) diff --git a/tests/Core/positive/test025.jvc b/tests/Core/positive/test025.jvc index 18c98dd714..e7f02157c6 100644 --- a/tests/Core/positive/test025.jvc +++ b/tests/Core/positive/test025.jvc @@ -6,7 +6,7 @@ def f := \x { if x < 1 then 1 else - x * g (x - 1) + 2 * x + g (x - 1) }; def h; @@ -15,7 +15,7 @@ def g := \x { if x < 1 then 1 else - x * h (x - 1) + x + h (x - 1) }; def h := \x { @@ -28,4 +28,5 @@ def h := \x { def writeLn := \x write x >> write "\n"; writeLn (f 5) >> -writeLn (f 10) +writeLn (f 10) >> +writeLn (f 100) diff --git a/tests/Core/positive/test040.jvc b/tests/Core/positive/test040.jvc new file mode 100644 index 0000000000..6cc205a8c4 --- /dev/null +++ b/tests/Core/positive/test040.jvc @@ -0,0 +1,53 @@ +-- letrec + +def sum := letrec sum := \x if x = 0 then 0 else x + sum (x - 1) in sum; + +def fact := \x + letrec fact' := \x \acc if x = 0 then acc else fact' (x - 1) (acc * x) + in fact' x 1; + +def fib := + letrec fib' := \n \x \y if n = 0 then x else fib' (n - 1) y (x + y) + in \n fib' n 0 1; + +def writeLn := \x write x >> write "\n"; + +def mutrec := + letrec[f g h] + f := \x { + if x < 1 then + 1 + else + g (x - 1) + 2 * x + }; + g := \x { + if x < 1 then + 1 + else + x + h (x - 1) + }; + h := \x letrec z := { + if x < 1 then + 1 + else + x * f (x - 1) + } in z + in writeLn (f 5) >> writeLn (f 10) >> writeLn (f 100) >> writeLn (g 5) >> writeLn (h 5); + +letrec x := 3 +in +writeLn x >> +writeLn (sum 10000) >> +writeLn (fact 5) >> +writeLn (fact 10) >> +writeLn (fact 100) >> +writeLn (fib 10) >> +writeLn (fib 100) >> +writeLn (fib 1000) >> +mutrec >> +letrec x := 1 in +letrec x' := x + letrec x := 2 in x in +letrec x := x' * x' in +letrec y := x + 2 in +letrec z := x + y in +writeLn (x + y + z)