56 Commits

Author SHA1 Message Date
87aed72ab2 # Modules
All checks were successful
Test, Build, and Release / test (push) Successful in 1m43s
Test, Build, and Release / build (push) Successful in 1m16s
Basic implementation of a module system including tests.
2025-01-27 16:04:04 -06:00
f71f88dce3 Small dependency ordering optimizations 2025-01-26 16:08:34 -06:00
918d929c09 # File eval mode now relies on main function
All checks were successful
Test, Build, and Release / test (push) Successful in 1m26s
Test, Build, and Release / build (push) Successful in 1m15s
To encourage organizing code in a way that helps in understanding, I
have implemented the common idiom of requiring a `main` function. In
tricu and other functional languages, it is usually placed near the top
of the module. The evaluator gracefully handles the situation of passing
multiple files where the intermediary "library" files do not have main functions.
2025-01-26 15:33:12 -06:00
a64b3f0829 Definition dependency analysis
All checks were successful
Test, Build, and Release / test (push) Successful in 1m34s
Test, Build, and Release / build (push) Successful in 1m21s
tricu now allows defining terms in any order and will resolve
dependencies to ensure that they're evaluated in the right order.
Undefined terms are detected and throw errors during dependency
ordering.
For now we can't define top-level mutually recursive terms.
2025-01-26 14:50:39 -06:00
e2621bc09d Allow lambda expressions without explicit paren
All checks were successful
Test, Build, and Release / test (push) Successful in 1m41s
Test, Build, and Release / build (push) Successful in 1m19s
2025-01-26 08:52:28 -06:00
ea128929da Add optimization cases for triage and composition 2025-01-25 15:12:28 -06:00
2bd388c871 Eval optimization! Tests for demos
All checks were successful
Test, Build, and Release / test (push) Successful in 1m30s
Test, Build, and Release / build (push) Successful in 1m26s
2025-01-25 09:18:13 -06:00
1f5a910fb2 Immutable definitions and documentation updates
All checks were successful
Test, Build, and Release / test (push) Successful in 1m22s
Test, Build, and Release / build (push) Successful in 1m23s
2025-01-24 16:14:33 -06:00
8b043911ca Add size demo 2025-01-23 18:57:59 -06:00
2e246eb1c8 Remove Nix caching that can't work due to /nix/store permissions
All checks were successful
Test, Build, and Release / test (push) Successful in 1m13s
Test, Build, and Release / build (push) Successful in 1m23s
2025-01-23 17:59:47 -06:00
ba340ae56f Update README to reflect demo
Some checks failed
Test, Build, and Release / build (push) Has been cancelled
Test, Build, and Release / test (push) Has been cancelled
2025-01-23 17:36:39 -06:00
739851c864 Minify and mark as pre-release
Some checks failed
Test, Build, and Release / test (push) Successful in 1m59s
Test, Build, and Release / build (push) Failing after 2m12s
2025-01-23 17:23:02 -06:00
8995efce15 Release 0.6.0
All checks were successful
Test, Build, and Release / test (push) Successful in 1m38s
Test, Build, and Release / build (push) Successful in 1m40s
2025-01-23 16:44:14 -06:00
03e2f6b93e Some special characters in ids; new demos
All checks were successful
Test and Build / test (push) Successful in 4m39s
Test and Build / build (push) Successful in 1m44s
Adds support for several special characters in identifiers. Adds a demo
for converting values to source code and another for checking equality.
Updates the existing demo and tests to reflect new names for functions
returning booleans.
2025-01-23 15:46:40 -06:00
419d66b4d1 All paths for caching cabal included :)
All checks were successful
Test and Build / test (push) Successful in 4m36s
Test and Build / build (push) Successful in 1m41s
2025-01-21 17:00:20 -06:00
4b98afd803 Use runner 0.1.0
All checks were successful
Test and Build / test (push) Successful in 2m52s
Test and Build / build (push) Successful in 1m42s
2025-01-21 16:49:15 -06:00
0768e11a02 Update Cabal caching path
Some checks failed
Test and Build / build (push) Has been cancelled
Test and Build / test (push) Has been cancelled
2025-01-21 16:48:29 -06:00
42fce0ae43 Drop unreachable cases of updateDepth
All checks were successful
Test and Build / test (push) Successful in 2m27s
Test and Build / build (push) Successful in 1m39s
2025-01-21 16:16:04 -06:00
51b1eb070f Add more explicit error handling for mismatched groupings 2025-01-21 16:06:10 -06:00
c2e5a8985a Inline pattern matching in Parser 2025-01-21 14:21:47 -06:00
9d7e4daa41 CI/CD for tests and builds (broken caching)
All checks were successful
Test and Build / test (push) Successful in 2m35s
Test and Build / build (push) Successful in 1m39s
2025-01-21 13:29:52 -06:00
edde0a80c9 Actually readable Level Order Traversal 2025-01-20 20:10:14 -06:00
35163a5d54 Allow multiline expressions 2025-01-20 19:20:29 -06:00
ca7f09e2ac Eliminate redundant eager calls of elimLambda 2025-01-20 16:05:06 -06:00
82e29440b0 Reduce duplication of elimLambda calls 2025-01-20 15:16:27 -06:00
ad02c8b86a General refactor for legibility
Priming to update all source to lhs and document extensively
2025-01-19 14:41:25 -06:00
a3282b794f 0.5.0 release commit 2025-01-06 09:14:04 -06:00
7b9a62462c Level Order Traversal demo 2025-01-03 12:00:06 -06:00
3eb28a2c62 Drop parseVarWithoutAssignment
Additionally sorts gitignore and adds attempted decoding of lists back
to the REPL
2025-01-03 10:31:35 -06:00
8c33e5ce66 Fix critical list evaluation bug and REPL updates 2025-01-02 19:08:14 -06:00
76487b15f9 Use better default output form in evaluator 2025-01-01 19:40:12 -06:00
18ff2d2e04 Clarify CLI options 2025-01-01 19:32:41 -06:00
fff29199d1 Support evaluation across multiple source files 2025-01-01 19:27:04 -06:00
a2c459b148 Provide "library" via tricu file directly
Allows easier loading of other files and drops the list of Haskell
strings containing the basic tools included
2025-01-01 18:53:56 -06:00
39be66a4d1 Fixes identifier lexing; support REPL file loading 2025-01-01 18:05:21 -06:00
bf58c9afbd Normalize CLI options and help display 2025-01-01 08:34:17 -06:00
7d38d99dcd Rename "compiler" functionality to Evaluator
Allows for stdin input for evaluation when no input file is provided.
2025-01-01 08:23:53 -06:00
458d3c3b10 Latest --help in README 2024-12-31 10:09:36 -06:00
0048fed6b4 Merge pull request 'Add "SimpleT" t output form' (#9) from feat/new-outputs into main
Reviewed-on: #9
2024-12-31 16:05:38 +00:00
476c3912a4 Merge branch 'main' into feat/new-outputs 2024-12-31 16:04:32 +00:00
493ef51a6a Add "SimpleT" t output form
This new output form allows easy piping to the decode function of the
tricu executable. Includes a new test for roundtrip evaluation of map,
compilation to tree calculus terms, and decoding back to a human
readable string.
2024-12-31 10:00:52 -06:00
e22ff06bfe Merge pull request 'Expands CLI support with output forms and decoding' (#7) from feat/ternary-representation into main
Reviewed-on: #7
2024-12-30 20:24:27 +00:00
5e2a4dff50 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.
2024-12-30 14:22:37 -06:00
8622af9ad2 Initial ternary representation options
Both parsing and conversion from T to ternary representation supported
2024-12-30 08:30:40 -06:00
fe70aa72ac Merge pull request 'Adds "compiler" and CLI argument handling' (#3) from feature/compiler-CLI into main
Reviewed-on: #3
2024-12-30 03:51:59 +00:00
2abeab9c04 Adds "compiler" and CLI argument handling 2024-12-29 21:49:57 -06:00
467e11edb3 Updates to tests
Uncomments a test for comment parsing behavior and removes a test for
incomplete function definitions.
2024-12-29 21:09:02 -06:00
38509724b1 Merge pull request 'Resolves issue with parsing comments' (#2) from fix/comments-0001 into main
Reviewed-on: #2
2024-12-30 03:03:39 +00:00
a8f72290a2 Resolves issue with parsing comments 2024-12-29 21:02:38 -06:00
b86ff6e9b8 Additional tests 2024-12-29 12:22:24 -06:00
a7674d4635 README updates for run/build 2024-12-29 10:41:04 -06:00
14fdb741dc README clarifications 2024-12-29 10:37:37 -06:00
60a9e3c1ee Expansion of testing suite to cover incl. library
Expands the testing suite to verify behavior of provided library
functions. Updates the README further for clarification on important
concepts.
2024-12-29 10:28:32 -06:00
c30f17367f Rename from sapling to tricu 2024-12-29 08:29:25 -06:00
064bed26c5 Further README clarification 2024-12-28 22:20:43 -06:00
ff2952010f README updates 2024-12-28 21:58:52 -06:00
42 changed files with 1746 additions and 718 deletions

View File

@ -0,0 +1,65 @@
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: 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: Build and shrink binary
run: |
nix build
cp -L ./result/bin/tricu ./tricu
chmod 755 ./tricu
nix develop --command upx ./tricu
- name: Release binary
uses: akkuman/gitea-release-action@v1
with:
files: |-
./tricu
token: '${{ secrets.RELEASE_TOKEN }}'
body: '${{ gitea.event.head_commit.message }}'
prerelease: true

17
.gitignore vendored
View File

@ -1,14 +1,11 @@
bin/
data/Purr.sqlite
data/encryptionKey
/result
/config.dhall
/Dockerfile
/docker-stack.yml
.stack-work/
*.swp
dist*
*.txt
*~
.env
.stack-work/
/Dockerfile
/config.dhall
/result
WD
*.hs.txt
bin/
dist*

105
README.md
View File

@ -1,54 +1,89 @@
# sapling
# tricu
## Introduction
sapling is a "micro-language" that I'm working on to investigate [Tree Calculus](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. 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.
It offers a minimal amount of syntax sugar:
tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)`.
- `t` operator behaving by the rules of Tree Calculus
- Function ("variable") definitions
- Lambda abstractions
- List, Number, and String literals (WIP)
## Features
This is an active experimentation project by [someone who has no idea what they're doing](https://eversole.co).
- Tree Calculus operator: `t`
- Assignments: `x = t t`
- Immutabile definitions
- Lambda abstraction syntax: `id = (\a : a)`
- List, Number, and String literals: `[(2) ("Hello")]`
- Function application: `not (not false)`
- Higher order/first-class functions: `map (\a : lconcat a "!") [("Hello")]`
- Intensionality blurs the distinction between functions and data (see REPL examples)
- Simple module system for code organization
## What does it look like?
## REPL examples
```
false = t
_ = t
true = t t
id = (\a : a)
triage = (\a b c : t (t a b) c)
match_bool = (\ot of : triage of (\_ : ot) t)
and = match_bool id (\_ : false)
if = (\cond then else : t (t else (t t then)) t cond)
triage = (\a b c : t (t a b) c)
test = triage "leaf" (\_ : "stem") (\_ _ : "fork")
tricu < -- Anything after `--` on a single line is a comment
tricu < id = (\a : a) -- Lambda abstraction is eliminated to tree calculus terms
tricu < head (map (\i : lconcat i " world!") [("Hello, ")])
tricu > "Hello, world!"
tricu < id (head (map (\i : lconcat i " world!") [("Hello, ")]))
tricu > "Hello, world!"
-- The REPL outputs the tree form results by default; they are elided here.
sapling < test t
DECODE -: "leaf"
sapling < test (t t)
DECODE -: "stem"
sapling < test (t t t)
DECODE -: "fork"
sapling < map (\i : listConcat i " is super cool!") [("He") ("She") ("Everybody")]
DECODE -: ["He is super cool!", "She is super cool!", "Everybody is super cool!"]
tricu < -- Intensionality! We can inspect the structure of a function or data.
tricu < triage = (\a b c : t (t a b) c)
tricu < test = triage "Leaf" (\z : "Stem") (\a b : "Fork")
tricu < test (t t)
tricu > "Stem"
tricu < -- We can even convert a term back to source code (/demos/toSource.tri)
tricu < toSource not?
tricu > "(t (t (t t) (t t t)) (t t (t t t)))"
tricu < -- or calculate its size (/demos/size.tri)
tricu < size not?
tricu > 12
```
## How to use
## Installation and Use
For now, you can easily build and run this project using Nix:
[Releases are available for Linux.](https://git.eversole.co/James/tricu/releases)
1. Clone the repository:
a. `git clone ssh://git.eversole.co/sapling.git`
b. `git clone https://git.eversole/sapling.git`
1. Run the REPL: `nix run`
Or you can easily build and/or run this project using [Nix](https://nixos.org/download/).
- Quick Start (REPL):
- `nix run git+https://git.eversole.co/James/tricu`
- Build executable in `./result/bin`:
- `nix build git+https://git.eversole.co/James/tricu`
`./result/bin/tricu --help`
```
tricu Evaluator and REPL
tricu [COMMAND] ... [OPTIONS]
tricu: Exploring Tree Calculus
Common flags:
-? --help Display help message
-V --version Print version information
tricu [repl] [OPTIONS]
Start interactive REPL
tricu eval [OPTIONS]
Evaluate tricu and return the result of the final expression.
-f --file=FILE Input file path(s) for evaluation.
Defaults to stdin.
-t --form=FORM Optional output form: (tree|fsl|ast|ternary|ascii|decode).
Defaults to tricu-compatible `t` tree form.
tricu decode [OPTIONS]
Decode a Tree Calculus value into a string representation.
-f --file=FILE Optional input file path to attempt decoding.
Defaults to stdin.
```
## Acknowledgements
Tree Calculus was discovered by [Barry Jay](https://github.com/barry-jay-personal/blog).
[treecalcul.us](https://treecalcul.us) is an excellent website with an intuitive playground created by [Johannes Bader](https://johannes-bader.com/) that introduced me to Tree Calculus. If sapling sounds interesting but compiling this repo sounds like a hassle, you should check out his site.
[treecalcul.us](https://treecalcul.us) is an excellent website with an intuitive Tree Calculus code playground created by [Johannes Bader](https://johannes-bader.com/) that introduced me to Tree Calculus.

41
demos/equality.tri Normal file
View File

@ -0,0 +1,41 @@
!module Equality
!import "lib/base.tri" Lib
main = lambdaEqualsTC
-- We represent `false` with a Leaf and `true` with a Stem Leaf
demo_false = t
demo_true = t t
-- Tree Calculus representation of the Boolean `not` function
not_TC? = t (t (t t) (t t t)) (t t (t t t))
-- /demos/toSource.tri contains an explanation of `triage`
demo_triage = \a b c : t (t a b) c
demo_matchBool = (\ot of : demo_triage
of
(\_ : ot)
(\_ _ : ot)
)
-- Lambda representation of the Boolean `not` function
not_Lambda? = demo_matchBool demo_false demo_true
-- Since tricu eliminates Lambda terms to SKI combinators, the tree form of many
-- functions defined via Lambda terms are larger than the most efficient TC
-- representation. Between different languages that evaluate to tree calculus
-- terms, the exact implementation of Lambda elimination may differ and lead
-- to different tree representations even if they share extensional behavior.
-- Let's see if these are the same:
lambdaEqualsTC = Lib.equal? not_TC? not_Lambda?
-- Here are some checks to verify their extensional behavior is the same:
true_TC? = not_TC? demo_false
false_TC? = not_TC? demo_true
true_Lambda? = not_Lambda? demo_false
false_Lambda? = not_Lambda? demo_true
bothTrueEqual? = Lib.equal? true_TC? true_Lambda?
bothFalseEqual? = Lib.equal? false_TC? false_Lambda?

View File

@ -0,0 +1,65 @@
!module LOT
!import "lib/base.tri" Lib
main = exampleTwo
-- Level Order Traversal of a labelled binary tree
-- Objective: Print each "level" of the tree on a separate line
--
-- We model labelled binary trees as nested lists where values act as labels. We
-- require explicit notation of empty nodes. Empty nodes can be represented
-- with an empty list, `[]`, which evaluates 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 : Lib.head node
left = (\node : Lib.if (Lib.emptyList? node)
[]
(Lib.if (Lib.emptyList? (Lib.tail node))
[]
(Lib.head (Lib.tail node))))
right = (\node : Lib.if (Lib.emptyList? node)
[]
(Lib.if (Lib.emptyList? (Lib.tail node))
[]
(Lib.if (Lib.emptyList? (Lib.tail (Lib.tail node)))
[]
(Lib.head (Lib.tail (Lib.tail node))))))
processLevel = Lib.y (\self queue : Lib.if (Lib.emptyList? queue)
[]
(Lib.pair (Lib.map label queue) (self (Lib.filter
(\node : Lib.not? (Lib.emptyList? node))
(Lib.lconcat (Lib.map left queue) (Lib.map right queue))))))
levelOrderTraversal_ = \a : processLevel (t a t)
toLineString = Lib.y (\self levels : Lib.if (Lib.emptyList? levels)
""
(Lib.lconcat
(Lib.lconcat (Lib.map (\x : Lib.lconcat x " ") (Lib.head levels)) "")
(Lib.if (Lib.emptyList? (Lib.tail levels)) "" (Lib.lconcat (t (t 10 t) t) (self (Lib.tail levels))))))
levelOrderToString = \s : toLineString (levelOrderTraversal_ s)
flatten = Lib.foldl (\acc x : Lib.lconcat acc x) ""
levelOrderTraversal = \s : Lib.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]]]

25
demos/size.tri Normal file
View File

@ -0,0 +1,25 @@
!module Size
!import "lib/base.tri" Lib
main = size size
compose = \f g x : f (g x)
succ = Lib.y (\self :
Lib.triage
1
t
(Lib.triage
(t (t t))
(\_ Lib.tail : t t (self Lib.tail))
t))
size = (\x :
(Lib.y (\self x :
compose succ
(Lib.triage
(\x : x)
self
(\x y : compose (self x) (self y))
x)) x 0))

51
demos/toSource.tri Normal file
View File

@ -0,0 +1,51 @@
!module ToSource
!import "lib/base.tri" Lib
main = toSource Lib.not?
-- 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 = (\leaf stem fork : t (t leaf stem) fork)
-- Base case of a single Leaf
sourceLeaf = t (Lib.head "t")
-- Stem case
sourceStem = (\convert : (\a rest :
t (Lib.head "(") -- Start with a left parenthesis "(".
(t (Lib.head "t") -- Add a "t"
(t (Lib.head " ") -- Add a space.
(convert a -- Recursively convert the argument.
(t (Lib.head ")") rest)))))) -- Close with ")" and append the rest.
-- Fork case
sourceFork = (\convert : (\a b rest :
t (Lib.head "(") -- Start with a left parenthesis "(".
(t (Lib.head "t") -- Add a "t"
(t (Lib.head " ") -- Add a space.
(convert a -- Recursively convert the first arg.
(t (Lib.head " ") -- Add another space.
(convert b -- Recursively convert the second arg.
(t (Lib.head ")") rest)))))))) -- Close with ")" and append the rest.
-- Wrapper around triage
toSource_ = Lib.y (\self arg :
Lib.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 Lib.true -- OUT: "(t t)"
exampleTwo = toSource Lib.not? -- OUT: "(t (t (t t) (t t t)) (t t (t t t)))"

View File

@ -1,5 +1,5 @@
{
description = "sapling";
description = "tricu";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
@ -10,7 +10,7 @@
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
packageName = "sapling";
packageName = "tricu";
containerPackageName = "${packageName}-container";
customGHC = pkgs.haskellPackages.ghcWithPackages (hpkgs: with hpkgs; [
@ -22,7 +22,7 @@
enableSharedExecutables = false;
enableSharedLibraries = false;
sapling = pkgs.haskell.lib.justStaticExecutables self.packages.${system}.default;
tricu = pkgs.haskell.lib.justStaticExecutables self.packages.${system}.default;
in {
packages.${packageName} =
@ -32,10 +32,11 @@
defaultPackage = self.packages.${system}.default;
devShells.default = pkgs.mkShell {
buildInputs = with pkgs.haskellPackages; [
cabal-install
ghcid
buildInputs = with pkgs; [
haskellPackages.cabal-install
haskellPackages.ghcid
customGHC
upx
];
inputsFrom = builtins.attrValues self.packages.${system};
};

83
lib/base.tri Normal file
View File

@ -0,0 +1,83 @@
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)
id = \a : a
pair = t
if = \cond then else : t (t else (t t then)) t cond
y = ((\mut wait fun : wait mut (\x : fun (wait mut x)))
(\x : x x)
(\a0 a1 a2 : t (t a0) (t t a2) a1))
triage = \leaf stem fork : t (t leaf stem) fork
test = triage "Leaf" (\_ : "Stem") (\_ _ : "Fork")
matchBool = (\ot of : triage
of
(\_ : ot)
(\_ _ : ot)
)
matchList = \a b : triage a _ b
matchPair = \a : triage _ _ a
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

View File

@ -1,8 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
let x = pkgs.haskellPackages.ghcWithPackages (hpkgs: with hpkgs; [
megaparsec
]);
in
pkgs.mkShell {
buildInputs = [ x ];
}

View File

@ -3,118 +3,189 @@ module Eval where
import Parser
import Research
import Data.List (partition, (\\))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.List (foldl')
import qualified Data.Set as Set
evalSingle :: Map String T -> SaplingAST -> Map String T
evalSingle env term = case term of
SFunc name [] body ->
let lineNoLambda = eliminateLambda body
result = evalAST env lineNoLambda
in 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 ->
evalSingle :: Env -> TricuAST -> Env
evalSingle env term
| SDef name [] body <- term =
if
| Map.member name env ->
errorWithoutStackTrace $
"Error: Identifier '" ++ name ++ "' is already defined."
| otherwise ->
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 value -> Map.insert "__result" value env
Nothing -> error $ "Variable " ++ name ++ " not defined"
_ ->
let result = evalAST env term
in Map.insert "__result" result env
Just v ->
Map.insert "!result" v env
Nothing ->
errorWithoutStackTrace $ "Variable `" ++ name ++ "` not defined\n\
\This error should never occur here. Please report this as an issue."
| otherwise =
Map.insert "!result" (evalAST env term) env
evalSapling :: Map String T -> [SaplingAST] -> Map String T
evalSapling env [] = env
evalSapling env [lastLine] =
let lastLineNoLambda = eliminateLambda lastLine
updatedEnv = evalSingle env lastLineNoLambda
in Map.insert "__result" (result updatedEnv) updatedEnv
evalSapling env (line:rest) =
let lineNoLambda = eliminateLambda line
updatedEnv = evalSingle env lineNoLambda
in evalSapling updatedEnv rest
evalTricu :: Env -> [TricuAST] -> Env
evalTricu env x = go env (reorderDefs env x)
where
go env [] = env
go env [x] =
let updatedEnv = evalSingle env x
in Map.insert "!result" (result updatedEnv) updatedEnv
go env (x:xs) =
evalTricu (evalSingle env x) xs
evalAST :: Map String T -> SaplingAST -> 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)
SFunc name args body ->
error $ "Unexpected function definition " ++ name
++ " in evalAST; define via evalSingle."
SLambda {} -> error "Internal error: SLambda found in evalAST after elimination."
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
eliminateLambda :: SaplingAST -> SaplingAST
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
elimLambda :: TricuAST -> TricuAST
elimLambda = go
where
-- η-reduction
go (SLambda [v] (SApp f (SVar x)))
| v == x && not (isFree v f) = elimLambda f
-- Triage optimization
go (SLambda [a] (SLambda [b] (SLambda [c] body)))
| body == triageBody = _TRIAGE
where
triageBody =
(SApp (SApp TLeaf (SApp (SApp TLeaf (SVar a)) (SVar b))) (SVar c))
-- Composition optimization
go (SLambda [f] (SLambda [g] (SLambda [x] body)))
| body == composeBody = _COMPOSE
where
composeBody = SApp (SVar f) (SApp (SVar g) (SVar x))
-- General elimination
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
-- This is my attempt to implement the lambda calculus elimination rules defined
-- in "Typed Program Analysis without Encodings" by Barry Jay.
-- https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf
lambdaToT :: String -> SaplingAST -> SaplingAST
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
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 = errorWithoutStackTrace "Unhandled toSKI conversion"
freeVars :: SaplingAST -> Set.Set String
_S = parseSingle "t (t (t t t)) t"
_K = parseSingle "t t"
_I = parseSingle "t (t (t t)) t"
_TRIAGE = parseSingle "t (t (t t (t (t (t t t))))) t"
_COMPOSE = parseSingle "t (t (t t (t (t (t t t)) t))) (t t)"
isFree :: String -> TricuAST -> Bool
isFree x = Set.member x . freeVars
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 (SList s ) = foldMap freeVars s
freeVars (SApp f a ) = freeVars f <> freeVars a
freeVars (TLeaf ) = Set.empty
freeVars (SDef _ _ 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
freeVars (SLambda v b ) = foldr Set.delete (freeVars b) v
freeVars _ = Set.empty
isFree :: String -> SaplingAST -> Bool
isFree x = Set.member x . freeVars
reorderDefs :: Env -> [TricuAST] -> [TricuAST]
reorderDefs env defs
| not (null missingDeps) =
errorWithoutStackTrace $
"Missing dependencies detected: " ++ show missingDeps
| otherwise = orderedDefs ++ others
where
(defsOnly, others) = partition isDef defs
defNames = [ name | SDef name _ _ <- defsOnly ]
toAST :: T -> SaplingAST
toAST Leaf = TLeaf
toAST (Stem a) = TStem (toAST a)
toAST (Fork a b) = TFork (toAST a) (toAST b)
defsWithFreeVars = [(def, freeVars body) | def@(SDef _ _ body) <- defsOnly]
-- We need the SKI operators in an unevaluated SaplingAST tree form so that we
-- can keep the evaluation functions straightforward
tI :: SaplingAST
tI = SApp (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf))) TLeaf
graph = buildDepGraph defsOnly
sortedDefs = sortDeps graph
defMap = Map.fromList [(name, def) | def@(SDef name _ _) <- defsOnly]
orderedDefs = map (\name -> defMap Map.! name) sortedDefs
tK :: SaplingAST
tK = SApp TLeaf TLeaf
freeVarsDefs = foldMap snd defsWithFreeVars
freeVarsOthers = foldMap freeVars others
allFreeVars = freeVarsDefs <> freeVarsOthers
validNames = Set.fromList defNames `Set.union` Set.fromList (Map.keys env)
missingDeps = Set.toList (allFreeVars `Set.difference` validNames)
tS :: SaplingAST
tS = SApp (SApp TLeaf (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf))) TLeaf
isDef (SDef _ _ _) = True
isDef _ = False
result :: Map String T -> T
result r = case Map.lookup "__result" r of
buildDepGraph :: [TricuAST] -> Map.Map String (Set.Set String)
buildDepGraph topDefs
| not (null duplicateNames) =
errorWithoutStackTrace $
"Duplicate definitions detected: " ++ show duplicateNames
| otherwise =
Map.fromList
[ (name, depends topDefs (SDef name [] body))
| SDef name _ body <- topDefs]
where
names = [name | SDef name _ _ <- topDefs]
duplicateNames =
[ name | (name, count) <- Map.toList (countOccurrences names) , count > 1]
countOccurrences = foldr (\x -> Map.insertWith (+) x 1) Map.empty
sortDeps :: Map.Map String (Set.Set String) -> [String]
sortDeps graph = go [] Set.empty (Map.keys graph)
where
go sorted sortedSet [] = sorted
go sorted sortedSet remaining =
let ready = [ name | name <- remaining
, let deps = Map.findWithDefault Set.empty name graph
, Set.isSubsetOf deps sortedSet ]
notReady = remaining \\ ready
in if null ready
then errorWithoutStackTrace
"ERROR: Cyclic dependency detected and prohibited.\n\
\RESOLVE: Use nested lambdas."
else go (sorted ++ ready)
(Set.union sortedSet (Set.fromList ready))
notReady
depends :: [TricuAST] -> TricuAST -> Set.Set String
depends topDefs (SDef _ _ body) =
Set.intersection
(Set.fromList [n | SDef n _ _ <- topDefs])
(freeVars body)
depends _ _ = Set.empty
result :: Env -> T
result r = case Map.lookup "!result" r of
Just a -> a
Nothing -> error "No __result field found in provided environment"
Nothing -> errorWithoutStackTrace "No !result field found in provided env"
mainResult :: Env -> T
mainResult r = case Map.lookup "main" r of
Just a -> a
Nothing -> errorWithoutStackTrace "No valid definition for `main` found."

150
src/FileEval.hs Normal file
View File

@ -0,0 +1,150 @@
module FileEval where
import Eval
import Lexer
import Parser
import Research
import Data.List (partition)
import Control.Monad (foldM)
import System.IO
import qualified Data.Map as Map
import qualified Data.Set as Set
evaluateFileResult :: FilePath -> IO T
evaluateFileResult filePath = do
contents <- readFile filePath
let tokens = lexTricu contents
let moduleName = case parseProgram tokens of
Right ((SModule name) : _) -> name
_ -> ""
case parseProgram tokens of
Left err -> errorWithoutStackTrace (handleParseError err)
Right _ -> do
ast <- preprocessFile filePath
let finalEnv = mainAlias moduleName $ evalTricu Map.empty ast
case Map.lookup "main" finalEnv of
Just finalResult -> return finalResult
Nothing -> errorWithoutStackTrace "No `main` function detected"
evaluateFile :: FilePath -> IO Env
evaluateFile filePath = do
contents <- readFile filePath
let tokens = lexTricu contents
let moduleName = case parseProgram tokens of
Right ((SModule name) : _) -> name
_ -> ""
case parseProgram tokens of
Left err -> errorWithoutStackTrace (handleParseError err)
Right _ -> do
ast <- preprocessFile filePath
pure $ mainAlias moduleName $ evalTricu Map.empty ast
evaluateFileWithContext :: Env -> FilePath -> IO Env
evaluateFileWithContext env filePath = do
contents <- readFile filePath
let tokens = lexTricu contents
let moduleName = case parseProgram tokens of
Right ((SModule name) : _) -> name
_ -> ""
case parseProgram tokens of
Left err -> errorWithoutStackTrace (handleParseError err)
Right _ -> do
ast <- preprocessFile filePath
pure $ mainAlias moduleName $ evalTricu env ast
mainAlias :: String -> Env -> Env
mainAlias "" env = env
mainAlias moduleName env =
case Map.lookup (moduleName ++ ".main") env of
Just value -> Map.insert "main" value env
Nothing -> env
preprocessFile :: FilePath -> IO [TricuAST]
preprocessFile filePath = preprocessFile' Set.empty filePath
preprocessFile' :: Set.Set FilePath -> FilePath -> IO [TricuAST]
preprocessFile' inProgress filePath
| filePath `Set.member` inProgress =
errorWithoutStackTrace $ "Encountered cyclic import: " ++ filePath
| otherwise = do
contents <- readFile filePath
let tokens = lexTricu contents
case parseProgram tokens of
Left err -> errorWithoutStackTrace (handleParseError err)
Right asts -> do
let (moduleName, restAST) = extractModule asts
let (imports, nonImports) = partition isImport restAST
let newInProgress = Set.insert filePath inProgress
importedASTs <- concat <$> mapM (processImport newInProgress) imports
let namespacedAST = namespaceDefinitions moduleName nonImports
pure $ importedASTs ++ namespacedAST
where
extractModule :: [TricuAST] -> (String, [TricuAST])
extractModule ((SModule name) : xs) = (name, xs)
extractModule xs = ("", xs)
isImport :: TricuAST -> Bool
isImport (SImport _ _) = True
isImport _ = False
processImport :: Set.Set FilePath -> TricuAST -> IO [TricuAST]
processImport inProgress (SImport filePath moduleName) = do
importedAST <- preprocessFile' inProgress filePath
pure $ namespaceDefinitions moduleName importedAST
processImport _ _ = error "Unexpected non-import in processImport"
namespaceDefinitions :: String -> [TricuAST] -> [TricuAST]
namespaceDefinitions moduleName = map (namespaceDefinition moduleName)
namespaceDefinition :: String -> TricuAST -> TricuAST
namespaceDefinition "" def = def
namespaceDefinition moduleName (SDef name args body)
| isPrefixed name = SDef name args (namespaceBody moduleName body)
| otherwise = SDef (namespaceVariable moduleName name)
args (namespaceBody moduleName body)
namespaceDefinition moduleName other =
namespaceBody moduleName other
namespaceBody :: String -> TricuAST -> TricuAST
namespaceBody moduleName (SVar name)
| isPrefixed name = SVar name
| otherwise = SVar (namespaceVariable moduleName name)
namespaceBody moduleName (SApp func arg) =
SApp (namespaceBody moduleName func) (namespaceBody moduleName arg)
namespaceBody moduleName (SLambda args body) =
SLambda args (namespaceBodyScoped moduleName args body)
namespaceBody moduleName (SList items) =
SList (map (namespaceBody moduleName) items)
namespaceBody moduleName (TFork left right) =
TFork (namespaceBody moduleName left) (namespaceBody moduleName right)
namespaceBody moduleName (TStem subtree) =
TStem (namespaceBody moduleName subtree)
namespaceBody moduleName (SDef name args body)
| isPrefixed name = SDef name args (namespaceBody moduleName body)
| otherwise = SDef (namespaceVariable moduleName name)
args (namespaceBody moduleName body)
namespaceBody _ other = other
namespaceBodyScoped :: String -> [String] -> TricuAST -> TricuAST
namespaceBodyScoped moduleName args body = case body of
SVar name ->
if name `elem` args
then SVar name
else namespaceBody moduleName (SVar name)
SApp func arg -> SApp (namespaceBodyScoped moduleName args func) (namespaceBodyScoped moduleName args arg)
SLambda innerArgs innerBody -> SLambda innerArgs (namespaceBodyScoped moduleName (args ++ innerArgs) innerBody)
SList items -> SList (map (namespaceBodyScoped moduleName args) items)
TFork left right -> TFork (namespaceBodyScoped moduleName args left) (namespaceBodyScoped moduleName args right)
TStem subtree -> TStem (namespaceBodyScoped moduleName args subtree)
SDef name innerArgs innerBody ->
SDef (namespaceVariable moduleName name) innerArgs (namespaceBodyScoped moduleName (args ++ innerArgs) innerBody)
other -> other
isPrefixed :: String -> Bool
isPrefixed name = '.' `elem` name
namespaceVariable :: String -> String -> String
namespaceVariable "" name = name
namespaceVariable moduleName name = moduleName ++ "." ++ name

View File

@ -1,39 +1,30 @@
module Lexer where
import Research
import Text.Megaparsec
import Text.Megaparsec.Char
import Control.Monad (void)
import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char hiding (space)
import Text.Megaparsec.Char.Lexer
import qualified Data.Set as Set
type Lexer = Parsec Void String
data LToken
= LKeywordT
| LIdentifier String
| LIntegerLiteral Int
| LStringLiteral String
| LAssign
| LColon
| LBackslash
| LOpenParen
| LCloseParen
| LOpenBracket
| LCloseBracket
| LNewline
| LComment String
deriving (Show, Eq, Ord)
keywordT :: Lexer LToken
keywordT = string "t" *> notFollowedBy alphaNumChar *> pure LKeywordT
identifier :: Lexer LToken
identifier = do
name <- some (letterChar <|> char '_' <|> char '-')
if (name == "t" || name == "__result")
then fail "Keywords (`t`, `__result`) cannot be used as an identifier"
first <- letterChar <|> char '_'
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"
else return (LIdentifier name)
integerLiteral :: Lexer LToken
@ -45,12 +36,25 @@ stringLiteral :: Lexer LToken
stringLiteral = do
char '"'
content <- many (noneOf ['"'])
if null content
then fail "Empty string literals are not allowed"
else do
char '"' --"
return (LStringLiteral content)
lModule :: Lexer LToken
lModule = do
_ <- string "!module"
space1
LIdentifier moduleName <- identifier
return (LModule moduleName)
lImport :: Lexer LToken
lImport = do
_ <- string "!import"
space1
LStringLiteral path <- stringLiteral
space1
LIdentifier name <- identifier
return (LImport path name)
assign :: Lexer LToken
assign = char '=' *> pure LAssign
@ -75,20 +79,34 @@ closeBracket = char ']' *> pure LCloseBracket
lnewline :: Lexer LToken
lnewline = char '\n' *> pure LNewline
comment :: Lexer LToken
comment = do
string "--"
content <- many (satisfy (/= '\n'))
optional (char '\n')
pure (LComment content)
sc :: Lexer ()
sc = skipMany (void (char ' ') <|> void (char '\t') <|> void comment)
sc = space
(void $ takeWhile1P (Just "space") (\c -> c == ' ' || c == '\t'))
(skipLineComment "--")
(skipBlockComment "|-" "-|")
saplingLexer :: Lexer [LToken]
saplingLexer = many (sc *> choice
[ try identifier
tricuLexer :: Lexer [LToken]
tricuLexer = do
sc
header <- many $ do
tok <- choice
[ try lModule
, try lImport
, lnewline
]
sc
pure tok
tokens <- many $ do
tok <- choice tricuLexer'
sc
pure tok
sc
eof
pure (header ++ tokens)
where
tricuLexer' =
[ try lnewline
, try identifier
, try keywordT
, try integerLiteral
, try stringLiteral
@ -99,10 +117,9 @@ saplingLexer = many (sc *> choice
, closeParen
, openBracket
, closeBracket
, lnewline
] <* sc) <* eof
]
lexSapling :: String -> [LToken]
lexSapling input = case runParser saplingLexer "" input of
Left err -> error $ "Lexical error:\n" ++ errorBundlePretty err
lexTricu :: String -> [LToken]
lexTricu input = case runParser tricuLexer "" input of
Left err -> errorWithoutStackTrace $ "Lexical error:\n" ++ errorBundlePretty err
Right tokens -> tokens

View File

@ -1,47 +0,0 @@
module Library where
import Eval
import Parser
import Research
import qualified Data.Map as Map
library :: Map.Map String T
library = evalSapling Map.empty $ parseSapling $ unlines
[ "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)"
, "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)"
, "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)"
, "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)"
, "nonEmptyList = matchList false (\\y z : true)"
, "head = matchList t (\\hd tl : hd)"
, "tail = matchList t (\\hd tl : tl)"
, "isLeaf = (\\_ : triage true false false)"
, "listConcat = 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))))"
]

View File

@ -1,22 +1,87 @@
module Main where
import Eval
import Lexer
import Library
import Parser
import REPL (repl)
import Eval (evalTricu, mainResult, result)
import FileEval
import Parser (parseTricu)
import REPL
import Research
import qualified Data.Map as Map
import Control.Monad (foldM)
import Control.Monad.IO.Class (liftIO)
import Text.Megaparsec (runParser)
import System.Console.CmdArgs
import qualified Data.Map as Map
data TricuArgs
= Repl
| Evaluate { file :: [FilePath], form :: EvaluatedForm }
| TDecode { file :: [FilePath] }
deriving (Show, Data, Typeable)
replMode :: TricuArgs
replMode = Repl
&= help "Start interactive REPL"
&= auto
&= name "repl"
evaluateMode :: TricuArgs
evaluateMode = Evaluate
{ file = def &= help "Input file path(s) for evaluation.\n \
\ Defaults to stdin."
&= name "f" &= typ "FILE"
, form = TreeCalculus &= typ "FORM"
&= help "Optional output form: (tree|fsl|ast|ternary|ascii|decode).\n \
\ Defaults to tricu-compatible `t` tree form."
&= name "t"
}
&= help "Evaluate tricu and return the result of the final expression."
&= explicit
&= name "eval"
decodeMode :: TricuArgs
decodeMode = TDecode
{ file = def
&= help "Optional input file path to attempt decoding.\n \
\ Defaults to stdin."
&= name "f" &= typ "FILE"
}
&= help "Decode a Tree Calculus value into a string representation."
&= explicit
&= name "decode"
main :: IO ()
main = do
putStrLn "Welcome to the Sapling Interpreter"
putStrLn "You can exit at any time by typing and entering: "
putStrLn ":_exit"
repl library
args <- cmdArgs $ modes [replMode, evaluateMode, decodeMode]
&= help "tricu: Exploring Tree Calculus"
&= program "tricu"
&= summary "tricu Evaluator and REPL"
case args of
Repl -> do
putStrLn "Welcome to the tricu REPL"
putStrLn "You can exit with `CTRL+D` or the `:_exit` command.`"
library <- liftIO $ evaluateFile "./lib/base.tri"
repl $ Map.delete "!result" library
Evaluate { file = filePaths, form = form } -> do
result <- case filePaths of
[] -> do
t <- getContents
pure $ runTricu t
(filePath:restFilePaths) -> do
initialEnv <- evaluateFile filePath
finalEnv <- foldM evaluateFileWithContext initialEnv restFilePaths
pure $ mainResult finalEnv
let fRes = formatResult form result
putStr fRes
TDecode { file = filePaths } -> do
value <- case filePaths of
[] -> getContents
(filePath:_) -> readFile filePath
library <- liftIO $ evaluateFile "./lib/base.tri"
putStrLn $ decodeResult $ result $ evalTricu library $ parseTricu value
runSapling :: String -> T
runSapling s = result (evalSapling Map.empty $ parseSapling s)
runSaplingEnv env s = result (evalSapling env $ parseSapling s)
runTricu :: String -> T
runTricu input =
let asts = parseTricu input
finalEnv = evalTricu Map.empty asts
in result finalEnv

View File

@ -1,255 +1,316 @@
module Parser where
import Debug.Trace
import Lexer
import Research hiding (toList)
import Control.Exception (throw)
import Research
import Control.Monad (void)
import Control.Monad.State
import Data.List.NonEmpty (toList)
import qualified Data.Set as Set
import Data.Void
import Data.Void (Void)
import Text.Megaparsec
import Text.Megaparsec.Char
import Text.Megaparsec.Error (errorBundlePretty, ParseErrorBundle)
import Text.Megaparsec.Error (ParseErrorBundle, errorBundlePretty)
import qualified Data.Set as Set
type Parser = Parsec Void [LToken]
data PState = PState
{ parenDepth :: Int
, bracketDepth :: Int
} deriving (Show)
data SaplingAST
= SVar String
| SInt Int
| SStr String
| SList [SaplingAST]
| SFunc String [String] SaplingAST
| SApp SaplingAST SaplingAST
| TLeaf
| TStem SaplingAST
| TFork SaplingAST SaplingAST
| SLambda [String] SaplingAST
deriving (Show, Eq, Ord)
type ParserM = StateT PState (Parsec Void [LToken])
parseSapling :: String -> [SaplingAST]
parseSapling input =
let nonEmptyLines = filter (not . null) (lines input)
in map parseSingle nonEmptyLines
satisfyM :: (LToken -> Bool) -> ParserM LToken
satisfyM f = do
token <- lift (satisfy f)
modify' (updateDepth token)
return token
parseSingle :: String -> SaplingAST
parseSingle input = case runParser parseExpression "" (lexSapling input) of
Left err -> error $ handleParseError err
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 =
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
toks ->
case parseSingleExpr toks of
Left err -> errorWithoutStackTrace (handleParseError err)
Right ast -> ast
scnParser :: Parser ()
scnParser = skipMany (satisfy isNewline)
parseProgramM :: ParserM [TricuAST]
parseProgramM = do
skipMany topLevelNewline
moduleNode <- optional parseModuleM
skipMany topLevelNewline
importNodes <- many (do
node <- parseImportM
skipMany topLevelNewline
return node)
skipMany topLevelNewline
exprs <- sepEndBy parseOneExpression (some topLevelNewline)
skipMany topLevelNewline
return (maybe [] (: []) moduleNode ++ importNodes ++ exprs)
parseExpression :: Parser SaplingAST
parseExpression = choice
[ try parseFunction
, try parseLambda
, try parseLambdaExpression
, try parseListLiteral
, try parseApplication
, try parseTreeTerm
, parseLiteral
parseModuleM :: ParserM TricuAST
parseModuleM = do
LModule moduleName <- satisfyM isModule
pure (SModule moduleName)
where
isModule (LModule _) = True
isModule _ = False
parseImportM :: ParserM TricuAST
parseImportM = do
LImport filePath moduleName <- satisfyM isImport
pure (SImport filePath moduleName)
where
isImport (LImport _ _) = True
isImport _ = False
parseOneExpression :: ParserM TricuAST
parseOneExpression = scnParserM *> parseExpressionM
scnParserM :: ParserM ()
scnParserM = skipMany $ do
t <- lookAhead anySingle
st <- get
if | (parenDepth st > 0 || bracketDepth st > 0) && (t == LNewline) ->
void $ satisfyM (== LNewline)
| 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
]
parseFunction :: Parser SaplingAST
parseFunction = do
LIdentifier name <- satisfy isIdentifier
args <- many (satisfy isIdentifier)
satisfy (== LAssign)
body <- parseExpression
return (SFunc name (map getIdentifier args) body)
parseFunctionM :: ParserM TricuAST
parseFunctionM = do
let ident = (\case LIdentifier _ -> True; _ -> False)
LIdentifier name <- satisfyM ident
args <- many $ satisfyM ident
_ <- satisfyM (== LAssign)
scnParserM
body <- parseExpressionM
pure (SDef name (map getIdentifier args) body)
parseAtomicBase :: Parser SaplingAST
parseAtomicBase = choice
[ try parseVarWithoutAssignment
, parseTreeLeaf
, parseGrouped
]
parseVarWithoutAssignment :: Parser SaplingAST
parseVarWithoutAssignment = do
LIdentifier name <- satisfy isIdentifier
if (name == "t" || name == "__result")
then fail $ "Reserved keyword: " ++ name ++ " cannot be assigned."
else notFollowedBy (satisfy (== LAssign)) *> return (SVar name)
parseLambdaM :: ParserM TricuAST
parseLambdaM = do
let ident = (\case LIdentifier _ -> True; _ -> False)
_ <- satisfyM (== LBackslash)
params <- some (satisfyM ident)
_ <- satisfyM (== LColon)
scnParserM
body <- parseLambdaExpressionM
pure $ foldr (\param acc -> SLambda [getIdentifier param] acc) body params
parseLambda :: Parser SaplingAST
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 SaplingAST
parseLambdaExpression = choice
[ try parseLambdaApplication
, parseAtomicLambda
parseLambdaExpressionM :: ParserM TricuAST
parseLambdaExpressionM = choice
[ try parseLambdaApplicationM
, parseAtomicLambdaM
]
parseAtomicLambda :: Parser SaplingAST
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 SaplingAST
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 SaplingAST
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 :: SaplingAST -> Bool
isTreeTerm TLeaf = True
isTreeTerm (TStem _) = True
isTreeTerm (TFork _ _) = True
isTreeTerm _ = False
parseAtomicBaseM :: ParserM TricuAST
parseAtomicBaseM = choice
[ parseTreeLeafM
, parseGroupedM
]
parseTreeLeaf :: Parser SaplingAST
parseTreeLeaf = satisfy isKeywordT *> notFollowedBy (satisfy (== LAssign)) *> pure TLeaf
parseTreeLeafM :: ParserM TricuAST
parseTreeLeafM = do
let keyword = (\case LKeywordT -> True; _ -> False)
_ <- satisfyM keyword
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)
if | LIdentifier name <- token -> pure (SVar name)
| token == LKeywordT -> pure TLeaf
| otherwise -> fail "Unexpected token in list item"
parseVarM :: ParserM TricuAST
parseVarM = do
satisfyM (\case LIdentifier _ -> True; _ -> False) >>= \case
LIdentifier name
| name == "t" || name == "!result" ->
fail ("Reserved keyword: " ++ name ++ " cannot be assigned.")
| otherwise ->
pure (SVar name)
_ -> fail "Unexpected token while parsing variable"
parseIntLiteralM :: ParserM TricuAST
parseIntLiteralM = do
let intL = (\case LIntegerLiteral _ -> True; _ -> False)
token <- satisfyM intL
if | LIntegerLiteral value <- token ->
pure (SInt value)
| otherwise ->
fail "Unexpected token while parsing integer literal"
parseStrLiteralM :: ParserM TricuAST
parseStrLiteralM = do
let strL = (\case LStringLiteral _ -> True; _ -> False)
token <- satisfyM strL
if | LStringLiteral value <- token ->
pure (SStr value)
| otherwise ->
fail "Unexpected token while parsing string literal"
getIdentifier :: LToken -> String
getIdentifier (LIdentifier name) = name
getIdentifier _ = error "Expected identifier"
getIdentifier _ = errorWithoutStackTrace "Expected identifier"
parseTreeTerm :: Parser SaplingAST
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 SaplingAST
parseTreeLeafOrParenthesized = choice
[ between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseTreeTerm
, parseTreeLeaf
]
foldTree :: [SaplingAST] -> SaplingAST
foldTree [] = TLeaf
foldTree [x] = x
foldTree (x:y:rest) = TFork x (foldTree (y:rest))
parseAtomic :: Parser SaplingAST
parseAtomic = choice
[ parseVar
, parseTreeLeaf
, parseListLiteral
, parseGrouped
, parseLiteral
]
parseGrouped :: Parser SaplingAST
parseGrouped = between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseExpression
parseLiteral :: Parser SaplingAST
parseLiteral = choice
[ parseIntLiteral
, parseStrLiteral
]
parens :: Parser SaplingAST -> Parser SaplingAST
parens p = do
satisfy (== LOpenParen)
result <- p
satisfy (== LCloseParen)
return result
parseListLiteral :: Parser SaplingAST
parseListLiteral = do
satisfy (== LOpenBracket)
elements <- many parseListItem
satisfy (== LCloseBracket)
return (SList elements)
parseListItem :: Parser SaplingAST
parseListItem = choice
[ parseGroupedItem
, parseListLiteral
, parseSingleItem
]
parseGroupedItem :: Parser SaplingAST
parseGroupedItem = do
satisfy (== LOpenParen)
inner <- parseExpression
satisfy (== LCloseParen)
return inner
parseSingleItem :: Parser SaplingAST
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 SaplingAST
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 SaplingAST
parseIntLiteral = do
LIntegerLiteral value <- satisfy isIntegerLiteral
return (SInt value)
parseStrLiteral :: Parser SaplingAST
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
-- 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"

View File

@ -1,42 +1,61 @@
module REPL where
import Eval
import FileEval
import Lexer
import Parser
import Research
import Data.List (intercalate)
import qualified Data.Map as Map
import Control.Exception (SomeException, catch)
import Control.Monad.IO.Class (liftIO)
import Data.Char (isSpace)
import Data.List (dropWhile, dropWhileEnd, intercalate)
import System.Console.Haskeline
import System.IO (hFlush, stdout)
repl :: Map.Map String T -> IO ()
import qualified Data.Map as Map
repl :: Env -> IO ()
repl env = runInputT defaultSettings (loop env)
where
loop :: Map.Map String T -> InputT IO ()
loop :: Env -> InputT IO ()
loop env = do
minput <- getInputLine "sapling < "
case minput of
Nothing -> outputStrLn "Goodbye!"
Just ":_exit" -> outputStrLn "Goodbye!"
Just "" -> do
minput <- getInputLine "tricu < "
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 input -> do
let clearEnv = Map.delete "__result" env
newEnv = evalSingle clearEnv (parseSingle input)
case Map.lookup "__result" newEnv of
Just r -> do
outputStrLn $ "sapling > " ++ show r
outputStrLn $ "DECODE -: " ++ decodeResult r
Nothing -> return ()
| Just s <- minput, strip s == "!import" -> 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
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 _ -> ""
processInput :: Env -> String -> IO Env
processInput env input = do
let asts = parseTricu input
newEnv = evalTricu env asts
if
| Just r <- Map.lookup "!result" newEnv -> do
putStrLn $ "tricu > " ++ decodeResult r
| otherwise -> return ()
return newEnv
errorHandler :: Env -> SomeException -> IO (Env)
errorHandler env e = do
putStrLn $ "Error: " ++ show e
return env
strip :: String -> String
strip = dropWhileEnd isSpace . dropWhile isSpace

View File

@ -1,13 +1,61 @@
module Research where
import Data.List (intercalate)
import Control.Monad.State
import qualified Data.Map as Map
import Data.List (intercalate)
import Data.Map (Map)
import Data.Text (Text, replace)
import System.Console.CmdArgs (Data, Typeable)
import qualified Data.Map as Map
import qualified Data.Text as T
-- Tree Calculus Types
data T = Leaf | Stem T | Fork T T
deriving (Show, Eq, Ord)
-- Abstract Syntax Tree for tricu
data TricuAST
= SVar String
| SInt Int
| SStr String
| SList [TricuAST]
| SDef String [String] TricuAST
| SApp TricuAST TricuAST
| TLeaf
| TStem TricuAST
| TFork TricuAST TricuAST
| SLambda [String] TricuAST
| SEmpty
| SModule String
| SImport String String
deriving (Show, Eq, Ord)
-- Lexer Tokens
data LToken
= LKeywordT
| LIdentifier String
| LIntegerLiteral Int
| LStringLiteral String
| LAssign
| LColon
| LBackslash
| LOpenParen
| LCloseParen
| LOpenBracket
| LCloseBracket
| LNewline
| LModule String
| LImport String String
deriving (Show, Eq, Ord)
-- Output formats
data EvaluatedForm = TreeCalculus | FSL | AST | Ternary | Ascii | Decode
deriving (Show, Data, Typeable)
-- Environment containing previously evaluated TC terms
type Env = Map.Map String T
-- Tree Calculus Reduction
apply :: T -> T -> T
apply Leaf b = Stem b
apply (Stem a) b = Fork a b
@ -17,29 +65,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
_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
@ -87,7 +112,32 @@ toList (Fork x rest) = case toList rest of
Left err -> Left err
toList _ = Left "Invalid Tree Calculus list"
-- Utility
-- Outputs
formatResult :: EvaluatedForm -> T -> String
formatResult TreeCalculus = toSimpleT . show
formatResult FSL = show
formatResult AST = show . toAST
formatResult Ternary = toTernaryString
formatResult Ascii = toAscii
formatResult Decode = decodeResult
toSimpleT :: String -> String
toSimpleT s = T.unpack
$ replace "Fork" "t"
$ replace "Stem" "t"
$ replace "Leaf" "t"
$ (T.pack s)
toTernaryString :: T -> String
toTernaryString Leaf = "0"
toTernaryString (Stem t) = "1" ++ toTernaryString t
toTernaryString (Fork t1 t2) = "2" ++ toTernaryString t1 ++ toTernaryString t2
toAST :: T -> TricuAST
toAST Leaf = TLeaf
toAST (Stem a) = TStem (toAST a)
toAST (Fork a b) = TFork (toAST a) (toAST b)
toAscii :: T -> String
toAscii tree = go tree "" True
where
@ -102,41 +152,9 @@ toAscii tree = go tree "" True
++ go left (prefix ++ (if isLast then " " else "| ")) False
++ go right (prefix ++ (if isLast then " " else "| ")) True
rules :: IO ()
rules = putStr $ header
++ (unlines $ tcRules)
++ (unlines $ haskellRules)
++ footer
where
tcRules :: [String]
tcRules =
[ "| |"
, "| ┌--------- | Tree Calculus | ---------┐ |"
, "| | 1. t t a b -> a | |"
, "| | 2. t (t a) b c -> a c (b c)| |"
, "| | 3a. t (t a b) c t -> a | |"
, "| | 3b. t (t a b) c (t u) -> b u | |"
, "| | 3c. t (t a b) c (t u v) -> c u v | |"
, "| └-------------------------------------┘ |"
, "| |"
]
haskellRules :: [String]
haskellRules =
[ "| ┌------------------------------ | Haskell | --------------------------------┐ |"
, "| | | |"
, "| | data T = Leaf | Stem T | Fork TT | |"
, "| | | |"
, "| | apply :: T -> T -> T | |"
, "| | apply Leaf b = Stem b | |"
, "| | apply (Stem a) b = Fork a b | |"
, "| | apply (Fork Leaf a) _ = a | |"
, "| | apply (Fork (Stem a1) a2) b = apply (apply a1 b) (apply a2 b) | |"
, "| | 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 | |"
, "| └---------------------------------------------------------------------------┘ |"
]
header :: String
header = "┌-------------------- | Rules for evaluating Tree Calculus | -------------------┐\n"
footer :: String
footer = "└-------------------- | Rules for evaluating Tree Calculus | -------------------┘\n"
decodeResult :: T -> String
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

View File

@ -1,11 +1,15 @@
module Main where
import Eval
import FileEval
import Lexer
import Library
import Parser
import REPL
import Research
import Control.Exception (evaluate, try, SomeException)
import Control.Monad.IO.Class (liftIO)
import Data.List (isInfixOf)
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
@ -17,304 +21,533 @@ import qualified Data.Set as Set
main :: IO ()
main = defaultMain tests
runSapling :: String -> String
runSapling s = show $ result (evalSapling Map.empty $ parseSapling s)
runTricu :: String -> String
runTricu s = show $ result (evalTricu Map.empty $ parseTricu s)
tests :: TestTree
tests = testGroup "Sapling Tests"
[ lexerTests
, parserTests
, integrationTests
, evaluationTests
, lambdaEvalTests
, propertyTests
tests = testGroup "Tricu Tests"
[ lexer
, parser
, simpleEvaluation
, lambdas
, baseLibrary
, fileEval
, modules
, demos
]
lexerTests :: TestTree
lexerTests = testGroup "Lexer Tests"
lexer :: TestTree
lexer = testGroup "Lexer Tests"
[ testCase "Lex simple identifiers" $ do
let input = "x a b = a"
expect = Right [LIdentifier "x", LIdentifier "a", LIdentifier "b", LAssign, LIdentifier "a"]
runParser saplingLexer "" input @?= expect
runParser tricuLexer "" input @?= expect
, testCase "Lex Tree Calculus terms" $ do
let input = "t t t"
expect = Right [LKeywordT, LKeywordT, LKeywordT]
runParser saplingLexer "" input @?= expect
runParser tricuLexer "" input @?= expect
, testCase "Lex escaped characters in strings" $ do
let input = "\"hello\\nworld\""
expect = Right [LStringLiteral "hello\\nworld"]
runParser saplingLexer "" input @?= expect
runParser tricuLexer "" input @?= expect
, testCase "Lex mixed literals" $ do
let input = "t \"string\" 42"
expect = Right [LKeywordT, LStringLiteral "string", LIntegerLiteral 42]
runParser saplingLexer "" input @?= expect
runParser tricuLexer "" input @?= expect
, testCase "Lex invalid token" $ do
let input = "$invalid"
case runParser saplingLexer "" input of
let input = "&invalid"
case runParser tricuLexer "" input of
Left _ -> return ()
Right _ -> assertFailure "Expected lexer to fail on invalid token"
, testCase "Drop trailing whitespace in definitions" $ do
let input = "x = 5 "
expect = [LIdentifier "x",LAssign,LIntegerLiteral 5]
case (runParser saplingLexer "" input) of
case (runParser tricuLexer "" input) of
Left _ -> assertFailure "Failed to lex input"
Right i -> i @?= expect
, testCase "Error when using invalid characters in identifiers" $ do
case (runParser saplingLexer "" "__result = 5") of
case (runParser tricuLexer "" "!result = 5") of
Left _ -> return ()
Right _ -> assertFailure "Expected failure when trying to assign the value of __result"
Right _ -> assertFailure "Expected failure when trying to assign the value of !result"
]
parserTests :: TestTree
parserTests = testGroup "Parser Tests"
[ --testCase "Error when parsing incomplete definitions" $ do
-- let input = lexSapling "x = "
-- case (runParser parseExpression "" input) of
-- Left _ -> return ()
-- Right _ -> assertFailure "Expected failure on invalid input"
testCase "Error when assigning a value to T" $ do
let input = lexSapling "t = x"
case (runParser parseExpression "" input) of
parser :: TestTree
parser = testGroup "Parser Tests"
[ testCase "Error when assigning a value to T" $ do
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)"
expect = SFunc "x" [] (SLambda ["a"] (SLambda ["b"] (SLambda ["c"] (SVar "a"))))
expect = SDef "x" [] (SLambda ["a"] (SLambda ["b"] (SLambda ["c"] (SVar "a"))))
parseSingle input @?= expect
, testCase "Parse nested Tree Calculus terms" $ do
let input = "t (t t) t"
expect = SApp (SApp TLeaf (SApp TLeaf TLeaf)) TLeaf
parseSingle input @?= expect
, testCase "Parse sequential Tree Calculus terms" $ do
let input = "t t t"
expect = SApp (SApp TLeaf TLeaf) TLeaf
parseSingle input @?= expect
, testCase "Parse mixed list literals" $ do
let input = "[t (\"hello\") t]"
expect = SList [TLeaf, SStr "hello", TLeaf]
parseSingle input @?= expect
, testCase "Parse function with applications" $ do
let input = "f = (\\x : t x)"
expect = SFunc "f" [] (SLambda ["x"] (SApp TLeaf (SVar "x")))
expect = SDef "f" [] (SLambda ["x"] (SApp TLeaf (SVar "x")))
parseSingle input @?= expect
, testCase "Parse nested lists" $ do
let input = "[t [(t t)]]"
expect = SList [TLeaf,SList [SApp TLeaf TLeaf]]
parseSingle input @?= expect
, testCase "Parse complex parentheses" $ do
let input = "t (t t (t t))"
expect = SApp TLeaf (SApp (SApp TLeaf TLeaf) (SApp TLeaf TLeaf))
parseSingle input @?= expect
, testCase "Parse empty list" $ do
let input = "[]"
expect = SList []
parseSingle input @?= expect
, testCase "Parse multiple nested lists" $ do
let input = "[[t t] [t (t t)]]"
expect = SList [SList [TLeaf,TLeaf],SList [TLeaf,SApp TLeaf TLeaf]]
parseSingle input @?= expect
, testCase "Parse whitespace variance" $ do
let input1 = "[t t]"
let input2 = "[ t t ]"
expect = SList [TLeaf, TLeaf]
parseSingle input1 @?= expect
parseSingle input2 @?= expect
, testCase "Parse string in list" $ do
let input = "[(\"hello\")]"
expect = SList [SStr "hello"]
parseSingle input @?= expect
, testCase "Parse parentheses inside list" $ do
let input = "[t (t t)]"
expect = SList [TLeaf,SApp TLeaf TLeaf]
parseSingle input @?= expect
, testCase "Parse nested parentheses in function body" $ do
let input = "f = (\\x : t (t (t t)))"
expect = SFunc "f" [] (SLambda ["x"] (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf))))
expect = SDef "f" [] (SLambda ["x"] (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf))))
parseSingle input @?= expect
, testCase "Parse lambda abstractions" $ do
let input = "(\\a : a)"
expect = (SLambda ["a"] (SVar "a"))
parseSingle input @?= expect
, testCase "Parse multiple arguments to lambda abstractions" $ do
let input = "x = (\\a b : a)"
expect = SFunc "x" [] (SLambda ["a"] (SLambda ["b"] (SVar "a")))
expect = SDef "x" [] (SLambda ["a"] (SLambda ["b"] (SVar "a")))
parseSingle input @?= expect
, testCase "Grouping T terms with parentheses in function application" $ do
let input = "x = (\\a : a)\nx (t)"
expect = [SFunc "x" [] (SLambda ["a"] (SVar "a")),SApp (SVar "x") TLeaf]
parseSapling input @?= expect
expect = [SDef "x" [] (SLambda ["a"] (SVar "a")),SApp (SVar "x") TLeaf]
parseTricu input @?= expect
, testCase "Comments 1" $ do
let input = "(t) (t) -- (t)"
expect = [SApp TLeaf TLeaf]
parseTricu input @?= expect
, testCase "Comments 2" $ do
let input = "(t) -- (t) -- (t)"
expect = [TLeaf]
parseTricu input @?= expect
]
integrationTests :: TestTree
integrationTests = testGroup "Integration Tests"
[ testCase "Combine lexer and parser" $ do
let input = "x = t t t"
expect = SFunc "x" [] (SApp (SApp TLeaf TLeaf) TLeaf)
parseSingle input @?= expect
, testCase "Complex Tree Calculus expression" $ do
let input = "t (t t t) t"
expect = SApp (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf)) TLeaf
parseSingle input @?= expect
]
evaluationTests :: TestTree
evaluationTests = testGroup "Evaluation Tests"
simpleEvaluation :: TestTree
simpleEvaluation = testGroup "Evaluation Tests"
[ testCase "Evaluate single Leaf" $ do
let input = "t"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= Leaf
, testCase "Evaluate single Stem" $ do
let input = "t t"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= Stem Leaf
, testCase "Evaluate single Fork" $ do
let input = "t t t"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= Fork Leaf Leaf
, testCase "Evaluate nested Fork and Stem" $ do
let input = "t (t t) t"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= Fork (Stem Leaf) Leaf
, testCase "Evaluate `not` function" $ do
let input = "t (t (t t) (t t t)) t"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?=
Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf
, testCase "Environment updates with definitions" $ do
let input = "x = t\ny = x"
env = evalSapling Map.empty (parseSapling input)
env = evalTricu Map.empty (parseTricu input)
Map.lookup "x" env @?= Just Leaf
Map.lookup "y" env @?= Just Leaf
, testCase "Variable substitution" $ do
let input = "x = t t\ny = t x\ny"
env = evalSapling Map.empty (parseSapling input)
env = evalTricu Map.empty (parseTricu input)
(result env) @?= Stem (Stem Leaf)
, testCase "Multiline input evaluation" $ do
let input = "x = t\ny = t t\nx"
env = evalSapling Map.empty (parseSapling input)
env = evalTricu Map.empty (parseTricu input)
(result env) @?= Leaf
, testCase "Evaluate string literal" $ do
let input = "\"hello\""
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= ofString "hello"
, testCase "Evaluate list literal" $ do
let input = "[t (t t)]"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= ofList [Leaf, Stem Leaf]
, testCase "Evaluate empty list" $ do
let input = "[]"
let ast = parseSingle input
(result $ evalSingle Map.empty ast) @?= ofList []
, testCase "Evaluate variable dependency chain" $ do
let input = "x = t (t t)\n \
\ y = x\n \
\ z = y\n \
\ variablewithamuchlongername = z\n \
\ variablewithamuchlongername"
env = evalSapling Map.empty (parseSapling input)
env = evalTricu Map.empty (parseTricu input)
(result env) @?= (Stem (Stem Leaf))
, testCase "Evaluate variable shadowing" $ do
, testCase "Immutable definitions" $ do
let input = "x = t t\nx = t\nx"
env = evalSapling Map.empty (parseSapling input)
(result env) @?= Leaf
env = evalTricu Map.empty (parseTricu input)
result <- try (evaluate (runTricu input)) :: IO (Either SomeException String)
case result of
Left _ -> return ()
Right _ -> assertFailure "Expected evaluation error"
, 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 = evalSapling Map.empty (parseSapling input)
env = evalTricu Map.empty (parseTricu input)
result env @?= Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf
, testCase "Constant function matches" $ do
let input = "k = (\\a b : a)\nk (t t) t"
env = evalSapling Map.empty (parseSapling input)
result env @?= Stem Leaf
, testCase "Boolean AND_ TF" $ do
let input = "and (t t) (t)"
env = evalSapling library (parseSapling input)
result env @?= Leaf
, testCase "Boolean AND_ FT" $ do
let input = "and (t) (t t)"
env = evalSapling library (parseSapling input)
result env @?= Leaf
, testCase "Boolean AND_ FF" $ do
let input = "and (t) (t)"
env = evalSapling library (parseSapling input)
result env @?= Leaf
, testCase "Boolean AND_ TT" $ do
let input = "and (t t) (t t)"
env = evalSapling library (parseSapling input)
result env @?= Stem Leaf
, testCase "Verifying Equality" $ do
let input = "equal (t t t) (t t t)"
env = evalSapling library (parseSapling input)
result env @?= Stem Leaf
]
lambdaEvalTests :: TestTree
lambdaEvalTests = testGroup "Lambda Evaluation Tests"
lambdas :: TestTree
lambdas = testGroup "Lambda Evaluation Tests"
[ testCase "Lambda Identity Function" $ do
let input = "id = (\\x : x)\nid t"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Lambda Constant Function (K combinator)" $ do
let input = "k = (\\x y : x)\nk t (t t)"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Lambda Application with Variable" $ do
let input = "id = (\\x : x)\nval = t t\nid val"
runSapling input @?= "Stem Leaf"
runTricu input @?= "Stem Leaf"
, testCase "Lambda Application with Multiple Arguments" $ do
let input = "apply = (\\f x y : f x y)\nk = (\\a b : a)\napply k t (t t)"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Nested Lambda Application" $ do
let input = "apply = (\\f x y : f x y)\nid = (\\x : x)\napply (\\f x : f x) id t"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Lambda with a complex body" $ do
let input = "f = (\\x : t (t x))\nf t"
runSapling input @?= "Stem (Stem Leaf)"
runTricu input @?= "Stem (Stem Leaf)"
, testCase "Lambda returning a function" $ do
let input = "f = (\\x : (\\y : x))\ng = f t\ng (t t)"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Lambda with Shadowing" $ do
let input = "f = (\\x : (\\x : x))\nf t (t t)"
runSapling input @?= "Stem Leaf"
runTricu input @?= "Stem Leaf"
, testCase "Lambda returning another lambda" $ do
let input = "k = (\\x : (\\y : x))\nk_app = k t\nk_app (t t)"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Lambda with free variables" $ do
let input = "y = t t\nf = (\\x : y)\nf t"
runSapling input @?= "Stem Leaf"
runTricu input @?= "Stem Leaf"
, testCase "SKI Composition" $ do
let input = "s = (\\x y z : x z (y z))\nk = (\\x y : x)\ni = (\\x : x)\ncomp = s k i\ncomp t (t t)"
runSapling input @?= "Stem (Stem Leaf)"
runTricu input @?= "Stem (Stem Leaf)"
, testCase "Lambda with multiple parameters and application" $ do
let input = "f = (\\a b c : t a b c)\nf t (t t) (t t t)"
runSapling input @?= "Stem Leaf"
runTricu input @?= "Stem Leaf"
, testCase "Lambda with nested application in the body" $ do
let input = "f = (\\x : t (t (t x)))\nf t"
runSapling input @?= "Stem (Stem (Stem Leaf))"
runTricu input @?= "Stem (Stem (Stem Leaf))"
, testCase "Lambda returning a function and applying it" $ do
let input = "f = (\\x : (\\y : t x y))\ng = f t\ng (t t)"
runSapling input @?= "Fork Leaf (Stem Leaf)"
runTricu input @?= "Fork Leaf (Stem Leaf)"
, testCase "Lambda applying a variable" $ do
let input = "id = (\\x : x)\na = t t\nid a"
runSapling input @?= "Stem Leaf"
runTricu input @?= "Stem Leaf"
, testCase "Nested lambda abstractions in the same expression" $ do
let input = "f = (\\x : (\\y : x y))\ng = (\\z : z)\nf g t"
runSapling input @?= "Leaf"
runTricu input @?= "Leaf"
, testCase "Lambda with a string literal" $ do
let input = "f = (\\x : x)\nf \"hello\""
runSapling input @?= "Fork (Fork Leaf (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) Leaf))))"
runTricu input @?= "Fork (Fork Leaf (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork Leaf (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) (Fork (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork (Stem Leaf) Leaf))))))) Leaf))))"
, testCase "Lambda with an integer literal" $ do
let input = "f = (\\x : x)\nf 42"
runSapling input @?= "Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) Leaf)))))"
runTricu input @?= "Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) Leaf)))))"
, testCase "Lambda with a list literal" $ do
let input = "f = (\\x : x)\nf [t (t t)]"
runSapling input @?= "Fork Leaf (Fork (Stem Leaf) Leaf)"
runTricu input @?= "Fork Leaf (Fork (Stem Leaf) Leaf)"
]
propertyTests :: TestTree
propertyTests = testGroup "Property Tests"
[ testProperty "Lexing and parsing round-trip" $ \input ->
case runParser saplingLexer "" input of
Left _ -> property True
Right tokens -> case runParser parseExpression "" tokens of
Left _ -> property True
Right ast -> parseSingle input === ast
baseLibrary :: TestTree
baseLibrary = testGroup "Library Tests"
[ testCase "K combinator 1" $ do
library <- evaluateFile "./lib/base.tri"
let input = "k (t) (t t)"
env = evalTricu library (parseTricu input)
result env @?= Leaf
, testCase "K combinator 2" $ do
library <- evaluateFile "./lib/base.tri"
let input = "k (t t) (t)"
env = evalTricu library (parseTricu input)
result env @?= Stem Leaf
, testCase "K combinator 3" $ do
library <- evaluateFile "./lib/base.tri"
let input = "k (t t t) (t)"
env = evalTricu library (parseTricu input)
result env @?= Fork Leaf Leaf
, testCase "S combinator" $ do
library <- evaluateFile "./lib/base.tri"
let input = "s (t) (t) (t)"
env = evalTricu library (parseTricu input)
result env @?= Fork Leaf (Stem Leaf)
, testCase "SKK == I (fully expanded)" $ do
library <- evaluateFile "./lib/base.tri"
let input = "s k k"
env = evalTricu library (parseTricu input)
result env @?= Fork (Stem (Stem Leaf)) (Stem Leaf)
, testCase "I combinator" $ do
library <- evaluateFile "./lib/base.tri"
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
library <- evaluateFile "./lib/base.tri"
let input = "test t"
env = decodeResult $ result $ evalTricu library (parseTricu input)
env @?= "\"Leaf\""
, testCase "Triage test (Stem Leaf)" $ do
library <- evaluateFile "./lib/base.tri"
let input = "test (t t)"
env = decodeResult $ result $ evalTricu library (parseTricu input)
env @?= "\"Stem\""
, testCase "Triage test (Fork Leaf Leaf)" $ do
library <- evaluateFile "./lib/base.tri"
let input = "test (t t t)"
env = decodeResult $ result $ evalTricu library (parseTricu input)
env @?= "\"Fork\""
, testCase "Boolean NOT: true" $ do
library <- evaluateFile "./lib/base.tri"
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"
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)"
env = evalTricu library (parseTricu input)
result env @?= Leaf
, testCase "Boolean AND FT" $ do
library <- evaluateFile "./lib/base.tri"
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)"
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)"
env = evalTricu library (parseTricu input)
result env @?= Stem Leaf
, testCase "List head" $ do
library <- evaluateFile "./lib/base.tri"
let input = "head [(t) (t t) (t t t)]"
env = evalTricu library (parseTricu input)
result env @?= Leaf
, testCase "List tail" $ do
library <- evaluateFile "./lib/base.tri"
let input = "head (tail (tail [(t) (t t) (t t t)]))"
env = evalTricu library (parseTricu input)
result env @?= Fork Leaf Leaf
, testCase "List map" $ do
library <- evaluateFile "./lib/base.tri"
let input = "head (tail (map (\\a : (t t t)) [(t) (t) (t)]))"
env = evalTricu library (parseTricu input)
result env @?= Fork Leaf Leaf
, testCase "Empty list check" $ do
library <- evaluateFile "./lib/base.tri"
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)])"
env = evalTricu library (parseTricu input)
result env @?= Stem Leaf
, testCase "Concatenate strings" $ do
library <- evaluateFile "./lib/base.tri"
let input = "lconcat \"Hello, \" \"world!\""
env = decodeResult $ result $ evalTricu library (parseTricu input)
env @?= "\"Hello, world!\""
, testCase "Verifying Equality" $ do
library <- evaluateFile "./lib/base.tri"
let input = "equal? (t t t) (t t t)"
env = evalTricu library (parseTricu input)
result env @?= Stem Leaf
]
fileEval :: TestTree
fileEval = testGroup "File evaluation tests"
[ testCase "Forks" $ do
res <- liftIO $ evaluateFileResult "./test/fork.tri"
res @?= Fork Leaf Leaf
, testCase "File ends with comment" $ do
res <- liftIO $ evaluateFileResult "./test/comments-1.tri"
res @?= Fork (Stem Leaf) Leaf
, testCase "Mapping and Equality" $ do
library <- liftIO $ evaluateFile "./lib/base.tri"
fEnv <- liftIO $ evaluateFileWithContext library "./test/map.tri"
(mainResult fEnv) @?= Stem Leaf
, testCase "Eval and decoding string" $ do
library <- liftIO $ evaluateFile "./lib/base.tri"
res <- liftIO $ evaluateFileWithContext library "./test/string.tri"
decodeResult (result res) @?= "\"String test!\""
]
modules :: TestTree
modules = testGroup "Test modules"
[ testCase "Detect cyclic dependencies" $ do
result <- try (liftIO $ evaluateFileResult "./test/cycle-1.tri") :: IO (Either SomeException T)
case result of
Left e -> do
let errorMsg = show e
if "Encountered cyclic import" `isInfixOf` errorMsg
then return ()
else assertFailure $ "Unexpected error: " ++ errorMsg
Right _ -> assertFailure "Expected cyclic dependencies"
, testCase "Module imports and namespacing" $ do
res <- liftIO $ evaluateFileResult "./test/namespace-A.tri"
res @?= Leaf
, testCase "Multiple imports" $ do
res <- liftIO $ evaluateFileResult "./test/vars-A.tri"
res @?= Leaf
, testCase "Error on unresolved variable" $ do
result <- try (liftIO $ evaluateFileResult "./test/unresolved-A.tri") :: IO (Either SomeException T)
case result of
Left e -> do
let errorMsg = show e
if "undefinedVar" `isInfixOf` errorMsg
then return ()
else assertFailure $ "Unexpected error: " ++ errorMsg
Right _ -> assertFailure "Expected unresolved variable error"
, testCase "Multi-level imports" $ do
res <- liftIO $ evaluateFileResult "./test/multi-level-A.tri"
res @?= Leaf
, testCase "Lambda expression namespaces" $ do
res <- liftIO $ evaluateFileResult "./test/lambda-A.tri"
res @?= Leaf
]
-- All of our demo tests are also module tests
demos :: TestTree
demos = testGroup "Test provided demo functionality"
[ testCase "Structural equality demo" $ do
res <- liftIO $ evaluateFileResult "./demos/equality.tri"
decodeResult res @?= "t t"
, testCase "Convert values back to source code demo" $ do
res <- liftIO $ evaluateFileResult "./demos/toSource.tri"
decodeResult res @?= "\"(t (t (t t) (t t t)) (t t (t t t)))\""
, testCase "Determining the size of functions" $ do
res <- liftIO $ evaluateFileResult "./demos/size.tri"
decodeResult res @?= "454"
, testCase "Level Order Traversal demo" $ do
res <- liftIO $ evaluateFileResult "./demos/levelOrderTraversal.tri"
decodeResult res @?= "\"\n1 \n2 3 \n4 5 6 7 \n8 11 10 9 12 \""
]

