REPL import warning; version info in CLI
All checks were successful
Test, Build, and Release / test (push) Successful in 1m18s
Test, Build, and Release / build (push) Successful in 1m13s

Adds the ability to toggle result decoding in REPL. Adds several more
useful functions to the base library.
This commit is contained in:
James Eversole 2025-01-30 14:19:30 -06:00
parent bf1000d174
commit 7d1b6a741d
11 changed files with 142 additions and 61 deletions

View File

@ -2,7 +2,9 @@
## Introduction ## Introduction
tricu (pronounced "tree-shoe") is a purely functional interpreted language implemented in Haskell. It is fundamentally based on the application of [Tree Calculus](https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf) terms, but minimal syntax sugar is included to provide a useful programming tool. tricu is under active development and you can expect breaking changes with nearly every commit. tricu (pronounced "tree-shoe") is a purely functional interpreted language implemented in Haskell. It is fundamentally based on the application of [Tree Calculus](https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf) terms, but minimal syntax sugar is included to provide a useful programming tool.
*tricu is under active development and you should expect breaking changes with every commit.*
tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)`. tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)`.
@ -14,7 +16,7 @@ tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)
- Lambda abstraction syntax: `id = (\a : a)` - Lambda abstraction syntax: `id = (\a : a)`
- List, Number, and String literals: `[(2) ("Hello")]` - List, Number, and String literals: `[(2) ("Hello")]`
- Function application: `not (not false)` - Function application: `not (not false)`
- Higher order/first-class functions: `map (\a : lconcat a "!") [("Hello")]` - Higher order/first-class functions: `map (\a : append a "!") [("Hello")]`
- Intensionality blurs the distinction between functions and data (see REPL examples) - Intensionality blurs the distinction between functions and data (see REPL examples)
- Simple module system for code organization - Simple module system for code organization
@ -23,9 +25,9 @@ tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)
``` ```
tricu < -- Anything after `--` on a single line is a comment tricu < -- Anything after `--` on a single line is a comment
tricu < id = (\a : a) -- Lambda abstraction is eliminated to tree calculus terms tricu < id = (\a : a) -- Lambda abstraction is eliminated to tree calculus terms
tricu < head (map (\i : lconcat i " world!") [("Hello, ")]) tricu < head (map (\i : append i " world!") [("Hello, ")])
tricu > "Hello, world!" tricu > "Hello, world!"
tricu < id (head (map (\i : lconcat i " world!") [("Hello, ")])) tricu < id (head (map (\i : append i " world!") [("Hello, ")]))
tricu > "Hello, world!" tricu > "Hello, world!"
tricu < -- Intensionality! We can inspect the structure of a function or data. tricu < -- Intensionality! We can inspect the structure of a function or data.

View File

