Expands CLI support with output forms and decoding

Adds CLI options for compiling to a Tree Calculus, AST, Ternary, and
ASCII tree view. Adds CLI command for attempted decoding of a compiled
result to Number/String/List.
This commit is contained in:
James Eversole 2024-12-30 14:19:43 -06:00
parent 8622af9ad2
commit 5e2a4dff50
13 changed files with 131 additions and 57 deletions

View File

@ -1,6 +1,7 @@
module Compiler where
import Eval
import Library
import Parser
import Research
@ -11,13 +12,8 @@ import qualified Data.Map as Map
evaluateFile :: FilePath -> IO T
evaluateFile filePath = do
contents <- readFile filePath
let linesOfFile = lines contents
let env = foldl evaluateLine Map.empty linesOfFile
case Map.lookup "__result" env of
let asts = parseTricu contents
let finalEnv = evalTricu library asts
case Map.lookup "__result" finalEnv of
Just finalResult -> return finalResult
Nothing -> error "No result found in final environment"
evaluateLine :: Env -> String -> Env
evaluateLine env line =
let ast = parseSingle line
in evalSingle env ast

View File

@ -30,33 +30,36 @@ evalSingle env term = case term of
in Map.insert "__result" result env
evalTricu :: Map String T -> [TricuAST] -> Map String T
evalTricu env [] = env
evalTricu env [lastLine] =
evalTricu env list = evalTricu' env (filter (/= SEmpty) list)
where
evalTricu' :: Map String T -> [TricuAST] -> Map String T
evalTricu' env [] = env
evalTricu' env [lastLine] =
let lastLineNoLambda = eliminateLambda lastLine
updatedEnv = evalSingle env lastLineNoLambda
in Map.insert "__result" (result updatedEnv) updatedEnv
evalTricu env (line:rest) =
evalTricu' env (line:rest) =
let lineNoLambda = eliminateLambda line
updatedEnv = evalSingle env lineNoLambda
in evalTricu updatedEnv rest
evalAST :: Map String T -> TricuAST -> T
evalAST env term = case term of
SVar name -> case Map.lookup name env of
Just value -> value
Nothing -> error $ "Variable " ++ name ++ " not defined"
TLeaf -> Leaf
TStem t -> Stem (evalAST env t)
TFork t1 t2 -> Fork (evalAST env t1) (evalAST env t2)
SApp t1 t2 -> apply (evalAST env t1) (evalAST env t2)
SStr str -> ofString str
SInt num -> ofNumber num
SList elems -> ofList (map (evalAST Map.empty) elems)
SEmpty -> Leaf
SFunc name args body ->
error $ "Unexpected function definition " ++ name
++ " in evalAST; define via evalSingle."
SLambda {} -> error "Internal error: SLambda found in evalAST after elimination."
SVar name -> case Map.lookup name env of
Just value -> value
Nothing -> error $ "Variable " ++ name ++ " not defined"
TLeaf -> Leaf
TStem t -> Stem (evalAST env t)
TFork t1 t2 -> Fork (evalAST env t1) (evalAST env t2)
SApp t1 t2 -> apply (evalAST env t1) (evalAST env t2)
SStr str -> ofString str
SInt num -> ofNumber num
SList elems -> ofList (map (evalAST Map.empty) elems)
SEmpty -> Leaf
SFunc name args body ->
error $ "Unexpected function definition " ++ name
++ " in evalAST; define via evalSingle."
SLambda {} -> error "Internal error: SLambda found in evalAST after elimination."
eliminateLambda :: TricuAST -> TricuAST
eliminateLambda (SLambda (v:vs) body)
@ -117,5 +120,5 @@ tS = SApp (SApp TLeaf (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf))) TLeaf
result :: Map String T -> T
result r = case Map.lookup "__result" r of
Just a -> a
Nothing -> error "No __result field found in provided environment"
Just a -> a
Nothing -> error "No __result field found in provided environment"

View File

@ -37,7 +37,7 @@ library = evalTricu Map.empty $ parseTricu $ unlines
, "emptyList = matchList true (\\y z : false)"
, "head = matchList t (\\hd tl : hd)"
, "tail = matchList t (\\hd tl : tl)"
, "listConcat = y (\\self : matchList (\\k : k) (\\h r k : pair h (self r k)))"
, "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)))"

