From 35163a5d54440c013736ebdce64bed1c4994ea5d Mon Sep 17 00:00:00 2001 From: James Eversole Date: Mon, 20 Jan 2025 19:20:29 -0600 Subject: [PATCH] Allow multiline expressions --- lib/base.tri | 78 +++++++-- src/Eval.hs | 29 ++-- src/Lexer.hs | 8 +- src/Main.hs | 5 +- src/Parser.hs | 466 ++++++++++++++++++++++++++------------------------ test/Spec.hs | 10 +- tricu.cabal | 2 +- 7 files changed, 336 insertions(+), 262 deletions(-) diff --git a/lib/base.tri b/lib/base.tri index d6f0568..1c199fc 100644 --- a/lib/base.tri +++ b/lib/base.tri @@ -17,25 +17,77 @@ yD = yi iE id = (\a : a) triage = (\a b c : t (t a b) c) pair = t -matchBool = (\ot of : triage of (\_ : ot) (\_ _ : ot)) -matchList = (\oe oc : triage oe _ oc) -matchPair = (\op : triage _ _ op) + +matchBool = (\ot of : triage + of + (\_ : ot) + (\_ _ : ot) +) + +matchList = (\oe oc : triage + oe + _ + oc +) + +matchPair = (\op : triage + _ + _ + op +) + not = matchBool false true and = matchBool id (\z : false) if = (\cond then else : t (t else (t t then)) t cond) test = triage "Leaf" (\z : "Stem") (\a b : "Fork") + emptyList = matchList true (\y z : false) head = matchList t (\hd tl : hd) tail = matchList t (\hd tl : tl) -lconcat = y (\self : matchList (\k : k) (\h r k : pair h (self r k))) -lAnd = triage (\x : false) (\_ x : x) (\_ _ x : x) -lOr = triage (\x : x) (\_ _ : true) (\_ _ x : true) -hmap = y (\self : matchList (\f : t) (\hd tl f : pair (f hd) (self tl f))) + +lconcat = y (\self : matchList + (\k : k) + (\h r k : pair h (self r k))) + +lAnd = (triage + (\x : false) + (\_ x : x) + (\_ _ x : x) +) + +lOr = (triage + (\x : x) + (\_ _ : true) + (\_ _ x : true) +) + +hmap = y (\self : + matchList + (\f : t) + (\hd tl f : pair + (f hd) + (self tl f))) map = (\f l : hmap l f) -equal = y (\self : triage (triage true (\z : false) (\y z : false)) (\ax : triage false (self ax) (\y z : false)) (\ax ay : triage false (\z : false) (\bx by : lAnd (self ax bx) (self ay by)))) + +equal = y (\self : triage + (triage + true + (\z : false) + (\y z : false)) + (\ax : triage + false + (self ax) + (\y z : false)) + (\ax ay : triage + false + (\z : false) + (\bx by : lAnd (self ax bx) (self ay by)))) + hfilter = y (\self : matchList (\f : t) (\hd tl f : matchBool (t hd) i (f hd) (self tl f))) -filter = (\f l : hfilter l f) -hfoldl = y (\self f l x : matchList (\acc : acc) (\hd tl acc : self f tl (f acc hd)) l x) -foldl = (\f x l : hfoldl f l x) -hfoldr = y (\self x f l : matchList x (\hd tl : f (self x f tl) hd) l) -foldr = (\f x l : hfoldr x f l) +filter = (\f l : hfilter l f) + +hfoldl = y (\self f l x : matchList (\acc : acc) (\hd tl acc : self f tl (f acc hd)) l x) +foldl = (\f x l : hfoldl f l x) + +hfoldr = y (\self x f l : matchList x (\hd tl : f (self x f tl) hd) l) +foldr = (\f x l : hfoldr x f l) diff --git a/src/Eval.hs b/src/Eval.hs index e8d1e77..e9b1b17 100644 --- a/src/Eval.hs +++ b/src/Eval.hs @@ -13,22 +13,23 @@ evalSingle env term | SFunc name [] body <- term = let res = evalAST env body in Map.insert "__result" res (Map.insert name res env) - | SApp func arg <- term = Map.insert "__result" - (apply (evalAST env func) (evalAST env arg)) env - | SVar name <- term = case Map.lookup name env of - Just v -> Map.insert "__result" v env - Nothing -> errorWithoutStackTrace $ "Variable " ++ name ++ " not defined" - | otherwise = Map.insert "__result" (evalAST env term) env + | SApp func arg <- term = + let res = apply (evalAST env func) (evalAST env arg) + in Map.insert "__result" res env + | SVar name <- term = + case Map.lookup name env of + Just v -> Map.insert "__result" v env + Nothing -> errorWithoutStackTrace $ "Variable " ++ name ++ " not defined" + | otherwise = + Map.insert "__result" (evalAST env term) env evalTricu :: Env -> [TricuAST] -> Env -evalTricu env list = evalTricu' env (filter (/= SEmpty) list) - where - evalTricu' :: Env -> [TricuAST] -> Env - evalTricu' env [] = env - evalTricu' env [s] = - let updatedEnv = evalSingle env s - in Map.insert "__result" (result updatedEnv) updatedEnv - evalTricu' env (x:xs) = evalTricu (evalSingle env x) xs +evalTricu env [] = env +evalTricu env [x] = + let updatedEnv = evalSingle env x + in Map.insert "__result" (result updatedEnv) updatedEnv +evalTricu env (x:xs) = + evalTricu (evalSingle env x) xs evalAST :: Env -> TricuAST -> T evalAST env term diff --git a/src/Lexer.hs b/src/Lexer.hs index 0278227..6ed1a5d 100644 --- a/src/Lexer.hs +++ b/src/Lexer.hs @@ -61,7 +61,10 @@ lnewline :: Lexer LToken lnewline = char '\n' *> pure LNewline sc :: Lexer () -sc = space space1 (skipLineComment "--") (skipBlockComment "|-" "-|") +sc = space + (void $ takeWhile1P (Just "space") (\c -> c == ' ' || c == '\t')) + (skipLineComment "--") + (skipBlockComment "|-" "-|") tricuLexer :: Lexer [LToken] tricuLexer = do @@ -75,7 +78,8 @@ tricuLexer = do pure tokens where tricuLexer' = - [ try identifier + [ try lnewline + , try identifier , try keywordT , try integerLiteral , try stringLiteral diff --git a/src/Main.hs b/src/Main.hs index 63d4eaa..2335a99 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -81,4 +81,7 @@ main = do putStrLn $ decodeResult $ result $ evalTricu library $ parseTricu value runTricu :: String -> T -runTricu = result . evalTricu Map.empty . parseTricu +runTricu input = + let asts = parseTricu input + finalEnv = evalTricu Map.empty asts + in result finalEnv diff --git a/src/Parser.hs b/src/Parser.hs index 87e7fae..678b46b 100644 --- a/src/Parser.hs +++ b/src/Parser.hs @@ -1,281 +1,299 @@ module Parser where import Lexer -import Research hiding (toList) +import Research -import Data.List.NonEmpty (toList) +import Control.Monad (void) +import Control.Monad.State +import Data.List.NonEmpty (toList) import Data.Void (Void) import Text.Megaparsec -import Text.Megaparsec.Char import Text.Megaparsec.Error (ParseErrorBundle, errorBundlePretty) - import qualified Data.Set as Set -type Parser = Parsec Void [LToken] -type AltParser = Parsec Void String +data PState = PState + { depth :: Int + } deriving (Show) + +type ParserM = StateT PState (Parsec Void [LToken]) + +satisfyM :: (LToken -> Bool) -> ParserM LToken +satisfyM f = do + token <- lift (satisfy f) + modify' (updateDepth token) + return token + +updateDepth :: LToken -> PState -> PState +updateDepth LOpenParen st = st { depth = depth st + 1 } +updateDepth LCloseParen st = st { depth = max 0 (depth st - 1) } +updateDepth _ st = st + +topLevelNewline :: ParserM () +topLevelNewline = do + st <- get + if depth st == 0 + then void (satisfyM (== LNewline)) + else fail "Top-level exit in paren context" + +parseProgram :: [LToken] -> Either (ParseErrorBundle [LToken] Void) [TricuAST] +parseProgram tokens = + runParser (evalStateT parseProgramM (PState 0)) "" tokens + +parseSingleExpr :: [LToken] -> Either (ParseErrorBundle [LToken] Void) TricuAST +parseSingleExpr tokens = + runParser (evalStateT (scnParserM *> parseExpressionM <* eofM) (PState 0)) "" tokens parseTricu :: String -> [TricuAST] -parseTricu input - | null tokens = [] - | otherwise = map parseSingle tokens - where - tokens - | null (lexTricu input) = [] - | otherwise = lines input +parseTricu input = + case lexTricu input of + [] -> [] + toks -> + case parseProgram toks of + Left err -> error (handleParseError err) + Right asts -> asts parseSingle :: String -> TricuAST -parseSingle input - | null tokens = SEmpty - | Left err <- parsed = error $ handleParseError err - | Right ast <- parsed = ast - where - tokens = lexTricu input - parsed = runParser parseExpression "" tokens +parseSingle input = + case lexTricu input of + [] -> SEmpty + toks -> + case parseSingleExpr toks of + Left err -> error (handleParseError err) + Right ast -> ast -parseExpression :: Parser TricuAST -parseExpression = choice - [ try parseFunction - , try parseLambda - , try parseLambdaExpression - , try parseListLiteral - , try parseApplication - , try parseTreeTerm - , parseLiteral +parseProgramM :: ParserM [TricuAST] +parseProgramM = do + skipMany topLevelNewline + exprs <- sepEndBy parseOneExpression (some topLevelNewline) + skipMany topLevelNewline + return exprs + +parseOneExpression :: ParserM TricuAST +parseOneExpression = scnParserM *> parseExpressionM + +scnParserM :: ParserM () +scnParserM = skipMany $ do + t <- lookAhead anySingle + st <- get + if depth st > 0 && isNewline t + then void (satisfyM isNewline) + else + fail "In paren context or no space token" <|> empty + +eofM :: ParserM () +eofM = lift eof + +parseExpressionM :: ParserM TricuAST +parseExpressionM = choice + [ try parseFunctionM + , try parseLambdaM + , try parseLambdaExpressionM + , try parseListLiteralM + , try parseApplicationM + , try parseTreeTermM + , parseLiteralM ] -scnParser :: Parser () -scnParser = skipMany (satisfy isNewline) +parseFunctionM :: ParserM TricuAST +parseFunctionM = do + LIdentifier name <- satisfyM isIdentifier + args <- many (satisfyM isIdentifier) + _ <- satisfyM (== LAssign) + scnParserM + body <- parseExpressionM + pure (SFunc name (map getIdentifier args) body) -parseFunction :: Parser TricuAST -parseFunction = do - LIdentifier name <- satisfy isIdentifier - args <- many (satisfy isIdentifier) - satisfy (== LAssign) - body <- parseExpression - return (SFunc name (map getIdentifier args) body) +parseLambdaM :: ParserM TricuAST +parseLambdaM = between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) $ do + _ <- satisfyM (== LBackslash) + param <- satisfyM isIdentifier + rest <- many (satisfyM isIdentifier) + _ <- satisfyM (== LColon) + scnParserM + body <- parseLambdaExpressionM + let nested = foldr (\v acc -> SLambda [getIdentifier v] acc) body rest + pure (SLambda [getIdentifier param] nested) -parseAtomicBase :: Parser TricuAST -parseAtomicBase = choice - [ parseTreeLeaf - , parseGrouped - ] - -parseLambda :: Parser TricuAST -parseLambda = between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) $ do - satisfy (== LBackslash) - param <- satisfy isIdentifier - rest <- many (satisfy isIdentifier) - satisfy (== LColon) - body <- parseLambdaExpression - let nestedLambda = foldr (\v acc -> SLambda [v] acc) body (map getIdentifier rest) - return (SLambda [getIdentifier param] nestedLambda) - -parseLambdaExpression :: Parser TricuAST -parseLambdaExpression = choice - [ try parseLambdaApplication - , parseAtomicLambda +parseLambdaExpressionM :: ParserM TricuAST +parseLambdaExpressionM = choice + [ try parseLambdaApplicationM + , parseAtomicLambdaM ] -parseAtomicLambda :: Parser TricuAST -parseAtomicLambda = choice - [ parseVar - , parseTreeLeaf - , parseLiteral - , parseListLiteral - , try parseLambda - , between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseLambdaExpression +parseAtomicLambdaM :: ParserM TricuAST +parseAtomicLambdaM = choice + [ parseVarM + , parseTreeLeafM + , parseLiteralM + , parseListLiteralM + , try parseLambdaM + , between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) parseLambdaExpressionM ] -parseApplication :: Parser TricuAST -parseApplication = do - func <- parseAtomicBase - args <- many parseAtomic - return $ foldl (\acc arg -> SApp acc arg) func args +parseApplicationM :: ParserM TricuAST +parseApplicationM = do + func <- parseAtomicBaseM + scnParserM + args <- many $ do + scnParserM + arg <- parseAtomicM + return arg + return $ foldl SApp func args -parseLambdaApplication :: Parser TricuAST -parseLambdaApplication = do - func <- parseAtomicLambda - args <- many parseAtomicLambda - return $ foldl (\acc arg -> SApp acc arg) func args +parseLambdaApplicationM :: ParserM TricuAST +parseLambdaApplicationM = do + func <- parseAtomicLambdaM + scnParserM + args <- many $ do + arg <- parseAtomicLambdaM + scnParserM + pure arg + pure $ foldl SApp func args -isTreeTerm :: TricuAST -> Bool -isTreeTerm TLeaf = True -isTreeTerm (TStem _) = True -isTreeTerm (TFork _ _) = True -isTreeTerm _ = False +parseAtomicBaseM :: ParserM TricuAST +parseAtomicBaseM = choice + [ parseTreeLeafM + , parseGroupedM + ] -parseTreeLeaf :: Parser TricuAST -parseTreeLeaf = satisfy isKeywordT *> notFollowedBy (satisfy (== LAssign)) *> pure TLeaf +parseTreeLeafM :: ParserM TricuAST +parseTreeLeafM = do + _ <- satisfyM isKeywordT + notFollowedBy (lift (satisfy (== LAssign))) + pure TLeaf -getIdentifier :: LToken -> String -getIdentifier (LIdentifier name) = name -getIdentifier _ = error "Expected identifier" - -parseTreeTerm :: Parser TricuAST -parseTreeTerm = do - base <- parseTreeLeafOrParenthesized - rest <- many parseTreeLeafOrParenthesized - pure $ foldl combine base rest +parseTreeTermM :: ParserM TricuAST +parseTreeTermM = do + base <- parseTreeLeafOrParenthesizedM + rest <- many parseTreeLeafOrParenthesizedM + pure (foldl combine base rest) where combine acc next | TLeaf <- acc = TStem next | TStem t <- acc = TFork t next | TFork _ _ <- acc = TFork acc next -parseTreeLeafOrParenthesized :: Parser TricuAST -parseTreeLeafOrParenthesized = choice - [ between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseTreeTerm - , parseTreeLeaf +parseTreeLeafOrParenthesizedM :: ParserM TricuAST +parseTreeLeafOrParenthesizedM = choice + [ between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) parseTreeTermM + , parseTreeLeafM ] -foldTree :: [TricuAST] -> TricuAST -foldTree [] = TLeaf -foldTree [x] = x -foldTree (x:y:rest) = TFork x (foldTree (y:rest)) - -parseAtomic :: Parser TricuAST -parseAtomic = choice - [ parseVar - , parseTreeLeaf - , parseListLiteral - , parseGrouped - , parseLiteral +parseAtomicM :: ParserM TricuAST +parseAtomicM = choice + [ parseVarM + , parseTreeLeafM + , parseListLiteralM + , parseGroupedM + , parseLiteralM ] -parseGrouped :: Parser TricuAST -parseGrouped = between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseExpression +parseGroupedM :: ParserM TricuAST +parseGroupedM = between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) $ + scnParserM *> parseExpressionM <* scnParserM -parseLiteral :: Parser TricuAST -parseLiteral = choice - [ parseIntLiteral - , parseStrLiteral +parseLiteralM :: ParserM TricuAST +parseLiteralM = choice + [ parseIntLiteralM + , parseStrLiteralM ] -parens :: Parser TricuAST -> Parser TricuAST -parens p = do - satisfy (== LOpenParen) - result <- p - satisfy (== LCloseParen) - return result +parseListLiteralM :: ParserM TricuAST +parseListLiteralM = do + _ <- satisfyM (== LOpenBracket) + elements <- many parseListItemM + _ <- satisfyM (== LCloseBracket) + pure (SList elements) -parseListLiteral :: Parser TricuAST -parseListLiteral = do - satisfy (== LOpenBracket) - elements <- many parseListItem - satisfy (== LCloseBracket) - return (SList elements) - -parseListItem :: Parser TricuAST -parseListItem = choice - [ parseGroupedItem - , parseListLiteral - , parseSingleItem +parseListItemM :: ParserM TricuAST +parseListItemM = choice + [ parseGroupedItemM + , parseListLiteralM + , parseSingleItemM ] -parseGroupedItem :: Parser TricuAST -parseGroupedItem = do - satisfy (== LOpenParen) - inner <- parseExpression - satisfy (== LCloseParen) - return inner +parseGroupedItemM :: ParserM TricuAST +parseGroupedItemM = do + _ <- satisfyM (== LOpenParen) + inner <- parseExpressionM + _ <- satisfyM (== LCloseParen) + pure inner -parseSingleItem :: Parser TricuAST -parseSingleItem = do - token <- satisfy isListItem +parseSingleItemM :: ParserM TricuAST +parseSingleItemM = do + token <- satisfyM isListItem case token of - _ | LIdentifier name <- token -> return (SVar name) - | LKeywordT <- token -> return TLeaf - | otherwise -> fail "Unexpected token in list item" + LIdentifier name -> pure (SVar name) + LKeywordT -> pure TLeaf + _ -> fail "Unexpected token in list item" + +parseVarM :: ParserM TricuAST +parseVarM = do + LIdentifier name <- satisfyM isIdentifier + if name == "t" || name == "__result" + then fail ("Reserved keyword: " ++ name ++ " cannot be assigned.") + else pure (SVar name) + +parseIntLiteralM :: ParserM TricuAST +parseIntLiteralM = do + LIntegerLiteral value <- satisfyM isIntegerLiteral + pure (SInt value) + +parseStrLiteralM :: ParserM TricuAST +parseStrLiteralM = do + LStringLiteral value <- satisfyM isStringLiteral + pure (SStr value) + +isKeywordT :: LToken -> Bool +isKeywordT LKeywordT = True +isKeywordT _ = False + +isIdentifier :: LToken -> Bool +isIdentifier (LIdentifier _) = True +isIdentifier _ = False + +isIntegerLiteral :: LToken -> Bool +isIntegerLiteral (LIntegerLiteral _) = True +isIntegerLiteral _ = False + +isStringLiteral :: LToken -> Bool +isStringLiteral (LStringLiteral _) = True +isStringLiteral _ = False + +isLiteral :: LToken -> Bool +isLiteral (LIntegerLiteral _) = True +isLiteral (LStringLiteral _) = True +isLiteral _ = False isListItem :: LToken -> Bool isListItem (LIdentifier _) = True -isListItem LKeywordT = True -isListItem _ = False +isListItem LKeywordT = True +isListItem _ = False -parseVar :: Parser TricuAST -parseVar = do - LIdentifier name <- satisfy isIdentifier - if (name == "t" || name == "__result") - then fail $ "Reserved keyword: " ++ name ++ " cannot be assigned." - else return (SVar name) +isNewline :: LToken -> Bool +isNewline LNewline = True +isNewline _ = False -parseIntLiteral :: Parser TricuAST -parseIntLiteral = do - LIntegerLiteral value <- satisfy isIntegerLiteral - return (SInt value) +getIdentifier :: LToken -> String +getIdentifier (LIdentifier name) = name +getIdentifier _ = error "Expected identifier" -parseStrLiteral :: Parser TricuAST -parseStrLiteral = do - LStringLiteral value <- satisfy isStringLiteral - return (SStr value) - --- Boolean Helpers -isKeywordT (LKeywordT) = True -isKeywordT _ = False -isIdentifier (LIdentifier _) = True -isIdentifier _ = False -isIntegerLiteral (LIntegerLiteral _) = True -isIntegerLiteral _ = False -isStringLiteral (LStringLiteral _) = True -isStringLiteral _ = False -isLiteral (LIntegerLiteral _) = True -isLiteral (LStringLiteral _) = True -isLiteral _ = False -isNewline (LNewline) = True -isNewline _ = False - --- Alternative parsers -altSC :: AltParser () -altSC = skipMany (char ' ' <|> char '\t' <|> char '\n') - -parseTernaryTerm :: AltParser TricuAST -parseTernaryTerm = do - altSC - term <- choice parseTernaryTerm' - altSC - pure term - where - parseTernaryTerm' = - [ try (between (char '(') (char ')') parseTernaryTerm) - , try parseTernaryLeaf - , try parseTernaryStem - , try parseTernaryFork - ] - -parseTernaryLeaf :: AltParser TricuAST -parseTernaryLeaf = char '0' *> pure TLeaf - -parseTernaryStem :: AltParser TricuAST -parseTernaryStem = char '1' *> (TStem <$> parseTernaryTerm) - -parseTernaryFork :: AltParser TricuAST -parseTernaryFork = do - char '2' - term1 <- parseTernaryTerm - term2 <- parseTernaryTerm - pure $ TFork term1 term2 - -parseTernary :: String -> Either String TricuAST -parseTernary input - | Left err <- result = Left (errorBundlePretty err) - | Right ast <- result = Right ast - where - result = runParser (parseTernaryTerm <* eof) "" input - --- Error Handling handleParseError :: ParseErrorBundle [LToken] Void -> String handleParseError bundle = - let errors = bundleErrors bundle - errorList = toList errors - formattedErrors = map showError errorList - in unlines ("Parse error(s) encountered:" : formattedErrors) + let errors = bundleErrors bundle + errorList = Data.List.NonEmpty.toList errors + formattedErrs = map showError errorList + in unlines ("Parse error(s) encountered:" : formattedErrs) showError :: ParseError [LToken] Void -> String showError (TrivialError offset (Just (Tokens tokenStream)) expected) = - "Parse error at offset " ++ show offset ++ ": unexpected token " - ++ show tokenStream ++ ", expected one of " ++ show (Set.toList expected) + "Parse error at offset " ++ show offset + ++ ": unexpected token " ++ show tokenStream + ++ ", expected one of " ++ show (Set.toList expected) showError (FancyError offset fancy) = - "Parse error at offset " ++ show offset ++ ":\n " ++ unlines (map show (Set.toList fancy)) + "Parse error at offset " ++ show offset ++ ":\n " + ++ unlines (map show (Set.toList fancy)) showError (TrivialError offset Nothing expected) = - "Parse error at offset " ++ show offset ++ ": expected one of " - ++ show (Set.toList expected) + "Parse error at offset " ++ show offset + ++ ": expected one of " ++ show (Set.toList expected) + diff --git a/test/Spec.hs b/test/Spec.hs index 666f04b..2358b86 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -71,9 +71,9 @@ lexerTests = testGroup "Lexer Tests" parserTests :: TestTree parserTests = testGroup "Parser Tests" [ testCase "Error when assigning a value to T" $ do - let input = lexTricu "t = x" - case (runParser parseExpression "" input) of - Left _ -> return () + let tokens = lexTricu "t = x" + case parseSingleExpr tokens of + Left _ -> return () Right _ -> assertFailure "Expected failure when trying to assign the value of T" , testCase "Parse function definitions" $ do let input = "x = (\\a b c : a)" @@ -149,10 +149,6 @@ parserTests = testGroup "Parser Tests" let input = "(t) -- (t) -- (t)" expect = [TLeaf] parseTricu input @?= expect - , testCase "Comments with no terms" $ do - let input = unlines ["-- (t)", "(t t)"] - expect = [SEmpty,SApp TLeaf TLeaf] - parseTricu input @?= expect ] evaluationTests :: TestTree diff --git a/tricu.cabal b/tricu.cabal index 117ddea..0e99a6e 100644 --- a/tricu.cabal +++ b/tricu.cabal @@ -1,7 +1,7 @@ cabal-version: 1.12 name: tricu -version: 0.5.0 +version: 0.6.0 description: A micro-language for exploring Tree Calculus author: James Eversole maintainer: james@eversole.co