1
test/ascii.tri Normal file
View File

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

1
test/assignment.tri Normal file
View File

@ -0,0 +1 @@
x = 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)
main = t (t t) t -- Fork (Stem Leaf) Leaf
-- t t
-- x
-- x = (\a : a)
-- t

5
test/cycle-1.tri Normal file
View File

@ -0,0 +1,5 @@
!module Cycle
!import "test/cycle-2.tri" Cycle2
cycle1 = t Cycle2.cycle2

5
test/cycle-2.tri Normal file
View File

@ -0,0 +1,5 @@
!module Cycle2
!import "test/cycle-1.tri" Cycle1
cycle2 = t Cycle1.cycle1

1
test/fork.tri Normal file
View File

@ -0,0 +1 @@
main = t t t

2
test/lambda-A.tri Normal file
View File

@ -0,0 +1,2 @@
!module A
main = (\x : x) t

2
test/map.tri Normal file
View File

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

5
test/modules-1.tri Normal file
View File

@ -0,0 +1,5 @@
!module Test
!import "lib/base.tri" Lib
main = Lib.not? t

1
test/modules-2.tri Normal file
View File

@ -0,0 +1 @@
n = t t t

3
test/multi-level-A.tri Normal file
View File

@ -0,0 +1,3 @@
!module A
!import "./test/multi-level-B.tri" B
main = B.main