View File

@ -1,11 +1,11 @@
module Main where
import Compiler
import Eval (evalTricu, result)
import Eval (evalTricu, result, toAST)
import Library (library)
import Parser (parseTricu)
import REPL (repl)
import Research (T)
import REPL
import Research
import Text.Megaparsec (runParser)
import System.Console.CmdArgs
@ -14,7 +14,11 @@ import qualified Data.Map as Map
data TricuArgs
= Repl
| Compile { file :: FilePath }
| Compile { file :: FilePath, output :: Maybe FilePath, form :: CompiledForm }
| Decode { input :: Maybe FilePath }
deriving (Show, Data, Typeable)
data CompiledForm = TreeCalculus | AST | Ternary | Ascii
deriving (Show, Data, Typeable)
replMode :: TricuArgs
@ -24,14 +28,31 @@ replMode = Repl
&= name "repl"
compileMode :: TricuArgs
compileMode = Compile { file = def &= typ "FILE" &= help "Relative or absolute path to compile" }
compileMode = Compile
{ file = def &= typ "FILE"
&= help "Relative or absolute path to file input for compilation" &= name "f"
, output = def &= typ "OUTPUT"
&= help "Optional output file path for resulting output" &= name "o"
, form = TreeCalculus &= typ "FORM"
&= help "Output form: (tree|ast|ternary|ascii)"
&= name "t"
}
&= help "Compile a file and return the result of the expression in the final line"
&= explicit
&= name "compile"
decodeMode :: TricuArgs
decodeMode = Decode
{ input = def &= typ "FILE"
&= help "Optional file path containing a Tree Calculus value. Defaults to stdin." &= name "f"
}
&= help "Decode a Tree Calculus value into a string representation"
&= explicit
&= name "decode"
main :: IO ()
main = do
args <- cmdArgs $ modes [replMode, compileMode]
args <- cmdArgs $ modes [replMode, compileMode, decodeMode]
&= help "tricu: Exploring Tree Calculus"
&= program "tricu"
&= summary "tricu - compiler and repl"
@ -41,7 +62,22 @@ main = do
putStrLn "Welcome to the tricu REPL"
putStrLn "You can exit with `CTRL+D` or the `:_exit` command.`"
repl library
Compile filePath -> do
Compile { file = filePath, output = maybeOutputPath, form = form } -> do
result <- evaluateFile filePath
print result
let fRes = formatResult form result
case maybeOutputPath of
Just outputPath -> do
writeFile outputPath fRes
putStrLn $ "Output to: " ++ outputPath
Nothing -> putStr fRes
Decode { input = maybeInputPath } -> do
value <- case maybeInputPath of
Just inputPath -> readFile inputPath
Nothing -> getContents
putStrLn $ decodeResult $ result $ evalTricu library $ parseTricu value
formatResult :: CompiledForm -> T -> String
formatResult TreeCalculus = show
formatResult AST = show . toAST
formatResult Ternary = toTernaryString
formatResult Ascii = toAscii

View File

@ -296,4 +296,3 @@ showError (FancyError offset fancy) =
showError (TrivialError offset Nothing expected) =
"Parse error at offset " ++ show offset ++ ": expected one of "
++ show (Set.toList expected)

View File

@ -27,18 +27,18 @@ repl env = runInputT defaultSettings (loop env)
Just input -> do
newEnv <- liftIO $ (processInput env input `catch` errorHandler env)
loop newEnv
processInput :: Env -> String -> IO (Env)
processInput :: Env -> String -> IO Env
processInput env input = do
let clearEnv = Map.delete "__result" env
newEnv = evalSingle clearEnv (parseSingle input)
let asts = parseTricu input
newEnv = evalTricu env asts
case Map.lookup "__result" newEnv of
Just r -> do
putStrLn $ "tricu > " ++ show r
putStrLn $ "READ -: \"" ++ decodeResult r ++ "\""
Nothing -> return ()
return newEnv
errorHandler :: Env -> SomeException -> IO (Env)
errorHandler env e = do
putStrLn $ "Error: " ++ show e

View File

