Compare commits
	
		
			14 Commits
		
	
	
		
			0.5.0
			...
			0.6.0-8995
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8995efce15 | |||
| 03e2f6b93e | |||
| 419d66b4d1 | |||
| 4b98afd803 | |||
| 0768e11a02 | |||
| 42fce0ae43 | |||
| 51b1eb070f | |||
| c2e5a8985a | |||
| 9d7e4daa41 | |||
| edde0a80c9 | |||
| 35163a5d54 | |||
| ca7f09e2ac | |||
| 82e29440b0 | |||
| ad02c8b86a | 
							
								
								
									
										86
									
								
								.gitea/workflows/test-and-build.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								.gitea/workflows/test-and-build.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | ||||
| name: Test, Build, and Release | ||||
|  | ||||
| on:  | ||||
|   push: | ||||
|     tags: | ||||
|       - '*' | ||||
|  | ||||
| jobs: | ||||
|   test: | ||||
|     container: | ||||
|       image: docker.matri.cx/nix-runner:v0.1.0 | ||||
|       credentials: | ||||
|         username: ${{ secrets.REGISTRY_USERNAME }} | ||||
|         password: ${{ secrets.REGISTRY_PASSWORD }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Set up cache for Cabal | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: | | ||||
|             ~/.cache/cabal | ||||
|             ~/.config/cabal | ||||
|             ~/.local/state/cabal | ||||
|           key: cabal-${{ hashFiles('tricu.cabal') }} | ||||
|           restore-keys: | | ||||
|             cabal- | ||||
|  | ||||
|       - name: Set up cache for Nix | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: | | ||||
|             /nix/store | ||||
|             /nix/var/nix/cache | ||||
|           key: nix-${{ hashFiles('flake.lock') }} | ||||
|           restore-keys: | | ||||
|             nix- | ||||
|  | ||||
|       - name: Initialize Cabal and update package list | ||||
|         run: | | ||||
|           nix develop --command cabal update | ||||
|  | ||||
|       - name: Run test suite | ||||
|         run: | | ||||
|           nix develop --command cabal test | ||||
|  | ||||
|   build: | ||||
|     needs: test | ||||
|     container: | ||||
|       image: docker.matri.cx/nix-runner:v0.1.0 | ||||
|       credentials: | ||||
|         username: ${{ secrets.REGISTRY_USERNAME }} | ||||
|         password: ${{ secrets.REGISTRY_PASSWORD }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Set up cache for Nix | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: | | ||||
|             /nix/store | ||||
|             /nix/var/nix/cache | ||||
|           key: nix-${{ hashFiles('flake.lock') }} | ||||
|           restore-keys: | | ||||
|             nix- | ||||
|    | ||||
|       - name: Build binary | ||||
|         run: | | ||||
|           nix build | ||||
|           ls -alh ./result/bin/tricu | ||||
|    | ||||
|       - name: Setup go for release actoin | ||||
|         uses: actions/setup-go@v5 | ||||
|         with: | ||||
|           go-version: '>=1.20.1' | ||||
|    | ||||
|       - name: Release binary | ||||
|         uses: https://gitea.com/actions/release-action@main | ||||
|         with: | ||||
|           files: |- | ||||
|             ./result/bin/tricu | ||||
|           api_key: '${{ secrets.RELEASE_TOKEN }}' | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @ -2,7 +2,7 @@ | ||||
|  | ||||
| ## Introduction | ||||
|  | ||||
| tricu (pronounced like "tree-shoe" in English) is a purely functional interpreted language implemented in Haskell. [I'm](https://eversole.co) developing tricu to further research the possibilities offered by the various forms of [Tree Calculi](https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf). | ||||
| tricu (pronounced "tree-shoe") is a purely functional interpreted language implemented in Haskell. [I'm](https://eversole.co) developing tricu to further research the possibilities offered by the various forms of [Tree Calculi](https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf). | ||||
|  | ||||
| tricu offers minimal syntax sugar yet manages to provide a complete, intuitive, and familiar programming environment. There is great power in simplicity. tricu offers: | ||||
|  | ||||
| @ -29,11 +29,11 @@ tricu > "Hello,  world!" | ||||
| tricu < -- Intensionality! We can inspect the structure of a function. | ||||
| tricu < triage = (\a b c : t (t a b) c) | ||||
| tricu < test = triage "Leaf" (\z : "Stem") (\a b : "Fork") | ||||
| tricu < test t t | ||||
| tricu < test (t t) | ||||
| tricu > "Stem" | ||||
| tricu < -- We can even write a function to convert a function to source code | ||||
| tricu < toTString id | ||||
| tricu > "t (t (t t)) t" | ||||
| tricu < -- We can even write a function to convert a term back to source code | ||||
| tricu < toSource not? | ||||
| tricu > "(t (t (t t) (t t t)) (t t (t t t)))" | ||||
| ``` | ||||
|  | ||||
| ## Installation and Use | ||||
|  | ||||
| @ -1,34 +0,0 @@ | ||||
| -- Level Order Traversal of a labelled binary tree | ||||
| -- Objective: Print each "level" of the tree on a separate line | ||||
| -- | ||||
| -- NOTICE: This demo relies on tricu base library functions | ||||
| --  | ||||
| -- We model labelled binary trees as sublists where values act as labels. We | ||||
| -- require explicit notation of empty nodes. Empty nodes can be represented  | ||||
| -- with an empty list, `[]`, which is equivalent to a single node `t`.  | ||||
| -- | ||||
| -- Example tree inputs: | ||||
| -- [("1") [("2") [("4") t t] t] [("3") [("5") t t] [("6") t t]]]] | ||||
| -- Graph: | ||||
| --       1 | ||||
| --      / \ | ||||
| --     2   3 | ||||
| --    /   /  \ | ||||
| --   4   5    6 | ||||
| -- | ||||
|  | ||||
| isLeaf = (\node : lOr (emptyList node) (emptyList (tail node))) | ||||
| getLabel = (\node : head node) | ||||
| getLeft = (\node : if (emptyList node) [] (if (emptyList (tail node)) [] (head (tail node)))) | ||||
| getRight = (\node : if (emptyList node) [] (if (emptyList (tail node)) [] (if (emptyList (tail (tail node))) [] (head (tail (tail node)))))) | ||||
|  | ||||
| processLevel = y (\self queue : if (emptyList queue) [] (pair (map getLabel queue) (self (filter (\node : not (emptyList node)) (lconcat (map getLeft queue) (map getRight queue)))))) | ||||
| levelOrderTraversal = (\a : processLevel (t a t)) | ||||
| toLineString = y (\self levels : if (emptyList levels) "" (lconcat (lconcat (map (\x : lconcat x " ") (head levels)) "") (if (emptyList (tail levels)) "" (lconcat (t (t 10 t) t) (self (tail levels)))))) | ||||
| levelOrderToString = (\s : toLineString (levelOrderTraversal s)) | ||||
|  | ||||
| flatten = foldl (\acc x : lconcat acc x) "" | ||||
| flatLOT = (\s : lconcat (t 10 t) (flatten (levelOrderToString s))) | ||||
|  | ||||
| exampleOne = flatLOT [("1") [("2") [("4") t t] t] [("3") [("5") t t] [("6") t t]]]] | ||||
| exampleTwo = flatLOT [("1") [("2") [("4") [("8") t t] [("9") t t]] [("6") [("10") t t] [("12") t t]]] [("3") [("5") [("11") t t] t] [("7") t t]]] | ||||
							
								
								
									
										24
									
								
								demos/equality.tri
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								demos/equality.tri
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| false = t | ||||
| true  = t t | ||||
|  | ||||
| triage = (\a b c : t (t a b) c) | ||||
|  | ||||
| matchBool = (\ot of : triage | ||||
|   of | ||||
|   (\_ : ot) | ||||
|   (\_ _ : ot) | ||||
| ) | ||||
|  | ||||
| not_TC?     = t (t (t t) (t t t)) (t t (t t t)) | ||||
| not_Lambda? = matchBool false true | ||||
|  | ||||
| areEqual? = equal not_TC not_Lambda | ||||
|  | ||||
| true_TC?  = not_TC false | ||||
| false_TC? = not_TC true | ||||
|  | ||||
| true_Lambda?  = not_Lambda false | ||||
| false_Lambda? = not_Lambda true | ||||
|  | ||||
| areTrueEqual?  = equal true_TC  true_Lambda | ||||
| areFalseEqual? = equal false_TC false_Lambda | ||||
							
								
								
									
										65
									
								
								demos/levelOrderTraversal.tri
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								demos/levelOrderTraversal.tri
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | ||||
| -- Level Order Traversal of a labelled binary tree | ||||
| -- Objective: Print each "level" of the tree on a separate line | ||||
| -- | ||||
| -- NOTICE: This demo relies on tricu base library functions | ||||
| --  | ||||
| -- We model labelled binary trees as sublists where values act as labels. We | ||||
| -- require explicit notation of empty nodes. Empty nodes can be represented  | ||||
| -- with an empty list, `[]`, which is equivalent to a single node `t`.  | ||||
| -- | ||||
| -- Example tree inputs: | ||||
| -- [("1") [("2") [("4") t t] t] [("3") [("5") t t] [("6") t t]]]] | ||||
| -- Graph: | ||||
| --       1 | ||||
| --      / \ | ||||
| --     2   3 | ||||
| --    /   /  \ | ||||
| --   4   5    6 | ||||
| -- | ||||
|  | ||||
| label = (\node : head node) | ||||
|  | ||||
| left  = (\node : if (emptyList node)  | ||||
|   []  | ||||
|   (if (emptyList (tail node))  | ||||
|     []  | ||||
|     (head (tail node)))) | ||||
|  | ||||
| right = (\node : if (emptyList node)  | ||||
|   []  | ||||
|   (if (emptyList (tail node))  | ||||
|     []  | ||||
|     (if (emptyList (tail (tail node)))  | ||||
|       []  | ||||
|       (head (tail (tail node)))))) | ||||
|  | ||||
| processLevel = y (\self queue : if (emptyList queue)  | ||||
|   []  | ||||
|   (pair (map label queue) (self (filter  | ||||
|     (\node : not (emptyList node))  | ||||
|       (lconcat (map left queue) (map right queue)))))) | ||||
|  | ||||
| levelOrderTraversal_ = (\a : processLevel (t a t)) | ||||
|  | ||||
| toLineString = y (\self levels : if (emptyList levels)  | ||||
|   ""  | ||||
|   (lconcat  | ||||
|     (lconcat (map (\x : lconcat x " ") (head levels)) "")  | ||||
|     (if (emptyList (tail levels)) "" (lconcat (t (t 10 t) t) (self (tail levels)))))) | ||||
|  | ||||
| levelOrderToString = (\s : toLineString (levelOrderTraversal_ s)) | ||||
|  | ||||
| flatten = foldl (\acc x : lconcat acc x) "" | ||||
|  | ||||
| levelOrderTraversal = (\s : lconcat (t 10 t) (flatten (levelOrderToString s))) | ||||
|  | ||||
| exampleOne = levelOrderTraversal [("1")  | ||||
|                                  [("2") [("4") t t] t]  | ||||
|                                  [("3") [("5") t t] [("6") t t]]] | ||||
|  | ||||
| exampleTwo = levelOrderTraversal [("1")  | ||||
|                                  [("2") [("4") [("8") t t] [("9") t t]]  | ||||
|                                         [("6") [("10") t t] [("12") t t]]]  | ||||
|                                  [("3") [("5") [("11") t t] t] [("7") t t]]] | ||||
|  | ||||
| exampleTwo | ||||
							
								
								
									
										46
									
								
								demos/toSource.tri
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								demos/toSource.tri
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| -- Thanks to intensionality, we can inspect the structure of a given value | ||||
| -- even if it's a function. This includes lambdas which are eliminated to | ||||
| -- Tree Calculus (TC) terms during evaluation. | ||||
|  | ||||
| -- Triage takes four arguments: the first three represent behaviors for each  | ||||
| -- structural case in Tree Calculus (Leaf, Stem, and Fork). | ||||
| -- The fourth argument is the value whose structure is inspected. By evaluating  | ||||
| -- the Tree Calculus term, `triage` enables branching logic based on the term's  | ||||
| -- shape, making it possible to perform structure-specific operations such as | ||||
| -- reconstructing the terms' source code representation. | ||||
| triage = (\a b c : t (t a b) c) | ||||
|  | ||||
| -- Base case of a single Leaf | ||||
| sourceLeaf = t (head "t") | ||||
|  | ||||
| -- Stem case | ||||
| sourceStem = (\convert : (\a rest : | ||||
|   t (head "(")                       -- Start with a left parenthesis "(". | ||||
|     (t (head "t")                    -- Add a "t" | ||||
|       (t (head " ")                  -- Add a space. | ||||
|         (convert a                   -- Recursively convert the argument. | ||||
|           (t (head ")") rest))))))   -- Close with ")" and append the rest. | ||||
|  | ||||
| -- Fork case | ||||
| sourceFork = (\convert : (\a b rest : | ||||
|   t (head "(")                           -- Start with a left parenthesis "(". | ||||
|     (t (head "t")                        -- Add a "t" | ||||
|       (t (head " ")                      -- Add a space. | ||||
|         (convert a                       -- Recursively convert the first arg. | ||||
|           (t (head " ")                  -- Add another space. | ||||
|             (convert b                   -- Recursively convert the second arg. | ||||
|               (t (head ")") rest)))))))) -- Close with ")" and append the rest. | ||||
|  | ||||
| -- Wrapper around triage  | ||||
| toSource_ = y (\self arg : | ||||
|   triage | ||||
|     sourceLeaf        -- Triage `a` case, Leaf | ||||
|     (sourceStem self) -- Triage `b` case, Stem | ||||
|     (sourceFork self) -- Triage `c` case, Fork | ||||
|     arg)              -- The term to be inspected | ||||
|  | ||||
| -- toSource takes a single TC term and returns a String | ||||
| toSource = (\v : toSource_ v "") | ||||
|  | ||||
| exampleOne = toSource true -- OUT: "(t t)" | ||||
| exampleTwo = toSource not  -- OUT: "(t (t (t t) (t t t)) (t t (t t t)))" | ||||
							
								
								
									
										133
									
								
								lib/base.tri
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								lib/base.tri
									
									
									
									
									
								
							| @ -1,41 +1,96 @@ | ||||
| false = t | ||||
| _ = t | ||||
| true = 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) | ||||
| iD = b (b iC) iC | ||||
| iE = b (b iD) iC | ||||
| yi = (\i : b m (c b (i m))) | ||||
| y = yi iC | ||||
| yC = yi iD | ||||
| yD = yi iE | ||||
| id = (\a : a) | ||||
| _     = t | ||||
| true  = 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) | ||||
| iD    = b (b iC) iC | ||||
| iE    = b (b iD) iC | ||||
| yi    = (\i : b m (c b (i m))) | ||||
| y     = yi iC | ||||
| yC    = yi iD | ||||
| yD    = yi iE | ||||
| id    = (\a : a) | ||||
| pair  = t | ||||
| if    = (\cond then else : t (t else (t t then)) t cond) | ||||
|  | ||||
| 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) | ||||
| 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))) | ||||
| 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)))) | ||||
| 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) | ||||
| test   = triage "Leaf" (\_ : "Stem") (\_ _ : "Fork") | ||||
|  | ||||
| matchBool = (\ot of : triage  | ||||
|   of  | ||||
|   (\_ : ot)  | ||||
|   (\_ _ : ot) | ||||
| ) | ||||
|  | ||||
| matchList = (\oe oc : triage  | ||||
|   oe  | ||||
|   _  | ||||
|   oc | ||||
| ) | ||||
|  | ||||
| matchPair = (\op : triage  | ||||
|   _  | ||||
|   _  | ||||
|   op | ||||
| ) | ||||
|  | ||||
| not? = matchBool false true | ||||
| and? = matchBool id (\_ : false) | ||||
| emptyList? = matchList true (\_ _ : false) | ||||
|  | ||||
| head = matchList t (\head _ : head) | ||||
| tail = matchList t (\_ tail : tail) | ||||
|  | ||||
| lconcat = y (\self : matchList  | ||||
|   (\k : k)  | ||||
|   (\h r k : pair h (self r k))) | ||||
|  | ||||
| lAnd = (triage  | ||||
|   (\_     : false)  | ||||
|   (\_ x   : x)  | ||||
|   (\_ _ x : x) | ||||
| ) | ||||
|  | ||||
| lOr = (triage  | ||||
|   (\x     : x)  | ||||
|   (\_ _   : true)  | ||||
|   (\_ _ _ : true) | ||||
| ) | ||||
|  | ||||
| map_ = y (\self :  | ||||
|   matchList  | ||||
|     (\_ : t)  | ||||
|     (\head tail f : pair (f head) (self tail f))) | ||||
| map = (\f l : map_ l f) | ||||
|  | ||||
| equal? = y (\self : triage  | ||||
|   (triage  | ||||
|     true  | ||||
|     (\_   : false)  | ||||
|     (\_ _ : false))  | ||||
|   (\ax :  | ||||
|     triage  | ||||
|       false  | ||||
|       (self ax)  | ||||
|       (\_ _ : false))  | ||||
|   (\ax ay :  | ||||
|     triage  | ||||
|       false  | ||||
|       (\_ : false)  | ||||
|       (\bx by : lAnd (self ax bx) (self ay by)))) | ||||
|  | ||||
| filter_ = y (\self : matchList  | ||||
|   (\_ : t)  | ||||
|   (\head tail f : matchBool (t head) i (f head) (self tail f))) | ||||
| filter  = (\f l : filter_ l f) | ||||
|  | ||||
| foldl_ = y (\self f l x : matchList (\acc : acc) (\head tail acc : self f tail (f acc head)) l x) | ||||
| 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  = (\f x l : foldr_ x f l) | ||||
|  | ||||
							
								
								
									
										167
									
								
								src/Eval.hs
									
									
									
									
									
								
							
							
						
						
									
										167
									
								
								src/Eval.hs
									
									
									
									
									
								
							| @ -8,110 +8,85 @@ import Data.Map  (Map) | ||||
| import qualified Data.Map as Map | ||||
| import qualified Data.Set as Set | ||||
|  | ||||
| evalSingle :: Map String T -> TricuAST -> Map String T | ||||
| evalSingle env term = case term of | ||||
|   SFunc name [] body -> | ||||
|     let lineNoLambda = eliminateLambda body | ||||
|         result = evalAST env lineNoLambda | ||||
|     in Map.insert "__result" result (Map.insert name result env) | ||||
|   SLambda _ body -> | ||||
|     let result = evalAST env body | ||||
|     in Map.insert "__result" result env | ||||
|   SApp func arg -> | ||||
|     let result = apply (evalAST env $ eliminateLambda func) (evalAST env $ eliminateLambda arg) | ||||
|     in Map.insert "__result" result env | ||||
|   SVar name -> | ||||
|     case Map.lookup name env of | ||||
|       Just value -> Map.insert "__result" value env | ||||
|       Nothing -> errorWithoutStackTrace $ "Variable " ++ name ++ " not defined" | ||||
|   _ -> | ||||
|     let result = evalAST env term | ||||
|     in Map.insert "__result" result env | ||||
| evalSingle :: Env -> TricuAST -> Env | ||||
| 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 = | ||||
|       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 :: Map String T -> [TricuAST] -> Map String T | ||||
| 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) = | ||||
|     let lineNoLambda = eliminateLambda line | ||||
|         updatedEnv = evalSingle env lineNoLambda | ||||
|     in evalTricu updatedEnv rest | ||||
| evalTricu :: Env -> [TricuAST] -> Env | ||||
| 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 :: Map String T -> TricuAST -> T | ||||
| evalAST env term = case term of | ||||
|   SVar name -> case Map.lookup name env of | ||||
|     Just value -> value | ||||
|     Nothing -> errorWithoutStackTrace $ "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 env) elems) | ||||
|   SEmpty -> Leaf | ||||
|   SFunc name args body -> | ||||
|     errorWithoutStackTrace $ "Unexpected function definition " ++ name | ||||
|   SLambda {} -> errorWithoutStackTrace "Internal error: SLambda found in evalAST after elimination." | ||||
|  | ||||
| eliminateLambda :: TricuAST -> TricuAST | ||||
| eliminateLambda (SLambda (v:vs) body) | ||||
|   | null vs = lambdaToT v (eliminateLambda body) | ||||
|   | otherwise = eliminateLambda (SLambda [v] (SLambda vs body)) | ||||
| eliminateLambda (SApp f arg) = SApp (eliminateLambda f) (eliminateLambda arg) | ||||
| eliminateLambda (TStem t) = TStem (eliminateLambda t) | ||||
| eliminateLambda (TFork l r) = TFork (eliminateLambda l) (eliminateLambda r) | ||||
| eliminateLambda (SList xs) = SList (map eliminateLambda xs) | ||||
| eliminateLambda other = other | ||||
| evalAST :: Env -> TricuAST -> T | ||||
| evalAST env term | ||||
|   | SLambda _ _ <- term = evalAST env (elimLambda term) | ||||
|   | SVar   name <- term = evalVar name | ||||
|   | TLeaf       <- term = Leaf | ||||
|   | TStem  t    <- term = Stem (evalAST env t) | ||||
|   | TFork  t u  <- term = Fork (evalAST env t) (evalAST env u) | ||||
|   | SApp   t u  <- term = apply (evalAST env t) (evalAST env u) | ||||
|   | SStr   s    <- term = ofString s | ||||
|   | SInt   n    <- term = ofNumber n | ||||
|   | SList  xs   <- term = ofList (map (evalAST env) xs) | ||||
|   | SEmpty      <- term = Leaf | ||||
|   | otherwise           = errorWithoutStackTrace "Unexpected AST term" | ||||
|     where | ||||
|       evalVar name = Map.findWithDefault | ||||
|         (errorWithoutStackTrace $ "Variable " ++ name ++ " not defined") | ||||
|         name env | ||||
|  | ||||
| -- https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf | ||||
| -- Chapter 4: Lambda-Abstraction | ||||
| lambdaToT :: String -> TricuAST -> TricuAST | ||||
| lambdaToT x (SVar y) | ||||
|   | x == y = tI | ||||
| lambdaToT x (SVar y) | ||||
|   | x /= y = SApp tK (SVar y) | ||||
| lambdaToT x t | ||||
|   | not (isFree x t) = SApp tK t | ||||
| lambdaToT x (SApp n u) | ||||
|   | not (isFree x (SApp n u)) = SApp tK (SApp (eliminateLambda n) (eliminateLambda u)) | ||||
| lambdaToT x (SApp n u) = SApp (SApp tS (lambdaToT x (eliminateLambda n))) (lambdaToT x (eliminateLambda u)) | ||||
| lambdaToT x body | ||||
|   | not (isFree x body) = SApp tK body | ||||
|   | otherwise = SApp (SApp tS (lambdaToT x body)) TLeaf | ||||
| elimLambda :: TricuAST -> TricuAST | ||||
| elimLambda = go | ||||
|   where | ||||
|     go (SLambda (v:vs) body) | ||||
|       | null vs              = toSKI v (elimLambda body) | ||||
|       | otherwise            = elimLambda (SLambda [v] (SLambda vs body)) | ||||
|     go (SApp f g)            = SApp (elimLambda f) (elimLambda g) | ||||
|     go x                     = x | ||||
|  | ||||
| freeVars :: TricuAST -> Set.Set String | ||||
| freeVars (SVar v) = Set.singleton v | ||||
| freeVars (SInt _) = Set.empty | ||||
| freeVars (SStr _) = Set.empty | ||||
| freeVars (SList xs) = foldMap freeVars xs | ||||
| freeVars (SApp f arg) = freeVars f <> freeVars arg | ||||
| freeVars TLeaf = Set.empty | ||||
| freeVars (SFunc _ _ b) = freeVars b | ||||
| freeVars (TStem t) = freeVars t | ||||
| freeVars (TFork l r) = freeVars l <> freeVars r | ||||
| freeVars (SLambda vs b) = foldr Set.delete (freeVars b) vs | ||||
|     toSKI x (SVar y) | ||||
|       | x == y           = _I | ||||
|       | otherwise        = SApp _K (SVar y) | ||||
|     toSKI x t@(SApp n u) | ||||
|       | not (isFree x t) = SApp _K t | ||||
|       | otherwise        = SApp (SApp _S (toSKI x n)) (toSKI x u) | ||||
|     toSKI x t | ||||
|       | not (isFree x t) = SApp _K t | ||||
|       | otherwise        = SApp (SApp _S (toSKI x t)) TLeaf | ||||
|  | ||||
| isFree :: String -> TricuAST -> Bool | ||||
| isFree x = Set.member x . freeVars | ||||
|     _S = parseSingle "t (t (t t t)) t" | ||||
|     _K = parseSingle "t t" | ||||
|     _I = parseSingle "t (t (t t)) t" | ||||
|      | ||||
|     isFree x = Set.member x . freeVars | ||||
|     freeVars (SVar    v    ) = Set.singleton v | ||||
|     freeVars (SInt    _    ) = Set.empty | ||||
|     freeVars (SStr    _    ) = Set.empty | ||||
|     freeVars (SList   s    ) = foldMap freeVars s | ||||
|     freeVars (SApp    f a  ) = freeVars f <> freeVars a | ||||
|     freeVars (TLeaf        ) = Set.empty | ||||
|     freeVars (SFunc   _ _ b) = freeVars b | ||||
|     freeVars (TStem   t    ) = freeVars t | ||||
|     freeVars (TFork   l r  ) = freeVars l <> freeVars r | ||||
|     freeVars (SLambda v b  ) = foldr Set.delete (freeVars b) v | ||||
|  | ||||
| -- We need the SKI operators in an unevaluated TricuAST tree form so that we | ||||
| -- can keep the evaluation functions straightforward | ||||
| tI :: TricuAST | ||||
| tI = SApp (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf))) TLeaf | ||||
|  | ||||
| tK :: TricuAST | ||||
| tK = SApp TLeaf TLeaf | ||||
|  | ||||
| tS :: TricuAST | ||||
| tS = SApp (SApp TLeaf (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf))) TLeaf | ||||
|  | ||||
| result :: Map String T -> T | ||||
| result :: Env -> T | ||||
| result r = case Map.lookup "__result" r of | ||||
|   Just a -> a | ||||
|   Nothing -> errorWithoutStackTrace "No __result field found in provided environment" | ||||
|  | ||||
							
								
								
									
										13
									
								
								src/Lexer.hs
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/Lexer.hs
									
									
									
									
									
								
							| @ -18,7 +18,10 @@ keywordT = string "t" *> notFollowedBy alphaNumChar *> pure LKeywordT | ||||
| identifier :: Lexer LToken | ||||
| identifier = do | ||||
|   first <- letterChar <|> char '_' | ||||
|   rest <- many (letterChar <|> char '_' <|> char '-' <|> digitChar) | ||||
|   rest  <- many $ letterChar  | ||||
|               <|> digitChar  | ||||
|               <|> char '_' <|> char '-' <|> char '?' <|> char '!' | ||||
|               <|> char '$' <|> char '#' <|> char '@' <|> char '%' | ||||
|   let name = first : rest | ||||
|   if (name == "t" || name == "__result") | ||||
|     then fail "Keywords (`t`, `__result`) cannot be used as an identifier" | ||||
| @ -61,7 +64,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 +81,8 @@ tricuLexer = do | ||||
|   pure tokens | ||||
|     where | ||||
|       tricuLexer' =  | ||||
|         [ try identifier | ||||
|         [ try lnewline | ||||
|         , try identifier | ||||
|         , try keywordT | ||||
|         , try integerLiteral | ||||
|         , try stringLiteral | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										513
									
								
								src/Parser.hs
									
									
									
									
									
								
							
							
						
						
									
										513
									
								
								src/Parser.hs
									
									
									
									
									
								
							| @ -1,277 +1,304 @@ | ||||
| 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 | ||||
|   { parenDepth  :: Int | ||||
|   , bracketDepth :: 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 { parenDepth   = parenDepth st   + 1 } | ||||
| updateDepth LOpenBracket  st = st { bracketDepth = bracketDepth st + 1 } | ||||
| updateDepth LCloseParen   st = st { parenDepth   = parenDepth st   - 1 } | ||||
| updateDepth LCloseBracket st = st { bracketDepth = bracketDepth st - 1 } | ||||
| updateDepth _ st = st | ||||
|  | ||||
| topLevelNewline :: ParserM () | ||||
| topLevelNewline = do | ||||
|   st <- get | ||||
|   if parenDepth st == 0 && bracketDepth st == 0 | ||||
|     then void (satisfyM (== LNewline)) | ||||
|     else fail "Top-level exit in nested context (paren or bracket)" | ||||
|  | ||||
| parseProgram :: [LToken] -> Either (ParseErrorBundle [LToken] Void) [TricuAST] | ||||
| parseProgram tokens = | ||||
|   runParser (evalStateT (parseProgramM <* finalizeDepth <* eof) (PState 0 0)) "" tokens | ||||
|  | ||||
| parseSingleExpr :: [LToken] -> Either (ParseErrorBundle [LToken] Void) TricuAST | ||||
| parseSingleExpr tokens = | ||||
|   runParser (evalStateT (scnParserM *> parseExpressionM <* finalizeDepth <* eof) (PState 0 0)) "" tokens | ||||
|  | ||||
| finalizeDepth :: ParserM () | ||||
| finalizeDepth = do | ||||
|   st <- get | ||||
|   case (parenDepth st, bracketDepth st) of | ||||
|     (0, 0) -> pure () | ||||
|     (p, b) -> fail $ "Unmatched tokens: " ++ show (p, b) | ||||
|  | ||||
| parseTricu :: String -> [TricuAST] | ||||
| parseTricu input | ||||
|   | null tokens = [] | ||||
|   | otherwise = map parseSingle tokens | ||||
|   where | ||||
|     tokens = case lexTricu input of | ||||
|       [] -> [] | ||||
|       tokens -> lines input | ||||
| parseTricu input = | ||||
|   case lexTricu input of | ||||
|     [] -> [] | ||||
|     toks -> | ||||
|       case parseProgram toks of | ||||
|         Left err   -> errorWithoutStackTrace (handleParseError err) | ||||
|         Right asts -> asts | ||||
|  | ||||
| parseSingle :: String -> TricuAST | ||||
| parseSingle input = case lexTricu input of | ||||
|   [] -> SEmpty | ||||
|   tokens -> case runParser parseExpression "" tokens of | ||||
|     Left err -> error $ handleParseError err | ||||
|     Right ast -> ast | ||||
| parseSingle input = | ||||
|   case lexTricu input of | ||||
|     [] -> SEmpty | ||||
|     toks -> | ||||
|       case parseSingleExpr toks of | ||||
|         Left err -> errorWithoutStackTrace (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 | (parenDepth st > 0 || bracketDepth st > 0) && case t of | ||||
|          LNewline -> True | ||||
|          _        -> False -> void $ satisfyM $ \case | ||||
|            LNewline -> True | ||||
|            _        -> False | ||||
|      | otherwise -> fail "In nested 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 $ \case | ||||
|     LIdentifier _ -> True | ||||
|     _             -> False | ||||
|   args <- many $ satisfyM $ \case | ||||
|     LIdentifier _ -> True | ||||
|     _             -> False | ||||
|   _    <- 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 $ \case | ||||
|       LIdentifier _ -> True | ||||
|       _             -> False | ||||
|     rest  <- many $ satisfyM $ \case | ||||
|       LIdentifier _ -> True | ||||
|       _             -> False | ||||
|     _     <- 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 $ \case | ||||
|     LKeywordT -> True | ||||
|     _         -> False | ||||
|   notFollowedBy $ lift $ satisfy (== LAssign) | ||||
|   pure TLeaf | ||||
|  | ||||
| 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 | ||||
|  | ||||
| parseTreeLeafOrParenthesizedM :: ParserM TricuAST | ||||
| parseTreeLeafOrParenthesizedM = choice | ||||
|   [ between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) parseTreeTermM | ||||
|   , parseTreeLeafM | ||||
|   ] | ||||
|  | ||||
| parseAtomicM :: ParserM TricuAST | ||||
| parseAtomicM = choice | ||||
|   [ parseVarM | ||||
|   , parseTreeLeafM | ||||
|   , parseListLiteralM | ||||
|   , parseGroupedM | ||||
|   , parseLiteralM | ||||
|   ] | ||||
|  | ||||
| parseGroupedM :: ParserM TricuAST | ||||
| parseGroupedM = between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) $ | ||||
|   scnParserM *> parseExpressionM <* scnParserM | ||||
|  | ||||
| parseLiteralM :: ParserM TricuAST | ||||
| parseLiteralM = choice | ||||
|   [ parseIntLiteralM | ||||
|   , parseStrLiteralM | ||||
|   ] | ||||
|  | ||||
| parseListLiteralM :: ParserM TricuAST | ||||
| parseListLiteralM = do | ||||
|   _        <- satisfyM (== LOpenBracket) | ||||
|   elements <- many $ do | ||||
|     scnParserM | ||||
|     parseListItemM | ||||
|   scnParserM | ||||
|   _        <- satisfyM (== LCloseBracket) | ||||
|   pure (SList elements) | ||||
|  | ||||
| parseListItemM :: ParserM TricuAST | ||||
| parseListItemM = choice | ||||
|   [ parseGroupedItemM | ||||
|   , parseListLiteralM | ||||
|   , parseSingleItemM | ||||
|   ] | ||||
|  | ||||
| parseGroupedItemM :: ParserM TricuAST | ||||
| parseGroupedItemM = do | ||||
|   _     <- satisfyM (== LOpenParen) | ||||
|   inner <- parseExpressionM | ||||
|   _     <- satisfyM (== LCloseParen) | ||||
|   pure inner | ||||
|  | ||||
| parseSingleItemM :: ParserM TricuAST | ||||
| parseSingleItemM = do | ||||
|   token <- satisfyM $ \case | ||||
|     LIdentifier _ -> True | ||||
|     LKeywordT     -> True | ||||
|     _             -> False | ||||
|   case token of | ||||
|     LIdentifier name -> pure (SVar name) | ||||
|     LKeywordT        -> pure TLeaf | ||||
|     _                -> fail "Unexpected token in list item" | ||||
|  | ||||
| parseVarM :: ParserM TricuAST | ||||
| parseVarM = do | ||||
|   LIdentifier name <- satisfyM $ \case | ||||
|     LIdentifier _ -> True | ||||
|     _             -> False | ||||
|   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 $ \case | ||||
|     LIntegerLiteral _ -> True | ||||
|     _                 -> False | ||||
|   pure (SInt value) | ||||
|  | ||||
| parseStrLiteralM :: ParserM TricuAST | ||||
| parseStrLiteralM = do | ||||
|   LStringLiteral value <- satisfyM $ \case  | ||||
|     LStringLiteral _ -> True | ||||
|     _ -> False | ||||
|   pure (SStr value) | ||||
|  | ||||
| getIdentifier :: LToken -> String | ||||
| getIdentifier (LIdentifier name) = name | ||||
| getIdentifier _ = error "Expected identifier" | ||||
| getIdentifier _                  = errorWithoutStackTrace "Expected identifier" | ||||
|  | ||||
| parseTreeTerm :: Parser TricuAST | ||||
| parseTreeTerm = do | ||||
|   base <- parseTreeLeafOrParenthesized | ||||
|   rest <- many parseTreeLeafOrParenthesized | ||||
|   pure $ foldl combine base rest | ||||
|   where | ||||
|     combine acc next = case acc of | ||||
|       TLeaf -> TStem next | ||||
|       TStem t -> TFork t next | ||||
|       TFork _ _ -> TFork acc next | ||||
|  | ||||
| parseTreeLeafOrParenthesized :: Parser TricuAST | ||||
| parseTreeLeafOrParenthesized = choice | ||||
|   [ between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseTreeTerm | ||||
|   , parseTreeLeaf | ||||
|   ] | ||||
|  | ||||
| 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 | ||||
|   ] | ||||
|  | ||||
| parseGrouped :: Parser TricuAST | ||||
| parseGrouped = between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseExpression | ||||
|  | ||||
| parseLiteral :: Parser TricuAST | ||||
| parseLiteral = choice | ||||
|   [ parseIntLiteral | ||||
|   , parseStrLiteral | ||||
|   ] | ||||
|  | ||||
| parens :: Parser TricuAST -> Parser TricuAST | ||||
| parens p = do | ||||
|   satisfy (== LOpenParen) | ||||
|   result <- p | ||||
|   satisfy (== LCloseParen) | ||||
|   return result | ||||
|  | ||||
| parseListLiteral :: Parser TricuAST | ||||
| parseListLiteral = do | ||||
|   satisfy (== LOpenBracket) | ||||
|   elements <- many parseListItem | ||||
|   satisfy (== LCloseBracket) | ||||
|   return (SList elements) | ||||
|  | ||||
| parseListItem :: Parser TricuAST | ||||
| parseListItem = choice | ||||
|   [ parseGroupedItem | ||||
|   , parseListLiteral | ||||
|   , parseSingleItem | ||||
|   ] | ||||
|  | ||||
| parseGroupedItem :: Parser TricuAST | ||||
| parseGroupedItem = do | ||||
|   satisfy (== LOpenParen) | ||||
|   inner <- parseExpression | ||||
|   satisfy (== LCloseParen) | ||||
|   return inner | ||||
|  | ||||
| parseSingleItem :: Parser TricuAST | ||||
| parseSingleItem = do | ||||
|   token <- satisfy isListItem | ||||
|   case token of | ||||
|     LIdentifier name -> return (SVar name) | ||||
|     LKeywordT -> return TLeaf | ||||
|     _ -> fail "Unexpected token in list item" | ||||
|  | ||||
| isListItem :: LToken -> Bool | ||||
| isListItem (LIdentifier _) = True | ||||
| 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) | ||||
|  | ||||
| parseIntLiteral :: Parser TricuAST | ||||
| parseIntLiteral = do | ||||
|   LIntegerLiteral value <- satisfy isIntegerLiteral | ||||
|   return (SInt value) | ||||
|  | ||||
| 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 = case runParser (parseTernaryTerm <* eof) "" input of | ||||
|   Left err -> Left (errorBundlePretty err) | ||||
|   Right ast -> Right ast | ||||
|  | ||||
| -- Error Handling | ||||
| handleParseError :: ParseErrorBundle [LToken] Void -> String | ||||
| handleParseError bundle = | ||||
|   let errors = bundleErrors bundle | ||||
|       errorList = toList errors | ||||
|       formattedErrors = map showError errorList | ||||
|       formattedErrors = map formatError (Data.List.NonEmpty.toList errors) | ||||
|   in unlines ("Parse error(s) encountered:" : formattedErrors) | ||||
|  | ||||
| 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) | ||||
| showError (FancyError offset 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) | ||||
| formatError :: ParseError [LToken] Void -> String | ||||
| formatError (TrivialError offset unexpected expected) = | ||||
|   let unexpectedMsg = case unexpected of | ||||
|         Just x  -> "unexpected token " ++ show x | ||||
|         Nothing -> "unexpected end of input" | ||||
|       expectedMsg = if null expected | ||||
|         then "" | ||||
|         else "expected " ++ show (Set.toList expected) | ||||
|   in "Parse error at offset " ++ show offset ++ ": " ++ unexpectedMsg ++ | ||||
|      if null expectedMsg then "" else " " ++ expectedMsg | ||||
| formatError (FancyError offset _) = | ||||
|   "Parse error at offset " ++ show offset ++ ": unexpected FancyError" | ||||
|  | ||||
							
								
								
									
										65
									
								
								src/REPL.hs
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								src/REPL.hs
									
									
									
									
									
								
							| @ -20,37 +20,36 @@ repl env = runInputT defaultSettings (loop env) | ||||
|     loop :: Env -> InputT IO () | ||||
|     loop env = do | ||||
|       minput <- getInputLine "tricu < " | ||||
|       case minput of | ||||
|         Nothing -> outputStrLn "Exiting tricu" | ||||
|         Just s -> case strip s of  | ||||
|           "!exit" -> outputStrLn "Exiting tricu" | ||||
|           "!load" -> do | ||||
|             path <- getInputLine "File path to load < " | ||||
|             case path of | ||||
|               Nothing -> do | ||||
|                 outputStrLn "No input received; stopping import." | ||||
|                 loop env | ||||
|               Just path -> do | ||||
|                 loadedEnv <- liftIO $ evaluateFileWithContext env (strip path) | ||||
|                 loop $ Map.delete "__result" (Map.union loadedEnv env) | ||||
|           "" -> do | ||||
|             outputStrLn "" | ||||
|             loop env | ||||
|           input -> do | ||||
|             case (take 2 input) of  | ||||
|               "--" -> loop env | ||||
|               _ -> do | ||||
|                 newEnv <- liftIO $ (processInput env input `catch` errorHandler env) | ||||
|                 loop newEnv | ||||
|    | ||||
|       if | ||||
|         | Nothing <- minput                     -> outputStrLn "Exiting tricu" | ||||
|         | Just s  <- minput, strip s == "!exit" -> outputStrLn "Exiting tricu" | ||||
|         | Just s  <- minput, strip s == ""      -> do | ||||
|           outputStrLn "" | ||||
|           loop env | ||||
|         | Just s  <- minput, strip s == "!load" -> do | ||||
|           path <- getInputLine "File path to load < " | ||||
|           if | ||||
|             | Nothing <- path -> do | ||||
|               outputStrLn "No input received; stopping import." | ||||
|               loop env | ||||
|             | Just p  <- path -> do | ||||
|               loadedEnv <- liftIO $ evaluateFileWithContext env (strip p) `catch` \e -> errorHandler env e | ||||
|               loop $ Map.delete "__result" (Map.union loadedEnv env) | ||||
|         | Just s <- minput -> do | ||||
|           if | ||||
|             | take 2 s == "--" -> loop env | ||||
|             | otherwise -> do | ||||
|               newEnv <- liftIO $ processInput env s `catch` errorHandler env | ||||
|               loop newEnv | ||||
|  | ||||
|     processInput :: Env -> String -> IO Env | ||||
|     processInput env input = do | ||||
|       let asts = parseTricu input | ||||
|       let asts   = parseTricu input | ||||
|           newEnv = evalTricu env asts | ||||
|       case Map.lookup "__result" newEnv of | ||||
|         Just r -> do | ||||
|       if | ||||
|         | Just r <- Map.lookup "__result" newEnv -> do | ||||
|           putStrLn $ "tricu > " ++ decodeResult r | ||||
|         Nothing -> return () | ||||
|         | otherwise -> return () | ||||
|       return newEnv | ||||
|      | ||||
|     errorHandler :: Env -> SomeException -> IO (Env) | ||||
| @ -62,10 +61,8 @@ repl env = runInputT defaultSettings (loop env) | ||||
|     strip = dropWhileEnd isSpace . dropWhile isSpace | ||||
|  | ||||
| decodeResult :: T -> String | ||||
| decodeResult tc = case toNumber tc of | ||||
|   Right num -> show num | ||||
|   Left _ -> case toString tc of | ||||
|     Right str -> "\"" ++ str ++ "\"" | ||||
|     Left _ -> case toList tc of | ||||
|       Right list -> "[" ++ intercalate ", " (map decodeResult list) ++ "]" | ||||
|       Left _ -> formatResult TreeCalculus tc | ||||
| decodeResult tc | ||||
|   | Right num  <- toNumber tc = show num | ||||
|   | Right str  <- toString tc = "\"" ++ str ++ "\"" | ||||
|   | Right list <- toList tc   = "[" ++ intercalate ", " (map decodeResult list) ++ "]" | ||||
|   | otherwise                 = formatResult TreeCalculus tc | ||||
|  | ||||
| @ -28,7 +28,7 @@ data TricuAST | ||||
|   | SEmpty | ||||
|   deriving (Show, Eq, Ord) | ||||
|  | ||||
| -- Tokens from Lexer | ||||
| -- Lexer Tokens | ||||
| data LToken | ||||
|   = LKeywordT | ||||
|   | LIdentifier String | ||||
| @ -61,19 +61,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 | ||||
|  | ||||
| -- SKI Combinators | ||||
| _S :: T | ||||
| _S = Fork (Stem (Fork Leaf Leaf)) Leaf | ||||
|  | ||||
| _K :: T | ||||
| _K = Stem Leaf | ||||
|  | ||||
| -- Identity | ||||
| -- We use the "point-free" style which drops a redundant node | ||||
| -- Full I form (SKK): Fork (Stem (Stem Leaf)) (Stem Leaf) | ||||
| _I :: T | ||||
| _I = Fork (Stem (Stem Leaf)) Leaf  | ||||
|  | ||||
| -- Booleans | ||||
| _false :: T | ||||
| _false = Leaf | ||||
|  | ||||
							
								
								
									
										41
									
								
								test/Spec.hs
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								test/Spec.hs
									
									
									
									
									
								
							| @ -31,7 +31,6 @@ tests = testGroup "Tricu Tests" | ||||
|   , lambdaEvalTests | ||||
|   , libraryTests | ||||
|   , fileEvaluationTests | ||||
|   , propertyTests | ||||
|   ] | ||||
|  | ||||
| lexerTests :: TestTree | ||||
| @ -72,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)" | ||||
| @ -150,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 | ||||
| @ -313,7 +308,7 @@ libraryTests = testGroup "Library Tests" | ||||
|       result env @?= Fork (Stem (Stem Leaf)) (Stem Leaf) | ||||
|   , testCase "I combinator" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "i not" | ||||
|       let input = "i not?" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) (Fork Leaf (Fork Leaf Leaf)) | ||||
|   , testCase "Triage test Leaf" $ do | ||||
| @ -333,32 +328,32 @@ libraryTests = testGroup "Library Tests" | ||||
|       env @?= "\"Fork\"" | ||||
|   , testCase "Boolean NOT: true" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "not true" | ||||
|       let input = "not? true" | ||||
|           env = result $ evalTricu library (parseTricu input) | ||||
|       env @?= Leaf | ||||
|   , testCase "Boolean NOT: false" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "not false" | ||||
|       let input = "not? false" | ||||
|           env = result $ evalTricu library (parseTricu input) | ||||
|       env @?= Stem Leaf | ||||
|   , testCase "Boolean AND TF" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "and (t t) (t)" | ||||
|       let input = "and? (t t) (t)" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Leaf | ||||
|   , testCase "Boolean AND FT" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "and (t) (t t)" | ||||
|       let input = "and? (t) (t t)" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Leaf | ||||
|   , testCase "Boolean AND FF" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "and (t) (t)" | ||||
|       let input = "and? (t) (t)" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Leaf | ||||
|   , testCase "Boolean AND TT" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "and (t t) (t t)" | ||||
|       let input = "and? (t t) (t t)" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Stem Leaf | ||||
|   , testCase "List head" $ do | ||||
| @ -378,12 +373,12 @@ libraryTests = testGroup "Library Tests" | ||||
|       result env @?= Fork Leaf Leaf | ||||
|   , testCase "Empty list check" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "emptyList []" | ||||
|       let input = "emptyList? []" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Stem Leaf | ||||
|   , testCase "Non-empty list check" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "not (emptyList [(1) (2) (3)])" | ||||
|       let input = "not? (emptyList? [(1) (2) (3)])" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Stem Leaf | ||||
|   , testCase "Concatenate strings" $ do | ||||
| @ -393,7 +388,7 @@ libraryTests = testGroup "Library Tests" | ||||
|       env @?= "\"Hello, world!\"" | ||||
|   , testCase "Verifying Equality" $ do | ||||
|       library <- evaluateFile "./lib/base.tri" | ||||
|       let input = "equal (t t t) (t t t)" | ||||
|       let input = "equal? (t t t) (t t t)" | ||||
|           env = evalTricu library (parseTricu input) | ||||
|       result env @?= Stem Leaf | ||||
|   ] | ||||
| @ -414,13 +409,3 @@ fileEvaluationTests = testGroup "Evaluation tests" | ||||
|       res <- liftIO $ evaluateFileWithContext library "./test/string.tri" | ||||
|       decodeResult (result res) @?= "\"String test!\"" | ||||
|   ] | ||||
|  | ||||
| propertyTests :: TestTree | ||||
| propertyTests = testGroup "Property Tests" | ||||
|   [ testProperty "Lexing and parsing round-trip" $ \input -> | ||||
|       case runParser tricuLexer "" input of | ||||
|         Left _ -> property True | ||||
|         Right tokens -> case runParser parseExpression "" tokens of | ||||
|           Left _ -> property True | ||||
|           Right ast -> parseSingle input === ast | ||||
|   ] | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| cabal-version: 1.12 | ||||
|  | ||||
| name:           tricu | ||||
| version:        0.5.0 | ||||
| version:        0.7.0 | ||||
| description:    A micro-language for exploring Tree Calculus | ||||
| author:         James Eversole | ||||
| maintainer:     james@eversole.co | ||||
| @ -18,6 +18,8 @@ executable tricu | ||||
|       src | ||||
|   default-extensions: | ||||
|       DeriveDataTypeable | ||||
|       LambdaCase | ||||
|       MultiWayIf | ||||
|       OverloadedStrings | ||||
|   ghc-options: -threaded -rtsopts -with-rtsopts=-N -optl-pthread -fPIC | ||||
|   build-depends: | ||||
| @ -43,6 +45,8 @@ test-suite tricu-tests | ||||
|   hs-source-dirs:      test, src | ||||
|   default-extensions: | ||||
|       DeriveDataTypeable | ||||
|       LambdaCase | ||||
|       MultiWayIf | ||||
|       OverloadedStrings | ||||
|   build-depends:        | ||||
|     base | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	