3
test/multi-level-B.tri Normal file
View File

@ -0,0 +1,3 @@
!module B
!import "./test/multi-level-C.tri" C
main = C.val

2
test/multi-level-C.tri Normal file
View File

@ -0,0 +1,2 @@
!module C
val = t

3
test/namespace-A.tri Normal file
View File

@ -0,0 +1,3 @@
!module A
!import "./test/namespace-B.tri" B
main = B.x

2
test/namespace-B.tri Normal file
View File

@ -0,0 +1,2 @@
!module B
x = t

21
test/size.tri Normal file
View File

@ -0,0 +1,21 @@
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 :
(y (\self x :
compose succ
(triage
(\x : x)
self
(\x y : compose (self x) (self y))
x)) x 0))
size size

1
test/string.tri Normal file
View File

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

1
test/undefined.tri Normal file
View File

@ -0,0 +1 @@
namedTerm = undefinedForTesting

2
test/unresolved-A.tri Normal file
View File

@ -0,0 +1,2 @@
!module A
main = undefinedVar

7
test/vars-A.tri Normal file
View File

@ -0,0 +1,7 @@
!module A
!import "./test/vars-B.tri" B
!import "./test/vars-C.tri" C
main = B.y (C.z)

2
test/vars-B.tri Normal file
View File