@ -18,16 +18,6 @@ apply (Fork (Fork a1 a2) a3) Leaf = a1
apply (Fork (Fork a1 a2) a3) (Stem u) = apply a2 u
apply (Fork (Fork a1 a2) a3) (Fork u v) = apply (apply a3 u) v
reduce :: T -> T
reduce expr =
let next = step expr
in if next == expr then expr else reduce next
step :: T -> T
step (Fork left right) = reduce (apply (reduce left) (reduce right))
step (Stem inner) = Stem (reduce inner)
step t = t
-- SKI Combinators
_S :: T
_S = Fork (Stem (Fork Leaf Leaf)) Leaf

View File

@ -1,2 +0,0 @@
x = t t t
x

View File

@ -1,12 +1,15 @@
module Main where
import Compiler
import Eval
import Lexer
import Library
import Parser
import REPL
import Research
import Control.Exception (evaluate, try, SomeException)
import Control.Monad.IO.Class (liftIO)
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
@ -28,6 +31,7 @@ tests = testGroup "Tricu Tests"
, evaluationTests
, lambdaEvalTests
, libraryTests
, compilerTests
, propertyTests
]
@ -213,7 +217,7 @@ evaluationTests = testGroup "Evaluation Tests"
let input = "x = t t\nx = t\nx"
env = evalTricu Map.empty (parseTricu input)
(result env) @?= Leaf
, testCase "Apply identity to Boolean Not" $ do
, testCase "Apply identity to Boolean Not" $ do
let not = "(t (t (t t) (t t t)) t)"
let input = "x = (\\a : a)\nx " ++ not
env = evalTricu Map.empty (parseTricu input)
@ -364,7 +368,7 @@ libraryTests = testGroup "Library Tests"
env = evalTricu library (parseTricu input)
result env @?= Stem Leaf
, testCase "Concatenate strings" $ do
let input = "listConcat \"Hello, \" \"world!\""
let input = "lconcat \"Hello, \" \"world!\""
env = decodeResult $ result $ evalTricu library (parseTricu input)
env @?= "Hello, world!"
, testCase "Verifying Equality" $ do
@ -373,6 +377,19 @@ libraryTests = testGroup "Library Tests"
result env @?= Stem Leaf
]
compilerTests :: TestTree
compilerTests = testGroup "Compiler tests"
[ testCase "Forks" $ do
res <- liftIO $ evaluateFile "./test/fork.tri"
res @?= Fork Leaf Leaf
, testCase "File ends with comment" $ do
res <- liftIO $ evaluateFile "./test/comments-1.tri"
res @?= Fork (Stem Leaf) Leaf
, testCase "Mapping and Equality" $ do
res <- liftIO $ evaluateFile "./test/map.tri"
res @?= Stem Leaf
]
propertyTests :: TestTree
propertyTests = testGroup "Property Tests"
[ testProperty "Lexing and parsing round-trip" $ \input ->

1
test/ascii.tri Normal file
View File

@ -0,0 +1 @@
t (t (t (t (t t) (t t t)) t) t t) t

9
test/comments-1.tri Normal file
View File

@ -0,0 +1,9 @@
-- This is a tricu comment!
-- t (t t) (t (t t t))
-- t (t t t) (t t)
-- x = (\a : a)
t (t t) t -- Fork (Stem Leaf) Leaf
-- t t
-- x
-- x = (\a : a)
-- t

1
test/fork.tri Normal file
View File

@ -0,0 +1 @@
t t t

24
test/map.tri Normal file
View File

@ -0,0 +1,24 @@
false = t
true = t t
_ = t
k = t t
i = t (t k) t
s = t (t (k t)) t
m = s i i
b = s (k s) k
c = s (s (k s) (s (k k) s)) (k k)
iC = (\a b c : s a (k c) b)
yi = (\i : b m (c b (i m)))
y = yi iC
triage = (\a b c : t (t a b) c)
pair = t
matchList = (\oe oc : triage oe _ oc)
lconcat = y (\self : matchList (\k : k) (\h r k : pair h (self r k)))
hmap = y (\self : matchList (\f : t) (\hd tl f : pair (f hd) (self tl f)))
map = (\f l : hmap l f)
lAnd = triage (\x : false) (\_ x : x) (\_ _ x : x)
lOr = triage (\x : x) (\_ _ : true) (\_ _ x : true)
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))))
x = map (\i : lconcat "Successfully concatenated " i) [("two strings!")]
equal x [("Successfully concatenated two strings!")]