@ -37,21 +37,21 @@ processLevel = y (\self queue : if (emptyList? queue)
[] []
(pair (map label queue) (self (filter (pair (map label queue) (self (filter
(\node : not? (emptyList? node)) (\node : not? (emptyList? node))
(lconcat (map left queue) (map right queue)))))) (append (map left queue) (map right queue))))))
levelOrderTraversal_ = \a : processLevel (t a t) levelOrderTraversal_ = \a : processLevel (t a t)
toLineString = y (\self levels : if (emptyList? levels) toLineString = y (\self levels : if (emptyList? levels)
"" ""
(lconcat (append
(lconcat (map (\x : lconcat x " ") (head levels)) "") (append (map (\x : append x " ") (head levels)) "")
(if (emptyList? (tail levels)) "" (lconcat (t (t 10 t) t) (self (tail levels)))))) (if (emptyList? (tail levels)) "" (append (t (t 10 t) t) (self (tail levels))))))
levelOrderToString = \s : toLineString (levelOrderTraversal_ s) levelOrderToString = \s : toLineString (levelOrderTraversal_ s)
flatten = foldl (\acc x : lconcat acc x) "" flatten = foldl (\acc x : append acc x) ""
levelOrderTraversal = \s : lconcat (t 10 t) (flatten (levelOrderToString s)) levelOrderTraversal = \s : append (t 10 t) (flatten (levelOrderToString s))
exampleOne = levelOrderTraversal [("1") exampleOne = levelOrderTraversal [("1")
[("2") [("4") t t] t] [("2") [("4") t t] t]

View File

@ -2,17 +2,6 @@
main = size size main = size size
compose = \f g x : f (g x)
succ = y (\self :
triage
1
t
(triage
(t (t t))
(\_ tail : t t (self tail))
t))
size = (\x : size = (\x :
(y (\self x : (y (\self x :
compose succ compose succ

View File

@ -15,6 +15,8 @@ y = ((\mut wait fun : wait mut (\x : fun (wait mut x)))
(\x : x x) (\x : x x)
(\a0 a1 a2 : t (t a0) (t t a2) a1)) (\a0 a1 a2 : t (t a0) (t t a2) a1))
compose = \f g x : f (g x)
triage = \leaf stem fork : t (t leaf stem) fork triage = \leaf stem fork : t (t leaf stem) fork
test = triage "Leaf" (\_ : "Stem") (\_ _ : "Fork") test = triage "Leaf" (\_ : "Stem") (\_ _ : "Fork")
@ -35,7 +37,14 @@ emptyList? = matchList true (\_ _ : false)
head = matchList t (\head _ : head) head = matchList t (\head _ : head)
tail = matchList t (\_ tail : tail) tail = matchList t (\_ tail : tail)
lconcat = y (\self : matchList or? = (\x y :
matchBool
(matchBool (t t) (t t) y)
(matchBool (t t) 0 y)
x)
xor? = matchBool id not?
append = y (\self : matchList
(\k : k) (\k : k)
(\h r k : pair h (self r k))) (\h r k : pair h (self r k)))
@ -71,6 +80,10 @@ equal? = y (\self : triage
(\_ : false) (\_ : false)
(\bx by : lAnd (self ax bx) (self ay by)))) (\bx by : lAnd (self ax bx) (self ay by))))
lExist? = y (\self x : matchList
false
(\h z : or? (equal? x h) (self x z)))
filter_ = y (\self : matchList filter_ = y (\self : matchList
(\_ : t) (\_ : t)
(\head tail f : matchBool (t head) i (f head) (self tail f))) (\head tail f : matchBool (t head) i (f head) (self tail f)))
@ -81,3 +94,57 @@ foldl = \f x l : foldl_ f l x
foldr_ = y (\self x f l : matchList x (\head tail : f (self x f tail) head) l) foldr_ = y (\self x f l : matchList x (\head tail : f (self x f tail) head) l)
foldr = \f x l : foldr_ x f l foldr = \f x l : foldr_ x f l
succ = y (\self :
triage
1
t
(triage
(t (t t))
(\_ tail : t t (self tail))
t))
length = y (\self : matchList
0
(\_ tail : succ (self tail)))
reverse = y (\self : matchList
t
(\head tail : append (self tail) (pair head t)))
snoc = y (\self x : matchList
(pair x t)
(\h z : pair h (self x z)))
count = y (\self x : matchList
0
(\h z : matchBool
(succ (self x z))
(self x z)
(equal? x h)))
last = y (\self : matchList
t
(\hd tl : matchBool
hd
(self tl)
(emptyList? tl)))
all? = y (\self pred : matchList
true
(\h z : and? (pred h) (self pred z)))
any? = y (\self pred : matchList
false
(\h z : or? (pred h) (self pred z)))
unique_ = y (\self seen : matchList
t
(\head rest : matchBool
(self seen rest)
(pair head (self (pair head seen) rest))
(lExist? head seen)))
unique = \xs : unique_ t xs
intersect = \xs ys : filter (\x : lExist? x ys) xs
union = \xs ys : unique (append xs ys)

View File

@ -74,8 +74,6 @@ elimLambda = go
-- Composition optimization -- Composition optimization
go (SLambda [f] (SLambda [g] (SLambda [x] body))) go (SLambda [f] (SLambda [g] (SLambda [x] body)))
| body == SApp (SVar f) (SApp (SVar g) (SVar x)) = _B | body == SApp (SVar f) (SApp (SVar g) (SVar x)) = _B
go (SLambda [f] (SLambda [x] (SLambda [y] body)))
| body == SApp (SApp (SVar f) (SVar y)) (SVar x) = _C
-- General elimination -- General elimination
go (SLambda (v:vs) body) go (SLambda (v:vs) body)
| null vs = toSKI v (elimLambda body) | null vs = toSKI v (elimLambda body)
@ -97,7 +95,6 @@ elimLambda = go
_K = parseSingle "t t" _K = parseSingle "t t"
_I = parseSingle "t (t (t t)) t" _I = parseSingle "t (t (t t)) t"
_B = parseSingle "t (t (t t (t (t (t t t)) t))) (t t)" _B = parseSingle "t (t (t t (t (t (t t t)) t))) (t t)"
_C = parseSingle "t (t (t (t (t t (t (t (t t t)) t))) (t (t (t t (t t))) (t (t (t t t)) t)))) (t t (t t))"
_TRIAGE = parseSingle "t (t (t t (t (t (t t t))))) t" _TRIAGE = parseSingle "t (t (t t (t (t (t t t))))) t"
isFree :: String -> TricuAST -> Bool isFree :: String -> TricuAST -> Bool

View File

@ -60,7 +60,8 @@ preprocessFile' inProgress filePath
Right asts -> do Right asts -> do
let (imports, nonImports) = partition isImport asts let (imports, nonImports) = partition isImport asts
let newInProgress = Set.insert filePath inProgress let newInProgress = Set.insert filePath inProgress
importedASTs <- concat <$> mapM (processImport newInProgress "") imports importedASTs <- concat <$>
mapM (processImport newInProgress "") imports
pure $ importedASTs ++ nonImports pure $ importedASTs ++ nonImports
where where
isImport :: TricuAST -> Bool isImport :: TricuAST -> Bool
@ -116,13 +117,20 @@ nsBodyScoped moduleName args body = case body of
if name `elem` args if name `elem` args
then SVar name then SVar name
else nsBody moduleName (SVar name) else nsBody moduleName (SVar name)
SApp func arg -> SApp (nsBodyScoped moduleName args func) (nsBodyScoped moduleName args arg) SApp func arg ->
SLambda innerArgs innerBody -> SLambda innerArgs (nsBodyScoped moduleName (args ++ innerArgs) innerBody) SApp (nsBodyScoped moduleName args func) (nsBodyScoped moduleName args arg)
SList items -> SList (map (nsBodyScoped moduleName args) items) SLambda innerArgs innerBody ->
TFork left right -> TFork (nsBodyScoped moduleName args left) (nsBodyScoped moduleName args right) SLambda innerArgs (nsBodyScoped moduleName (args ++ innerArgs) innerBody)
TStem subtree -> TStem (nsBodyScoped moduleName args subtree) SList items ->
SList (map (nsBodyScoped moduleName args) items)
TFork left right ->
TFork (nsBodyScoped moduleName args left)
(nsBodyScoped moduleName args right)
TStem subtree ->
TStem (nsBodyScoped moduleName args subtree)
SDef name innerArgs innerBody -> SDef name innerArgs innerBody ->
SDef (nsVariable moduleName name) innerArgs (nsBodyScoped moduleName (args ++ innerArgs) innerBody) SDef (nsVariable moduleName name) innerArgs
(nsBodyScoped moduleName (args ++ innerArgs) innerBody)
other -> other other -> other
isPrefixed :: String -> Bool isPrefixed :: String -> Bool

View File

@ -8,7 +8,9 @@ import Research
import Control.Monad (foldM) import Control.Monad (foldM)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Data.Version (showVersion)
import Text.Megaparsec (runParser) import Text.Megaparsec (runParser)
import Paths_tricu (version)
import System.Console.CmdArgs import System.Console.CmdArgs
import qualified Data.Map as Map import qualified Data.Map as Map
@ -52,10 +54,12 @@ decodeMode = TDecode
main :: IO () main :: IO ()
main = do main = do
let versionStr = "tricu Evaluator and REPL " ++ showVersion version
args <- cmdArgs $ modes [replMode, evaluateMode, decodeMode] args <- cmdArgs $ modes [replMode, evaluateMode, decodeMode]
&= help "tricu: Exploring Tree Calculus" &= help "tricu: Exploring Tree Calculus"
&= program "tricu" &= program "tricu"
&= summary "tricu Evaluator and REPL" &= summary versionStr
&= versionArg [explicit, name "version", summary versionStr]
case args of case args of
Repl -> do Repl -> do
putStrLn "Welcome to the tricu REPL" putStrLn "Welcome to the tricu REPL"

View File

@ -6,55 +6,69 @@ import Lexer
import Parser import Parser
import Research import Research
import Control.Exception (SomeException, catch) import Control.Exception (SomeException, catch)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Catch (handle, MonadCatch) import Control.Monad.Catch (handle, MonadCatch)
import Data.Char (isSpace) import Data.Char (isSpace)
import Data.List (dropWhile, dropWhileEnd, intercalate) import Data.List ( dropWhile
, dropWhileEnd
, intercalate
, isPrefixOf)
import System.Console.Haskeline import System.Console.Haskeline
import qualified Data.Map as Map import qualified Data.Map as Map
repl :: Env -> IO () repl :: Env -> IO ()
repl env = runInputT defaultSettings (withInterrupt (loop env)) repl env = runInputT defaultSettings (withInterrupt (loop env True))
where where
loop :: Env -> InputT IO () loop :: Env -> Bool -> InputT IO ()
loop env = handle (interruptHandler env) $ do loop env decode = handle (interruptHandler env decode) $ do
minput <- getInputLine "tricu < " minput <- getInputLine "tricu < "
case minput of case minput of
Nothing -> outputStrLn "Exiting tricu" Nothing -> outputStrLn "Exiting tricu"
Just s Just s
| strip s == "" -> loop env decode
| strip s == "!exit" -> outputStrLn "Exiting tricu" | strip s == "!exit" -> outputStrLn "Exiting tricu"
| strip s == "" -> loop env | strip s == "!decode" -> do
| strip s == "!import" -> do outputStrLn $ "Decoding " ++ (if decode then "disabled" else "enabled")
loop env (not decode)
| "!import" `isPrefixOf` strip s -> do
let afterImport = dropWhile (== ' ') $ drop (length ("!import" :: String)) (strip s)
if not (null afterImport)
then outputStrLn "Warning: REPL imports are interactive; \
\additional arguments are ignored."
else pure ()
path <- getInputLine "File path to load < " path <- getInputLine "File path to load < "
case path of case path of
Nothing -> do Nothing -> do
outputStrLn "No input received; stopping import." outputStrLn "No input received; stopping import."
loop env loop env decode
Just p -> do Just p -> do
loadedEnv <- liftIO $ evaluateFileWithContext env loadedEnv <- liftIO $ evaluateFileWithContext env
(strip p) `catch` \e -> errorHandler env e (strip p) `catch` \e -> errorHandler env e
loop $ Map.delete "!result" (Map.union loadedEnv env) loop (Map.delete "!result" (Map.union loadedEnv env)) decode
| take 2 s == "--" -> loop env | take 2 s == "--" -> loop env decode
| otherwise -> do | otherwise -> do
newEnv <- liftIO $ processInput env s `catch` errorHandler env newEnv <- liftIO $ processInput env s decode `catch` errorHandler env
loop newEnv loop newEnv decode
interruptHandler :: Env -> Interrupt -> InputT IO () interruptHandler :: Env -> Bool -> Interrupt -> InputT IO ()
interruptHandler env _ = do interruptHandler env decode _ = do
outputStrLn "Interrupted with CTRL+C\n\ outputStrLn "Interrupted with CTRL+C\n\
\You can use the !exit command or CTRL+D to exit" \You can use the !exit command or CTRL+D to exit"
loop env loop env decode
processInput :: Env -> String -> IO Env processInput :: Env -> String -> Bool -> IO Env
processInput env input = do processInput env input decode = do
let asts = parseTricu input let asts = parseTricu input
newEnv = evalTricu env asts newEnv = evalTricu env asts
if case Map.lookup "!result" newEnv of
| Just r <- Map.lookup "!result" newEnv -> do Just r -> do
putStrLn $ "tricu > " ++ decodeResult r putStrLn $ "tricu > " ++
| otherwise -> return () if decode
then decodeResult r
else show r
Nothing -> pure ()
return newEnv return newEnv
errorHandler :: Env -> SomeException -> IO (Env) errorHandler :: Env -> SomeException -> IO (Env)

View File

@ -468,7 +468,7 @@ baseLibrary = testGroup "Library Tests"
, testCase "Concatenate strings" $ do , testCase "Concatenate strings" $ do
library <- evaluateFile "./lib/base.tri" library <- evaluateFile "./lib/base.tri"
let input = "lconcat \"Hello, \" \"world!\"" let input = "append \"Hello, \" \"world!\""
env = decodeResult $ result $ evalTricu library (parseTricu input) env = decodeResult $ result $ evalTricu library (parseTricu input)
env @?= "\"Hello, world!\"" env @?= "\"Hello, world!\""

View File

@ -1,2 +1,2 @@
x = map (\i : lconcat "Successfully concatenated " i) [("two strings!")] x = map (\i : append "Successfully concatenated " i) [("two strings!")]
main = equal? x [("Successfully concatenated two strings!")] main = equal? x [("Successfully concatenated two strings!")]

View File

@ -1 +1 @@
head (map (\i : lconcat "String " i) [("test!")]) head (map (\i : append "String " i) [("test!")])