@ -0,0 +1,2 @@
!module B
y = \x : x

2
test/vars-C.tri Normal file
View File

@ -0,0 +1,2 @@
!module C
z = t

View File

@ -1,7 +1,7 @@
cabal-version: 1.12
name: sapling
version: 0.4.0
name: tricu
version: 0.12.0
description: A micro-language for exploring Tree Calculus
author: James Eversole
maintainer: james@eversole.co
@ -12,41 +12,45 @@ build-type: Simple
extra-source-files:
README.md
executable sapling
executable tricu
main-is: Main.hs
hs-source-dirs:
src
default-extensions:
ConstraintKinds
DataKinds
DeriveGeneric
FlexibleContexts
FlexibleInstances
GeneralizedNewtypeDeriving
DeriveDataTypeable
LambdaCase
MultiWayIf
OverloadedStrings
ScopedTypeVariables
ghc-options: -threaded -rtsopts -with-rtsopts=-N -optl-pthread -fPIC
build-depends:
base >=4.7
, cmdargs
, containers
, haskeline
, megaparsec
, mtl
, text
other-modules:
Eval
FileEval
Lexer
Library
Parser
REPL
Research
default-language: Haskell2010
test-suite sapling-tests
test-suite tricu-tests
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs: test, src
default-extensions:
DeriveDataTypeable
LambdaCase
MultiWayIf
OverloadedStrings
build-depends:
base
, cmdargs
, containers
, haskeline
, megaparsec
@ -54,11 +58,12 @@ test-suite sapling-tests
, tasty
, tasty-hunit
, tasty-quickcheck
, text
default-language: Haskell2010
other-modules:
Eval
FileEval
Lexer
Library
Parser
REPL
Research