Compare commits
17 Commits
0.15.0
...
contentsto
Author | SHA1 | Date | |
---|---|---|---|
72e5810ca9 | |||
b96a3f2ef0 | |||
6780b242b1 | |||
94514f7dd0 | |||
43e83be9a4 | |||
3717942589 | |||
b8e2743103 | |||
25bfe139e8 | |||
f2beb86d8a | |||
5024a2be4c | |||
fccee3e61c | |||
ad1918aa6f | |||
0a505172b4 | |||
e6e18239a7 | |||
871245b567 | |||
30b9505d5f | |||
f4e50353ed |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@
|
|||||||
/Dockerfile
|
/Dockerfile
|
||||||
/config.dhall
|
/config.dhall
|
||||||
/result
|
/result
|
||||||
|
.aider*
|
||||||
WD
|
WD
|
||||||
bin/
|
bin/
|
||||||
dist*
|
dist*
|
||||||
|
72
README.md
72
README.md
@ -2,37 +2,42 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
tricu (pronounced "tree-shoe") is a purely functional interpreted language implemented in Haskell. It is fundamentally based on the application of [Tree Calculus](https://github.com/barry-jay-personal/typed_tree_calculus/blob/main/typed_program_analysis.pdf) terms, but minimal syntax sugar is included to provide a useful programming tool.
|
tricu (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.
|
||||||
|
|
||||||
*tricu is under active development and you should expect breaking changes with every commit.*
|
*This experiment has concluded. tricu will see no further development or bugfixes.*
|
||||||
|
|
||||||
tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)`.
|
tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2)`.
|
||||||
|
|
||||||
|
## 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 Tree Calculus code playground created by [Johannes Bader](https://johannes-bader.com/) that introduced me to Tree Calculus.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Tree Calculus operator: `t`
|
- Tree Calculus **operator**: `t`
|
||||||
- Assignments: `x = t t`
|
- **Immutable definitions**: `x = t t`
|
||||||
- Immutable definitions
|
- **Lambda abstraction**: `id = (a : a)`
|
||||||
- Lambda abstraction syntax: `id = (\a : a)`
|
- **List, Number, and String** literals: `[(2) ("Hello")]`
|
||||||
- List, Number, and String literals: `[(2) ("Hello")]`
|
- **Function application**: `not (not false)`
|
||||||
- Function application: `not (not false)`
|
- **Higher order/first-class functions**: `map (a : append a "!") [("Hello")]`
|
||||||
- Higher order/first-class functions: `map (\a : append a "!") [("Hello")]`
|
- **Intensionality** blurs the distinction between functions and data (see REPL examples)
|
||||||
- Intensionality blurs the distinction between functions and data (see REPL examples)
|
- **Content-addressed store**: save, version, tag, and recall your tricu terms.
|
||||||
- Simple module system for code organization
|
|
||||||
|
|
||||||
## REPL examples
|
## REPL examples
|
||||||
|
|
||||||
```
|
```
|
||||||
tricu < -- Anything after `--` on a single line is a comment
|
tricu < -- Anything after `--` on a single line is a comment
|
||||||
tricu < id = (\a : a) -- Lambda abstraction is eliminated to tree calculus terms
|
tricu < id = (a : a) -- Lambda abstraction is eliminated to tree calculus terms
|
||||||
tricu < head (map (\i : append i " world!") [("Hello, ")])
|
tricu < head (map (i : append i " world!") [("Hello, ")])
|
||||||
tricu > "Hello, world!"
|
tricu > "Hello, world!"
|
||||||
tricu < id (head (map (\i : append i " world!") [("Hello, ")]))
|
tricu < id (head (map (i : append i " world!") [("Hello, ")]))
|
||||||
tricu > "Hello, world!"
|
tricu > "Hello, world!"
|
||||||
|
|
||||||
tricu < -- Intensionality! We can inspect the structure of a function or data.
|
tricu < -- Intensionality! We can inspect the structure of a function or data.
|
||||||
tricu < triage = (\a b c : t (t a b) c)
|
tricu < triage = (a b c : t (t a b) c)
|
||||||
tricu < test = triage "Leaf" (\z : "Stem") (\a b : "Fork")
|
tricu < test = triage "Leaf" (z : "Stem") (a b : "Fork")
|
||||||
tricu < test (t t)
|
tricu < test (t t)
|
||||||
tricu > "Stem"
|
tricu > "Stem"
|
||||||
tricu < -- We can even convert a term back to source code (/demos/toSource.tri)
|
tricu < -- We can even convert a term back to source code (/demos/toSource.tri)
|
||||||
@ -41,13 +46,36 @@ tricu > "(t (t (t t) (t t t)) (t t (t t t)))"
|
|||||||
tricu < -- or calculate its size (/demos/size.tri)
|
tricu < -- or calculate its size (/demos/size.tri)
|
||||||
tricu < size not?
|
tricu < size not?
|
||||||
tricu > 12
|
tricu > 12
|
||||||
|
|
||||||
|
tricu < !help
|
||||||
|
tricu version 0.20.0
|
||||||
|
Available commands:
|
||||||
|
!exit - Exit the REPL
|
||||||
|
!clear - Clear the screen
|
||||||
|
!reset - Reset preferences for selected versions
|
||||||
|
!help - Show tricu version and available commands
|
||||||
|
!output - Change output format (tree|fsl|ast|ternary|ascii|decode)
|
||||||
|
!definitions - List all defined terms in the content store
|
||||||
|
!import - Import definitions from file (definitions are stored)
|
||||||
|
!watch - Watch a file for changes (definitions are stored)
|
||||||
|
!versions - Show all versions of a term by name
|
||||||
|
!select - Select a specific version of a term for subsequent lookups
|
||||||
|
!tag - Add or update a tag for a term by hash or name
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Content Store
|
||||||
|
|
||||||
|
tricu uses a "content store" SQLite database that saves and versions your definitions persistently.
|
||||||
|
|
||||||
|
* **Persistent definitions:** Any term you define in the REPL is automatically saved.
|
||||||
|
* **Content-addressed:** Terms are stored based on a SHA256 hash of their content. This means identical terms are stored only once, even if they have different names.
|
||||||
|
* **Versioning and history:** If you redefine a name, the Content Store keeps a record of previous definitions associated with that name. You can explore the history of a term and access older versions.
|
||||||
|
* **Tagging:** You can assign tags to versions of your terms to organize and quickly switch between related function versions.
|
||||||
|
* **Querying:** The store allows you to search for terms by name, hash, or tags.
|
||||||
|
|
||||||
## Installation and Use
|
## Installation and Use
|
||||||
|
|
||||||
[Releases are available for Linux.](https://git.eversole.co/James/tricu/releases)
|
You can easily build and run this project using [Nix](https://nixos.org/download/).
|
||||||
|
|
||||||
Or you can easily build and run this project using [Nix](https://nixos.org/download/).
|
|
||||||
|
|
||||||
- Quick Start (REPL):
|
- Quick Start (REPL):
|
||||||
- `nix run git+https://git.eversole.co/James/tricu`
|
- `nix run git+https://git.eversole.co/James/tricu`
|
||||||
@ -83,9 +111,3 @@ tricu decode [OPTIONS]
|
|||||||
-f --file=FILE Optional input file path to attempt decoding.
|
-f --file=FILE Optional input file path to attempt decoding.
|
||||||
Defaults to stdin.
|
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 Tree Calculus code playground created by [Johannes Bader](https://johannes-bader.com/) that introduced me to Tree Calculus.
|
|
||||||
|
@ -11,20 +11,17 @@ demo_true = t t
|
|||||||
not_TC? = t (t (t t) (t t t)) (t t (t t t))
|
not_TC? = t (t (t t) (t t t)) (t t (t t t))
|
||||||
|
|
||||||
-- /demos/toSource.tri contains an explanation of `triage`
|
-- /demos/toSource.tri contains an explanation of `triage`
|
||||||
demo_triage = \a b c : t (t a b) c
|
demo_triage = a b c : t (t a b) c
|
||||||
demo_matchBool = (\ot of : demo_triage
|
demo_matchBool = a b : demo_triage b (_ : a) (_ _ : a)
|
||||||
of
|
|
||||||
(\_ : ot)
|
|
||||||
(\_ _ : ot)
|
|
||||||
)
|
|
||||||
-- Lambda representation of the Boolean `not` function
|
-- Lambda representation of the Boolean `not` function
|
||||||
not_Lambda? = demo_matchBool demo_false demo_true
|
not_Lambda? = demo_matchBool demo_false demo_true
|
||||||
|
|
||||||
-- Since tricu eliminates Lambda terms to SKI combinators, the tree form of many
|
-- As tricu eliminates Lambda terms to SKI combinators, the tree form of many
|
||||||
-- functions defined via Lambda terms are larger than the most efficient TC
|
-- functions defined via Lambda terms are larger than the most efficient TC
|
||||||
-- representation. Between different languages that evaluate to tree calculus
|
-- representation possible. Between different languages that evaluate to tree
|
||||||
-- terms, the exact implementation of Lambda elimination may differ and lead
|
-- calculus terms, the exact implementation of Lambda elimination may differ
|
||||||
-- to different tree representations even if they share extensional behavior.
|
-- and lead to different trees even if they share extensional behavior.
|
||||||
|
|
||||||
-- Let's see if these are the same:
|
-- Let's see if these are the same:
|
||||||
lambdaEqualsTC = equal? not_TC? not_Lambda?
|
lambdaEqualsTC = equal? not_TC? not_Lambda?
|
||||||
|
@ -18,47 +18,47 @@ main = exampleTwo
|
|||||||
-- / / \
|
-- / / \
|
||||||
-- 4 5 6
|
-- 4 5 6
|
||||||
|
|
||||||
label = \node : head node
|
label = node : head node
|
||||||
|
|
||||||
left = (\node : if (emptyList? node)
|
left = node : (if (emptyList? node)
|
||||||
[]
|
[]
|
||||||
(if (emptyList? (tail node))
|
(if (emptyList? (tail node))
|
||||||
[]
|
[]
|
||||||
(head (tail node))))
|
(head (tail node))))
|
||||||
|
|
||||||
right = (\node : if (emptyList? node)
|
right = node : (if (emptyList? node)
|
||||||
[]
|
[]
|
||||||
(if (emptyList? (tail node))
|
(if (emptyList? (tail node))
|
||||||
[]
|
[]
|
||||||
(if (emptyList? (tail (tail node)))
|
(if (emptyList? (tail (tail node)))
|
||||||
[]
|
[]
|
||||||
(head (tail (tail node))))))
|
(head (tail (tail node))))))
|
||||||
|
|
||||||
processLevel = y (\self queue : if (emptyList? queue)
|
processLevel = y (self queue : if (emptyList? queue)
|
||||||
[]
|
[]
|
||||||
(pair (map label queue) (self (filter
|
(pair (map label queue) (self (filter
|
||||||
(\node : not? (emptyList? node))
|
(node : not? (emptyList? node))
|
||||||
(append (map left queue) (map right queue))))))
|
(append (map left queue) (map right queue))))))
|
||||||
|
|
||||||
levelOrderTraversal_ = \a : processLevel (t a t)
|
levelOrderTraversal_ = a : processLevel (t a t)
|
||||||
|
|
||||||
toLineString = y (\self levels : if (emptyList? levels)
|
toLineString = y (self levels : if (emptyList? levels)
|
||||||
""
|
""
|
||||||
(append
|
(append
|
||||||
(append (map (\x : append x " ") (head levels)) "")
|
(append (map (x : append x " ") (head levels)) "")
|
||||||
(if (emptyList? (tail levels)) "" (append (t (t 10 t) t) (self (tail levels))))))
|
(if (emptyList? (tail levels)) "" (append (t (t 10 t) t) (self (tail levels))))))
|
||||||
|
|
||||||
levelOrderToString = \s : toLineString (levelOrderTraversal_ s)
|
levelOrderToString = s : toLineString (levelOrderTraversal_ s)
|
||||||
|
|
||||||
flatten = foldl (\acc x : append acc x) ""
|
flatten = foldl (acc x : append acc x) ""
|
||||||
|
|
||||||
levelOrderTraversal = \s : append (t 10 t) (flatten (levelOrderToString s))
|
levelOrderTraversal = s : append (t 10 t) (flatten (levelOrderToString s))
|
||||||
|
|
||||||
exampleOne = levelOrderTraversal [("1")
|
exampleOne = levelOrderTraversal [("1")
|
||||||
[("2") [("4") t t] t]
|
[("2") [("4") t t] t]
|
||||||
[("3") [("5") t t] [("6") t t]]]
|
[("3") [("5") t t] [("6") t t]]]
|
||||||
|
|
||||||
exampleTwo = levelOrderTraversal [("1")
|
exampleTwo = levelOrderTraversal [("1")
|
||||||
[("2") [("4") [("8") t t] [("9") t t]]
|
[("2") [("4") [("8") t t] [("9") t t]]
|
||||||
[("6") [("10") t t] [("12") t t]]]
|
[("6") [("10") t t] [("12") t t]]]
|
||||||
[("3") [("5") [("11") t t] t] [("7") t t]]]
|
[("3") [("5") [("11") t t] t] [("7") t t]]]
|
||||||
|
37
demos/patternMatching.tri
Normal file
37
demos/patternMatching.tri
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
!import "../lib/patterns.tri" !Local
|
||||||
|
|
||||||
|
-- We can do conditional pattern matching by providing a list of lists, where
|
||||||
|
-- each sublist contains a boolean expression and a function to return if said
|
||||||
|
-- boolean expression evaluates to true.
|
||||||
|
|
||||||
|
value = 42
|
||||||
|
main = match value [[(equal? "Hello") (_ : ", world!")] [(equal? 42) (_ : "The answer.")]]
|
||||||
|
|
||||||
|
-- < main
|
||||||
|
-- > "The answer."
|
||||||
|
|
||||||
|
matchExample = (x : match x
|
||||||
|
[[(equal? 1) (_ : "one")]
|
||||||
|
[(equal? 2) (_ : "two")]
|
||||||
|
[(equal? 3) (_ : "three")]
|
||||||
|
[(equal? 4) (_ : "four")]
|
||||||
|
[(equal? 5) (_ : "five")]
|
||||||
|
[(equal? 6) (_ : "six")]
|
||||||
|
[(equal? 7) (_ : "seven")]
|
||||||
|
[(equal? 8) (_ : "eight")]
|
||||||
|
[(equal? 9) (_ : "nine")]
|
||||||
|
[(equal? 10) (_ : "ten")]
|
||||||
|
[ otherwise (_ : "I ran out of fingers!")]])
|
||||||
|
|
||||||
|
-- < matchExample 3
|
||||||
|
-- > "three"
|
||||||
|
-- < matchExample 5
|
||||||
|
-- > "five"
|
||||||
|
-- < matchExample 9
|
||||||
|
-- > "nine"
|
||||||
|
-- < matchExample 11
|
||||||
|
-- > "I ran out of fingers!"
|
||||||
|
-- < matchExample "three"
|
||||||
|
-- > "I ran out of fingers!"
|
||||||
|
-- < matchExample [("hello") ("world")]
|
||||||
|
-- > "I ran out of fingers!"
|
@ -3,11 +3,9 @@
|
|||||||
|
|
||||||
main = size size
|
main = size size
|
||||||
|
|
||||||
size = (\x :
|
size = x : y (self x : compose succ (triage
|
||||||
(y (\self x :
|
id
|
||||||
compose succ
|
self
|
||||||
(triage
|
(x y : compose (self x) (self y))
|
||||||
(\x : x)
|
x)
|
||||||
self
|
) x 0
|
||||||
(\x y : compose (self x) (self y))
|
|
||||||
x)) x 0))
|
|
||||||
|
@ -18,25 +18,25 @@ main = toSource not?
|
|||||||
sourceLeaf = t (head "t")
|
sourceLeaf = t (head "t")
|
||||||
|
|
||||||
-- Stem case
|
-- Stem case
|
||||||
sourceStem = (\convert : (\a rest :
|
sourceStem = convert : (a rest :
|
||||||
t (head "(") -- Start with a left parenthesis "(".
|
t (head "(") -- Start with a left parenthesis "(".
|
||||||
(t (head "t") -- Add a "t"
|
(t (head "t") -- Add a "t"
|
||||||
(t (head " ") -- Add a space.
|
(t (head " ") -- Add a space.
|
||||||
(convert a -- Recursively convert the argument.
|
(convert a -- Recursively convert the argument.
|
||||||
(t (head ")") rest)))))) -- Close with ")" and append the rest.
|
(t (head ")") rest))))) -- Close with ")" and append the rest.
|
||||||
|
|
||||||
-- Fork case
|
-- Fork case
|
||||||
sourceFork = (\convert : (\a b rest :
|
sourceFork = convert : (a b rest :
|
||||||
t (head "(") -- Start with a left parenthesis "(".
|
t (head "(") -- Start with a left parenthesis "(".
|
||||||
(t (head "t") -- Add a "t"
|
(t (head "t") -- Add a "t"
|
||||||
(t (head " ") -- Add a space.
|
(t (head " ") -- Add a space.
|
||||||
(convert a -- Recursively convert the first arg.
|
(convert a -- Recursively convert the first arg.
|
||||||
(t (head " ") -- Add another space.
|
(t (head " ") -- Add another space.
|
||||||
(convert b -- Recursively convert the second arg.
|
(convert b -- Recursively convert the second arg.
|
||||||
(t (head ")") rest)))))))) -- Close with ")" and append the rest.
|
(t (head ")") rest))))))) -- Close with ")" and append the rest.
|
||||||
|
|
||||||
-- Wrapper around triage
|
-- Wrapper around triage
|
||||||
toSource_ = y (\self arg :
|
toSource_ = y (self arg :
|
||||||
triage
|
triage
|
||||||
sourceLeaf -- `triage` "a" case, Leaf
|
sourceLeaf -- `triage` "a" case, Leaf
|
||||||
(sourceStem self) -- `triage` "b" case, Stem
|
(sourceStem self) -- `triage` "b" case, Stem
|
||||||
@ -44,7 +44,7 @@ toSource_ = y (\self arg :
|
|||||||
arg) -- The term to be inspected
|
arg) -- The term to be inspected
|
||||||
|
|
||||||
-- toSource takes a single TC term and returns a String
|
-- toSource takes a single TC term and returns a String
|
||||||
toSource = \v : toSource_ v ""
|
toSource = v : toSource_ v ""
|
||||||
|
|
||||||
exampleOne = toSource true -- OUT: "(t t)"
|
exampleOne = toSource true -- OUT: "(t t)"
|
||||||
exampleTwo = toSource not? -- OUT: "(t (t (t t) (t t t)) (t t (t t t)))"
|
exampleTwo = toSource not? -- OUT: "(t (t (t t) (t t t)) (t t (t t t)))"
|
||||||
|
64
lib/base.tri
64
lib/base.tri
@ -1,74 +1,74 @@
|
|||||||
false = t
|
false = t
|
||||||
_ = t
|
_ = t
|
||||||
true = t t
|
true = t t
|
||||||
id = \a : a
|
id = a : a
|
||||||
const = \a b : a
|
const = a b : a
|
||||||
pair = t
|
pair = t
|
||||||
if = \cond then else : t (t else (t t then)) t cond
|
if = cond then else : t (t else (t t then)) t cond
|
||||||
|
|
||||||
y = ((\mut wait fun : wait mut (\x : fun (wait mut x)))
|
y = ((mut wait fun : wait mut (x : fun (wait mut x)))
|
||||||
(\x : x x)
|
(x : x x)
|
||||||
(\a0 a1 a2 : t (t a0) (t t a2) a1))
|
(a0 a1 a2 : t (t a0) (t t a2) a1))
|
||||||
|
|
||||||
compose = \f g x : f (g x)
|
compose = f g x : f (g x)
|
||||||
|
|
||||||
triage = \leaf stem fork : t (t leaf stem) fork
|
triage = leaf stem fork : t (t leaf stem) fork
|
||||||
test = triage "Leaf" (\_ : "Stem") (\_ _ : "Fork")
|
test = triage "Leaf" (_ : "Stem") (_ _ : "Fork")
|
||||||
|
|
||||||
matchBool = (\ot of : triage
|
matchBool = (ot of : triage
|
||||||
of
|
of
|
||||||
(\_ : ot)
|
(_ : ot)
|
||||||
(\_ _ : ot)
|
(_ _ : ot)
|
||||||
)
|
)
|
||||||
|
|
||||||
lAnd = (triage
|
lAnd = (triage
|
||||||
(\_ : false)
|
(_ : false)
|
||||||
(\_ x : x)
|
(_ x : x)
|
||||||
(\_ _ x : x))
|
(_ _ x : x))
|
||||||
|
|
||||||
lOr = (triage
|
lOr = (triage
|
||||||
(\x : x)
|
(x : x)
|
||||||
(\_ _ : true)
|
(_ _ : true)
|
||||||
(\_ _ _ : true))
|
(_ _ _ : true))
|
||||||
|
|
||||||
matchPair = \a : triage _ _ a
|
matchPair = a : triage _ _ a
|
||||||
|
|
||||||
not? = matchBool false true
|
not? = matchBool false true
|
||||||
and? = matchBool id (\_ : false)
|
and? = matchBool id (_ : false)
|
||||||
|
|
||||||
or? = (\x z :
|
or? = (x z :
|
||||||
matchBool
|
matchBool
|
||||||
(matchBool true true z)
|
(matchBool true true z)
|
||||||
(matchBool true false z)
|
(matchBool true false z)
|
||||||
x)
|
x)
|
||||||
|
|
||||||
xor? = (\x z :
|
xor? = (x z :
|
||||||
matchBool
|
matchBool
|
||||||
(matchBool false true z)
|
(matchBool false true z)
|
||||||
(matchBool true false z)
|
(matchBool true false z)
|
||||||
x)
|
x)
|
||||||
|
|
||||||
equal? = y (\self : triage
|
equal? = y (self : triage
|
||||||
(triage
|
(triage
|
||||||
true
|
true
|
||||||
(\_ : false)
|
(_ : false)
|
||||||
(\_ _ : false))
|
(_ _ : false))
|
||||||
(\ax :
|
(ax :
|
||||||
triage
|
triage
|
||||||
false
|
false
|
||||||
(self ax)
|
(self ax)
|
||||||
(\_ _ : false))
|
(_ _ : false))
|
||||||
(\ax ay :
|
(ax ay :
|
||||||
triage
|
triage
|
||||||
false
|
false
|
||||||
(\_ : false)
|
(_ : false)
|
||||||
(\bx by : lAnd (self ax bx) (self ay by))))
|
(bx by : lAnd (self ax bx) (self ay by))))
|
||||||
|
|
||||||
succ = y (\self :
|
succ = y (self :
|
||||||
triage
|
triage
|
||||||
1
|
1
|
||||||
t
|
t
|
||||||
(triage
|
(triage
|
||||||
(t (t t))
|
(t (t t))
|
||||||
(\_ tail : t t (self tail))
|
(_ tail : t t (self tail))
|
||||||
t))
|
t))
|
||||||
|
83
lib/list.tri
83
lib/list.tri
@ -1,77 +1,70 @@
|
|||||||
!import "base.tri" !Local
|
!import "base.tri" !Local
|
||||||
|
|
||||||
matchList = \a b : triage a _ b
|
_ = t
|
||||||
|
|
||||||
emptyList? = matchList true (\_ _ : false)
|
matchList = a b : triage a _ b
|
||||||
head = matchList t (\head _ : head)
|
|
||||||
tail = matchList t (\_ tail : tail)
|
|
||||||
|
|
||||||
append = y (\self : matchList
|
emptyList? = matchList true (_ _ : false)
|
||||||
(\k : k)
|
head = matchList t (head _ : head)
|
||||||
(\h r k : pair h (self r k)))
|
tail = matchList t (_ tail : tail)
|
||||||
|
|
||||||
lExist? = y (\self x : matchList
|
append = y (self : matchList
|
||||||
|
(k : k)
|
||||||
|
(h r k : pair h (self r k)))
|
||||||
|
|
||||||
|
lExist? = y (self x : matchList
|
||||||
false
|
false
|
||||||
(\h z : or? (equal? x h) (self x z)))
|
(h z : or? (equal? x h) (self x z)))
|
||||||
|
|
||||||
map_ = y (\self :
|
map_ = y (self :
|
||||||
matchList
|
matchList
|
||||||
(\_ : t)
|
(_ : t)
|
||||||
(\head tail f : pair (f head) (self tail f)))
|
(head tail f : pair (f head) (self tail f)))
|
||||||
map = \f l : map_ l f
|
map = f l : map_ l f
|
||||||
|
|
||||||
filter_ = y (\self : matchList
|
filter_ = y (self : matchList
|
||||||
(\_ : t)
|
(_ : t)
|
||||||
(\head tail f : matchBool (t head) id (f head) (self tail f)))
|
(head tail f : matchBool (t head) id (f head) (self tail f)))
|
||||||
filter = \f l : filter_ l 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_ = 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
|
foldl = f x l : foldl_ f l x
|
||||||
|
|
||||||
foldr_ = y (\self x f l : matchList x (\head tail : f (self x f tail) head) l)
|
foldr_ = y (self x f l : matchList x (head tail : f (self x f tail) head) l)
|
||||||
foldr = \f x l : foldr_ x f l
|
foldr = f x l : foldr_ x f l
|
||||||
|
|
||||||
length = y (\self : matchList
|
length = y (self : matchList
|
||||||
0
|
0
|
||||||
(\_ tail : succ (self tail)))
|
(_ tail : succ (self tail)))
|
||||||
|
|
||||||
reverse = y (\self : matchList
|
reverse = y (self : matchList
|
||||||
t
|
t
|
||||||
(\head tail : append (self tail) (pair head t)))
|
(head tail : append (self tail) (pair head t)))
|
||||||
|
|
||||||
snoc = y (\self x : matchList
|
snoc = y (self x : matchList
|
||||||
(pair x t)
|
(pair x t)
|
||||||
(\h z : pair h (self x z)))
|
(h z : pair h (self x z)))
|
||||||
|
|
||||||
count = y (\self x : matchList
|
count = y (self x : matchList
|
||||||
0
|
0
|
||||||
(\h z : matchBool
|
(h z : matchBool
|
||||||
(succ (self x z))
|
(succ (self x z))
|
||||||
(self x z)
|
(self x z)
|
||||||
(equal? x h)))
|
(equal? x h)))
|
||||||
|
|
||||||
last = y (\self : matchList
|
last = y (self : matchList
|
||||||
t
|
t
|
||||||
(\hd tl : matchBool
|
(hd tl : matchBool
|
||||||
hd
|
hd
|
||||||
(self tl)
|
(self tl)
|
||||||
(emptyList? tl)))
|
(emptyList? tl)))
|
||||||
|
|
||||||
all? = y (\self pred : matchList
|
all? = y (self pred : matchList
|
||||||
true
|
true
|
||||||
(\h z : and? (pred h) (self pred z)))
|
(h z : and? (pred h) (self pred z)))
|
||||||
|
|
||||||
any? = y (\self pred : matchList
|
any? = y (self pred : matchList
|
||||||
false
|
false
|
||||||
(\h z : or? (pred h) (self pred z)))
|
(h z : or? (pred h) (self pred z)))
|
||||||
|
|
||||||
unique_ = y (\self seen : matchList
|
intersect = xs ys : filter (x : lExist? x ys) xs
|
||||||
t
|
|
||||||
(\head rest : matchBool
|
|
||||||
(self seen rest)
|
|
||||||
(pair head (self (pair head seen) rest))
|
|
||||||
(lExist? head seen)))
|
|
||||||
unique = \xs : unique_ t xs
|
|
||||||
|
|
||||||
intersect = \xs ys : filter (\x : lExist? x ys) xs
|
|
||||||
union = \xs ys : unique (append xs ys)
|
|
||||||
|
@ -1,35 +1,24 @@
|
|||||||
|
!import "base.tri" !Local
|
||||||
!import "list.tri" !Local
|
!import "list.tri" !Local
|
||||||
|
|
||||||
match_ = y (\self value patterns :
|
match_ = y (self value patterns :
|
||||||
triage
|
triage
|
||||||
t
|
t
|
||||||
(\_ : t)
|
(_ : t)
|
||||||
(\pattern rest :
|
(pattern rest :
|
||||||
triage
|
triage
|
||||||
t
|
t
|
||||||
(\_ : t)
|
(_ : t)
|
||||||
(\test result :
|
(test result :
|
||||||
if (test value)
|
if (test value)
|
||||||
(result value)
|
(result value)
|
||||||
(self value rest))
|
(self value rest))
|
||||||
pattern)
|
pattern)
|
||||||
patterns)
|
patterns)
|
||||||
|
|
||||||
match = (\value patterns :
|
match = (value patterns :
|
||||||
match_ value (map (\sublist :
|
match_ value (map (sublist :
|
||||||
pair (head sublist) (head (tail sublist)))
|
pair (head sublist) (head (tail sublist)))
|
||||||
patterns))
|
patterns))
|
||||||
|
|
||||||
otherwise = const (t t)
|
otherwise = const (t t)
|
||||||
|
|
||||||
-- matchExample = (\x : match x [[(equal? 1) (\_ : "one")]
|
|
||||||
-- [(equal? 2) (\_ : "two")]
|
|
||||||
-- [(equal? 3) (\_ : "three")]
|
|
||||||
-- [(equal? 4) (\_ : "four")]
|
|
||||||
-- [(equal? 5) (\_ : "five")]
|
|
||||||
-- [(equal? 6) (\_ : "six")]
|
|
||||||
-- [(equal? 7) (\_ : "seven")]
|
|
||||||
-- [(equal? 8) (\_ : "eight")]
|
|
||||||
-- [(equal? 9) (\_ : "nine")]
|
|
||||||
-- [(equal? 10) (\_ : "ten")]
|
|
||||||
-- [ otherwise (\_ : "I ran out of fingers!")]])
|
|
||||||
|
230
src/ContentStore.hs
Normal file
230
src/ContentStore.hs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
module ContentStore where
|
||||||
|
|
||||||
|
import Research
|
||||||
|
import Parser
|
||||||
|
|
||||||
|
import Control.Monad (foldM, forM)
|
||||||
|
import Crypto.Hash (hash, SHA256, Digest)
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.List (intercalate, nub, sortBy, sort)
|
||||||
|
import Data.Maybe (catMaybes)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Database.SQLite.Simple
|
||||||
|
import Database.SQLite.Simple.FromRow (FromRow(..), field)
|
||||||
|
import System.Directory (createDirectoryIfMissing, getXdgDirectory, XdgDirectory(..))
|
||||||
|
import System.FilePath ((</>), takeDirectory)
|
||||||
|
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.Serialize as Cereal
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
data StoredTerm = StoredTerm
|
||||||
|
{ termHash :: Text
|
||||||
|
, termNames :: Text
|
||||||
|
, termData :: ByteString
|
||||||
|
, termMetadata :: Text
|
||||||
|
, termCreatedAt :: Integer
|
||||||
|
, termTags :: Text
|
||||||
|
} deriving (Show)
|
||||||
|
|
||||||
|
instance FromRow StoredTerm where
|
||||||
|
fromRow = StoredTerm <$> field <*> field <*> field <*> field <*> field <*> field
|
||||||
|
|
||||||
|
tryDeserializeTerm :: ByteString -> IO (Maybe T)
|
||||||
|
tryDeserializeTerm bs =
|
||||||
|
case deserializeTerm bs of
|
||||||
|
Right t -> return $ Just t
|
||||||
|
Left err -> do
|
||||||
|
putStrLn $ "Error deserializing term: " ++ err
|
||||||
|
return Nothing
|
||||||
|
|
||||||
|
parseNameList :: Text -> [Text]
|
||||||
|
parseNameList = filter (not . T.null) . T.splitOn ","
|
||||||
|
|
||||||
|
serializeNameList :: [Text] -> Text
|
||||||
|
serializeNameList = T.intercalate "," . nub . sort
|
||||||
|
|
||||||
|
initContentStore :: IO Connection
|
||||||
|
initContentStore = do
|
||||||
|
dbPath <- getContentStorePath
|
||||||
|
createDirectoryIfMissing True (takeDirectory dbPath)
|
||||||
|
conn <- open dbPath
|
||||||
|
execute_ conn "CREATE TABLE IF NOT EXISTS terms (\
|
||||||
|
\hash TEXT PRIMARY KEY, \
|
||||||
|
\names TEXT, \
|
||||||
|
\term_data BLOB, \
|
||||||
|
\metadata TEXT, \
|
||||||
|
\created_at INTEGER DEFAULT (strftime('%s','now')), \
|
||||||
|
\tags TEXT DEFAULT '')"
|
||||||
|
execute_ conn "CREATE INDEX IF NOT EXISTS terms_names_idx ON terms(names)"
|
||||||
|
execute_ conn "CREATE INDEX IF NOT EXISTS terms_tags_idx ON terms(tags)"
|
||||||
|
return conn
|
||||||
|
|
||||||
|
getContentStorePath :: IO FilePath
|
||||||
|
getContentStorePath = do
|
||||||
|
dataDir <- getXdgDirectory XdgData "tricu"
|
||||||
|
return $ dataDir </> "content-store.db"
|
||||||
|
|
||||||
|
instance Cereal.Serialize T where
|
||||||
|
put Leaf = Cereal.putWord8 0
|
||||||
|
put (Stem t) = do
|
||||||
|
Cereal.putWord8 1
|
||||||
|
Cereal.put t
|
||||||
|
put (Fork a b) = do
|
||||||
|
Cereal.putWord8 2
|
||||||
|
Cereal.put a
|
||||||
|
Cereal.put b
|
||||||
|
|
||||||
|
get = do
|
||||||
|
tag <- Cereal.getWord8
|
||||||
|
case tag of
|
||||||
|
0 -> return Leaf
|
||||||
|
1 -> Stem <$> Cereal.get
|
||||||
|
2 -> Fork <$> Cereal.get <*> Cereal.get
|
||||||
|
_ -> fail $ "Invalid tag for T: " ++ show tag
|
||||||
|
|
||||||
|
serializeTerm :: T -> ByteString
|
||||||
|
serializeTerm = LBS.toStrict . Cereal.encodeLazy
|
||||||
|
|
||||||
|
deserializeTerm :: ByteString -> Either String T
|
||||||
|
deserializeTerm = Cereal.decodeLazy . LBS.fromStrict
|
||||||
|
|
||||||
|
hashTerm :: T -> Text
|
||||||
|
hashTerm = T.pack . show . (hash :: ByteString -> Digest SHA256) . serializeTerm
|
||||||
|
|
||||||
|
storeTerm :: Connection -> [String] -> T -> IO Text
|
||||||
|
storeTerm conn newNamesStrList term = do
|
||||||
|
let termBS = serializeTerm term
|
||||||
|
termHashText = hashTerm term
|
||||||
|
newNamesTextList = map T.pack newNamesStrList
|
||||||
|
metadataText = T.pack "{}"
|
||||||
|
|
||||||
|
existingNamesQuery <- query conn
|
||||||
|
"SELECT names FROM terms WHERE hash = ?"
|
||||||
|
(Only termHashText) :: IO [Only Text]
|
||||||
|
|
||||||
|
case existingNamesQuery of
|
||||||
|
[] -> do
|
||||||
|
let allNamesToStore = serializeNameList newNamesTextList
|
||||||
|
execute conn
|
||||||
|
"INSERT INTO terms (hash, names, term_data, metadata, tags) VALUES (?, ?, ?, ?, ?)"
|
||||||
|
(termHashText, allNamesToStore, termBS, metadataText, T.pack "")
|
||||||
|
[(Only currentNamesText)] -> do
|
||||||
|
let currentNamesList = parseNameList currentNamesText
|
||||||
|
let combinedNamesList = currentNamesList ++ newNamesTextList
|
||||||
|
let allNamesToStore = serializeNameList combinedNamesList
|
||||||
|
execute conn
|
||||||
|
"UPDATE terms SET names = ?, metadata = ? WHERE hash = ?"
|
||||||
|
(allNamesToStore, metadataText, termHashText)
|
||||||
|
|
||||||
|
return termHashText
|
||||||
|
|
||||||
|
hashToTerm :: Connection -> Text -> IO (Maybe StoredTerm)
|
||||||
|
hashToTerm conn hashText =
|
||||||
|
queryMaybeOne conn (selectStoredTermFields <> " WHERE hash = ?") (Only hashText)
|
||||||
|
|
||||||
|
nameToTerm :: Connection -> Text -> IO (Maybe StoredTerm)
|
||||||
|
nameToTerm conn nameText =
|
||||||
|
queryMaybeOne conn
|
||||||
|
(selectStoredTermFields <> " WHERE (names = ? OR names LIKE ? OR names LIKE ? OR names LIKE ?) ORDER BY created_at DESC LIMIT 1")
|
||||||
|
(nameText, nameText <> T.pack ",%", T.pack "%," <> nameText <> T.pack ",%", T.pack "%," <> nameText)
|
||||||
|
|
||||||
|
listStoredTerms :: Connection -> IO [StoredTerm]
|
||||||
|
listStoredTerms conn =
|
||||||
|
query_ conn (selectStoredTermFields <> " ORDER BY created_at DESC")
|
||||||
|
|
||||||
|
storeEnvironment :: Connection -> Env -> IO [(String, Text)]
|
||||||
|
storeEnvironment conn env = do
|
||||||
|
let defs = Map.toList $ Map.delete "!result" env
|
||||||
|
let groupedDefs = Map.toList $ Map.fromListWith (++) [(term, [name]) | (name, term) <- defs]
|
||||||
|
|
||||||
|
forM groupedDefs $ \(term, namesList) -> do
|
||||||
|
hashVal <- storeTerm conn namesList term
|
||||||
|
return (head namesList, hashVal)
|
||||||
|
|
||||||
|
loadTerm :: Connection -> String -> IO (Maybe T)
|
||||||
|
loadTerm conn identifier = do
|
||||||
|
result <- getTerm conn (T.pack identifier)
|
||||||
|
case result of
|
||||||
|
Just storedTerm -> tryDeserializeTerm (termData storedTerm)
|
||||||
|
Nothing -> return Nothing
|
||||||
|
|
||||||
|
getTerm :: Connection -> Text -> IO (Maybe StoredTerm)
|
||||||
|
getTerm conn identifier = do
|
||||||
|
if '#' `elem` (T.unpack identifier)
|
||||||
|
then hashToTerm conn (T.pack $ drop 1 (T.unpack identifier))
|
||||||
|
else nameToTerm conn identifier
|
||||||
|
|
||||||
|
loadEnvironment :: Connection -> IO Env
|
||||||
|
loadEnvironment conn = do
|
||||||
|
terms <- listStoredTerms conn
|
||||||
|
foldM addTermToEnv Map.empty terms
|
||||||
|
where
|
||||||
|
addTermToEnv env storedTerm = do
|
||||||
|
maybeT <- tryDeserializeTerm (termData storedTerm)
|
||||||
|
case maybeT of
|
||||||
|
Just t -> do
|
||||||
|
let namesList = parseNameList (termNames storedTerm)
|
||||||
|
return $ foldl (\e name -> Map.insert (T.unpack name) t e) env namesList
|
||||||
|
Nothing -> return env
|
||||||
|
|
||||||
|
termVersions :: Connection -> String -> IO [(Text, T, Integer)]
|
||||||
|
termVersions conn name = do
|
||||||
|
let nameText = T.pack name
|
||||||
|
results <- query conn
|
||||||
|
("SELECT hash, term_data, created_at FROM terms WHERE (names = ? OR names LIKE ? OR names LIKE ? OR names LIKE ?) ORDER BY created_at DESC")
|
||||||
|
(nameText, nameText <> T.pack ",%", T.pack "%," <> nameText <> T.pack ",%", T.pack "%," <> nameText)
|
||||||
|
|
||||||
|
catMaybes <$> mapM (\(hashVal, termDataVal, timestamp) -> do
|
||||||
|
maybeT <- tryDeserializeTerm termDataVal
|
||||||
|
return $ fmap (\t -> (hashVal, t, timestamp)) maybeT
|
||||||
|
) results
|
||||||
|
|
||||||
|
setTag :: Connection -> Text -> Text -> IO ()
|
||||||
|
setTag conn hash tagValue = do
|
||||||
|
exists <- termExists conn hash
|
||||||
|
if exists
|
||||||
|
then do
|
||||||
|
currentTagsQuery <- query conn "SELECT tags FROM terms WHERE hash = ?" (Only hash) :: IO [Only Text]
|
||||||
|
case currentTagsQuery of
|
||||||
|
[Only tagsText] -> do
|
||||||
|
let tagsList = parseNameList tagsText
|
||||||
|
newTagsList = tagValue : tagsList
|
||||||
|
newTags = serializeNameList newTagsList
|
||||||
|
execute conn "UPDATE terms SET tags = ? WHERE hash = ?" (newTags, hash)
|
||||||
|
_ -> putStrLn $ "Term with hash " ++ T.unpack hash ++ " not found (should not happen if exists is true)"
|
||||||
|
else
|
||||||
|
putStrLn $ "Term with hash " ++ T.unpack hash ++ " does not exist"
|
||||||
|
|
||||||
|
termExists :: Connection -> Text -> IO Bool
|
||||||
|
termExists conn hash = do
|
||||||
|
results <- query conn "SELECT 1 FROM terms WHERE hash = ? LIMIT 1" (Only hash) :: IO [[Int]]
|
||||||
|
return $ not (null results)
|
||||||
|
|
||||||
|
termToTags :: Connection -> Text -> IO [Text]
|
||||||
|
termToTags conn hash = do
|
||||||
|
tagsQuery <- query conn "SELECT tags FROM terms WHERE hash = ?" (Only hash) :: IO [Only Text]
|
||||||
|
case tagsQuery of
|
||||||
|
[Only tagsText] -> return $ parseNameList tagsText
|
||||||
|
_ -> return []
|
||||||
|
|
||||||
|
tagToTerm :: Connection -> Text -> IO [StoredTerm]
|
||||||
|
tagToTerm conn tagValue = do
|
||||||
|
let pattern = "%" <> tagValue <> "%"
|
||||||
|
query conn (selectStoredTermFields <> " WHERE tags LIKE ? ORDER BY created_at DESC") (Only pattern)
|
||||||
|
|
||||||
|
allTermTags :: Connection -> IO [StoredTerm]
|
||||||
|
allTermTags conn = do
|
||||||
|
query_ conn (selectStoredTermFields <> " WHERE tags IS NOT NULL AND tags != '' ORDER BY created_at DESC")
|
||||||
|
|
||||||
|
selectStoredTermFields :: Query
|
||||||
|
selectStoredTermFields = "SELECT hash, names, term_data, metadata, created_at, tags FROM terms"
|
||||||
|
|
||||||
|
queryMaybeOne :: (FromRow r, ToRow q) => Connection -> Query -> q -> IO (Maybe r)
|
||||||
|
queryMaybeOne conn qry params = do
|
||||||
|
results <- query conn qry params
|
||||||
|
case results of
|
||||||
|
[row] -> return $ Just row
|
||||||
|
_ -> return Nothing
|
264
src/Eval.hs
264
src/Eval.hs
@ -1,35 +1,42 @@
|
|||||||
module Eval where
|
module Eval where
|
||||||
|
|
||||||
|
import ContentStore
|
||||||
import Parser
|
import Parser
|
||||||
import Research
|
import Research
|
||||||
|
|
||||||
|
import Control.Monad (forM_, foldM)
|
||||||
import Data.List (partition, (\\))
|
import Data.List (partition, (\\))
|
||||||
import Data.Map (Map)
|
import Data.Map (Map)
|
||||||
|
import Database.SQLite.Simple
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Data.List (foldl')
|
||||||
|
|
||||||
evalSingle :: Env -> TricuAST -> Env
|
evalSingle :: Env -> TricuAST -> Env
|
||||||
evalSingle env term
|
evalSingle env term
|
||||||
| SDef name [] body <- term
|
| SDef name [] body <- term
|
||||||
= case Map.lookup name env of
|
= case Map.lookup name env of
|
||||||
Just existingValue
|
Just existingValue
|
||||||
| existingValue == evalAST env body -> env
|
| existingValue == evalASTSync env body -> env
|
||||||
| otherwise -> errorWithoutStackTrace $
|
| otherwise
|
||||||
"Unable to rebind immutable identifier: " ++ name
|
-> let res = evalASTSync env body
|
||||||
Nothing ->
|
in Map.insert "!result" res (Map.insert name res env)
|
||||||
let res = evalAST env body
|
Nothing
|
||||||
in Map.insert "!result" res (Map.insert name res env)
|
-> let res = evalASTSync env body
|
||||||
|
in Map.insert "!result" res (Map.insert name res env)
|
||||||
| SApp func arg <- term
|
| SApp func arg <- term
|
||||||
= let res = apply (evalAST env func) (evalAST env arg)
|
= let res = apply (evalASTSync env func) (evalASTSync env arg)
|
||||||
in Map.insert "!result" res env
|
in Map.insert "!result" res env
|
||||||
| SVar name <- term
|
| SVar name Nothing <- term
|
||||||
= case Map.lookup name env of
|
= case Map.lookup name env of
|
||||||
Just v -> Map.insert "!result" v env
|
Just v -> Map.insert "!result" v env
|
||||||
Nothing ->
|
Nothing -> errorWithoutStackTrace $ "Variable " ++ name ++ " not defined"
|
||||||
errorWithoutStackTrace $ "Variable `" ++ name ++ "` not defined\n\
|
| SVar name (Just hash) <- term
|
||||||
\This error should never occur here. Please report this as an issue."
|
= errorWithoutStackTrace $ "Hash-specific variable lookup not supported in local evaluation: " ++ name ++ "#" ++ hash
|
||||||
| otherwise
|
| otherwise
|
||||||
= Map.insert "!result" (evalAST env term) env
|
= let res = evalASTSync env term
|
||||||
|
in Map.insert "!result" res env
|
||||||
|
|
||||||
evalTricu :: Env -> [TricuAST] -> Env
|
evalTricu :: Env -> [TricuAST] -> Env
|
||||||
evalTricu env x = go env (reorderDefs env x)
|
evalTricu env x = go env (reorderDefs env x)
|
||||||
@ -41,77 +48,156 @@ evalTricu env x = go env (reorderDefs env x)
|
|||||||
go env (x:xs) =
|
go env (x:xs) =
|
||||||
evalTricu (evalSingle env x) xs
|
evalTricu (evalSingle env x) xs
|
||||||
|
|
||||||
evalAST :: Env -> TricuAST -> T
|
evalASTSync :: Env -> TricuAST -> T
|
||||||
evalAST env term
|
evalASTSync env term = case term of
|
||||||
| SLambda _ _ <- term = evalAST env (elimLambda term)
|
SLambda _ _ -> evalASTSync env (elimLambda term)
|
||||||
| SVar name <- term = evalVar name
|
SVar name Nothing -> case Map.lookup name env of
|
||||||
| TLeaf <- term = Leaf
|
Just v -> v
|
||||||
| TStem t <- term = Stem (evalAST env t)
|
Nothing -> errorWithoutStackTrace $ "Variable " ++ name ++ " not defined"
|
||||||
| TFork t u <- term = Fork (evalAST env t) (evalAST env u)
|
SVar name (Just hash) ->
|
||||||
| SApp t u <- term = apply (evalAST env t) (evalAST env u)
|
case Map.lookup (name ++ "#" ++ hash) env of
|
||||||
| SStr s <- term = ofString s
|
Just v -> v
|
||||||
| SInt n <- term = ofNumber n
|
Nothing -> errorWithoutStackTrace $
|
||||||
| SList xs <- term = ofList (map (evalAST env) xs)
|
"Variable " ++ name ++ " with hash " ++ hash ++ " not found in environment"
|
||||||
| SEmpty <- term = Leaf
|
TLeaf -> Leaf
|
||||||
| otherwise = errorWithoutStackTrace "Unexpected AST term"
|
TStem t -> Stem (evalASTSync env t)
|
||||||
where
|
TFork t u -> Fork (evalASTSync env t) (evalASTSync env u)
|
||||||
evalVar name = Map.findWithDefault
|
SApp t u -> apply (evalASTSync env t) (evalASTSync env u)
|
||||||
(errorWithoutStackTrace $ "Variable " ++ name ++ " not defined")
|
SStr s -> ofString s
|
||||||
name env
|
SInt n -> ofNumber n
|
||||||
|
SList xs -> ofList (map (evalASTSync env) xs)
|
||||||
|
SEmpty -> Leaf
|
||||||
|
_ -> errorWithoutStackTrace $ "Unexpected AST term: " ++ show term
|
||||||
|
|
||||||
|
evalAST :: Maybe Connection -> Map.Map String T.Text -> TricuAST -> IO T
|
||||||
|
evalAST mconn selectedVersions ast = do
|
||||||
|
let varNames = collectVarNames ast
|
||||||
|
resolvedEnv <- resolveTermsFromStore mconn selectedVersions varNames
|
||||||
|
return $ evalASTSync resolvedEnv ast
|
||||||
|
|
||||||
|
collectVarNames :: TricuAST -> [(String, Maybe String)]
|
||||||
|
collectVarNames = go []
|
||||||
|
where
|
||||||
|
go acc (SVar name mhash) = (name, mhash) : acc
|
||||||
|
go acc (SApp t u) = go (go acc t) u
|
||||||
|
go acc (SLambda vars body) =
|
||||||
|
let boundVars = Set.fromList vars
|
||||||
|
collected = go [] body
|
||||||
|
in acc ++ filter (\(name, _) -> not $ Set.member name boundVars) collected
|
||||||
|
go acc (TStem t) = go acc t
|
||||||
|
go acc (TFork t u) = go (go acc t) u
|
||||||
|
go acc (SList xs) = foldl' go acc xs
|
||||||
|
go acc _ = acc
|
||||||
|
|
||||||
|
resolveTermsFromStore :: Maybe Connection -> Map.Map String T.Text -> [(String, Maybe String)] -> IO Env
|
||||||
|
resolveTermsFromStore Nothing _ _ = return Map.empty
|
||||||
|
resolveTermsFromStore (Just conn) selectedVersions varNames = do
|
||||||
|
foldM (\env (name, mhash) -> do
|
||||||
|
term <- resolveTermFromStore conn selectedVersions name mhash
|
||||||
|
case term of
|
||||||
|
Just t -> return $ Map.insert (getVarKey name mhash) t env
|
||||||
|
Nothing -> return env
|
||||||
|
) Map.empty varNames
|
||||||
|
where
|
||||||
|
getVarKey name Nothing = name
|
||||||
|
getVarKey name (Just hash) = name ++ "#" ++ hash
|
||||||
|
|
||||||
|
resolveTermFromStore :: Connection -> Map.Map String T.Text -> String -> Maybe String -> IO (Maybe T)
|
||||||
|
resolveTermFromStore conn selectedVersions name mhash = case mhash of
|
||||||
|
Just hashPrefix -> do
|
||||||
|
versions <- termVersions conn name
|
||||||
|
let matchingVersions = filter (\(hash, _, _) ->
|
||||||
|
T.isPrefixOf (T.pack hashPrefix) hash) versions
|
||||||
|
case matchingVersions of
|
||||||
|
[] -> return Nothing
|
||||||
|
[(_, term, _)] -> return $ Just term
|
||||||
|
_ -> return Nothing -- Ambiguous or too many matches
|
||||||
|
Nothing -> case Map.lookup name selectedVersions of
|
||||||
|
Just hash -> do
|
||||||
|
mterm <- hashToTerm conn hash
|
||||||
|
case mterm of
|
||||||
|
Just term -> case deserializeTerm (termData term) of
|
||||||
|
Right t -> return $ Just t
|
||||||
|
Left _ -> return Nothing
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Nothing -> do
|
||||||
|
versions <- termVersions conn name
|
||||||
|
case versions of
|
||||||
|
[] -> return Nothing
|
||||||
|
[(_, term, _)] -> return $ Just term
|
||||||
|
_ -> return $ Just $ (\(_, t, _) -> t) $ head versions
|
||||||
|
|
||||||
elimLambda :: TricuAST -> TricuAST
|
elimLambda :: TricuAST -> TricuAST
|
||||||
elimLambda = go
|
elimLambda = go
|
||||||
where
|
where
|
||||||
-- η-reduction
|
go term
|
||||||
go (SLambda [v] (SApp f (SVar x)))
|
| etaReduction term = elimLambda $ etaReduceResult term
|
||||||
| v == x && not (isFree v f) = elimLambda f
|
| triagePattern term = _TRI
|
||||||
-- Triage optimization
|
| composePattern term = _B
|
||||||
go (SLambda [a] (SLambda [b] (SLambda [c] body)))
|
| lambdaList term = elimLambda $ lambdaListResult term
|
||||||
| body == triageBody = _TRIAGE
|
| nestedLambda term = nestedLambdaResult term
|
||||||
where
|
| application term = applicationResult term
|
||||||
triageBody =
|
| isSList term = slistTransform term
|
||||||
SApp (SApp TLeaf (SApp (SApp TLeaf (SVar a)) (SVar b))) (SVar c)
|
| otherwise = term
|
||||||
-- Composition optimization
|
|
||||||
go (SLambda [f] (SLambda [g] (SLambda [x] body)))
|
|
||||||
| body == SApp (SVar f) (SApp (SVar g) (SVar x)) = _B
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
toSKI x (SVar y)
|
etaReduction (SLambda [v] (SApp f (SVar x Nothing))) = v == x && not (isFree v f)
|
||||||
| x == y = _I
|
etaReduction _ = False
|
||||||
| otherwise = SApp _K (SVar y)
|
etaReduceResult (SLambda [_] (SApp f _)) = f
|
||||||
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"
|
|
||||||
|
|
||||||
_S = parseSingle "t (t (t t t)) t"
|
triagePattern (SLambda [a] (SLambda [b] (SLambda [c] body))) = body == triageBody a b c
|
||||||
_K = parseSingle "t t"
|
triagePattern _ = False
|
||||||
_I = parseSingle "t (t (t t)) t"
|
|
||||||
_B = parseSingle "t (t (t t (t (t (t t t)) t))) (t t)"
|
composePattern (SLambda [f] (SLambda [g] (SLambda [x] body))) = body == composeBody f g x
|
||||||
_TRIAGE = parseSingle "t (t (t t (t (t (t t t))))) t"
|
composePattern _ = False
|
||||||
|
|
||||||
|
lambdaList (SLambda [_] (SList _)) = True
|
||||||
|
lambdaList _ = False
|
||||||
|
lambdaListResult (SLambda [v] (SList xs)) = SLambda [v] (foldr wrapTLeaf TLeaf xs)
|
||||||
|
wrapTLeaf m r = SApp (SApp TLeaf m) r
|
||||||
|
|
||||||
|
nestedLambda (SLambda (_:_) _) = True
|
||||||
|
nestedLambda _ = False
|
||||||
|
nestedLambdaResult (SLambda (v:vs) body)
|
||||||
|
| null vs = toSKI v (go body) -- Changed elimLambda to go
|
||||||
|
| otherwise = go (SLambda [v] (SLambda vs body)) -- Changed elimLambda to go
|
||||||
|
|
||||||
|
application (SApp _ _) = True
|
||||||
|
application _ = False
|
||||||
|
applicationResult (SApp f g) = SApp (go f) (go g) -- Changed elimLambda to go
|
||||||
|
|
||||||
|
isSList (SList _) = True
|
||||||
|
isSList _ = False
|
||||||
|
|
||||||
|
slistTransform :: TricuAST -> TricuAST
|
||||||
|
slistTransform (SList xs) = foldr (\m r -> SApp (SApp TLeaf (go m)) r) TLeaf xs
|
||||||
|
slistTransform ast = ast -- Should not be reached if isSList is the guard
|
||||||
|
|
||||||
|
toSKI x (SVar y Nothing)
|
||||||
|
| x == y = _I
|
||||||
|
| otherwise = SApp _K (SVar y Nothing)
|
||||||
|
toSKI x (SApp m n) = SApp (SApp _S (toSKI x m)) (toSKI x n)
|
||||||
|
toSKI x (SLambda [y] body) = toSKI x (toSKI y body) -- This should ideally not happen if lambdas are fully eliminated first
|
||||||
|
toSKI _ sl@(SList _) = SApp _K (go sl) -- Ensure SList itself is transformed if somehow passed to toSKI directly
|
||||||
|
toSKI _ term = SApp _K term
|
||||||
|
|
||||||
|
_S = parseSingle "t (t (t t t)) t"
|
||||||
|
_K = parseSingle "t t"
|
||||||
|
_I = parseSingle "t (t (t t)) t"
|
||||||
|
_B = parseSingle "t (t (t t (t (t (t t t)) t))) (t t)"
|
||||||
|
_TRI = parseSingle "t (t (t t (t (t (t t t))))) t"
|
||||||
|
|
||||||
|
triageBody a b c = SApp (SApp TLeaf (SApp (SApp TLeaf (SVar a Nothing)) (SVar b Nothing))) (SVar c Nothing)
|
||||||
|
composeBody f g x = SApp (SVar f Nothing) (SVar g Nothing) -- Note: This might not be the standard B combinator body f(g x)
|
||||||
|
|
||||||
isFree :: String -> TricuAST -> Bool
|
isFree :: String -> TricuAST -> Bool
|
||||||
isFree x = Set.member x . freeVars
|
isFree x = Set.member x . freeVars
|
||||||
|
|
||||||
freeVars :: TricuAST -> Set.Set String
|
freeVars :: TricuAST -> Set.Set String
|
||||||
freeVars (SVar v ) = Set.singleton v
|
freeVars (SVar v Nothing) = Set.singleton v
|
||||||
freeVars (SInt _ ) = Set.empty
|
freeVars (SVar v (Just _)) = Set.singleton v
|
||||||
freeVars (SStr _ ) = Set.empty
|
freeVars (SApp t u) = Set.union (freeVars t) (freeVars u)
|
||||||
freeVars (SList s ) = foldMap freeVars s
|
freeVars (SLambda vs body) = Set.difference (freeVars body) (Set.fromList vs)
|
||||||
freeVars (SApp f a ) = freeVars f <> freeVars a
|
freeVars _ = Set.empty
|
||||||
freeVars TLeaf = Set.empty
|
|
||||||
freeVars (SDef _ _ b) = freeVars b
|
|
||||||
freeVars (TStem t ) = freeVars t
|
|
||||||
freeVars (TFork l r ) = freeVars l <> freeVars r
|
|
||||||
freeVars (SLambda v b ) = foldr Set.delete (freeVars b) v
|
|
||||||
freeVars _ = Set.empty
|
|
||||||
|
|
||||||
reorderDefs :: Env -> [TricuAST] -> [TricuAST]
|
reorderDefs :: Env -> [TricuAST] -> [TricuAST]
|
||||||
reorderDefs env defs
|
reorderDefs env defs
|
||||||
@ -128,7 +214,7 @@ reorderDefs env defs
|
|||||||
graph = buildDepGraph defsOnly
|
graph = buildDepGraph defsOnly
|
||||||
sortedDefs = sortDeps graph
|
sortedDefs = sortDeps graph
|
||||||
defMap = Map.fromList [(name, def) | def@(SDef name _ _) <- defsOnly]
|
defMap = Map.fromList [(name, def) | def@(SDef name _ _) <- defsOnly]
|
||||||
orderedDefs = map (\name -> defMap Map.! name) sortedDefs
|
orderedDefs = map (defMap Map.!) sortedDefs
|
||||||
|
|
||||||
freeVarsDefs = foldMap snd defsWithFreeVars
|
freeVarsDefs = foldMap snd defsWithFreeVars
|
||||||
freeVarsOthers = foldMap freeVars others
|
freeVarsOthers = foldMap freeVars others
|
||||||
@ -136,8 +222,8 @@ reorderDefs env defs
|
|||||||
validNames = Set.fromList defNames `Set.union` Set.fromList (Map.keys env)
|
validNames = Set.fromList defNames `Set.union` Set.fromList (Map.keys env)
|
||||||
missingDeps = Set.toList (allFreeVars `Set.difference` validNames)
|
missingDeps = Set.toList (allFreeVars `Set.difference` validNames)
|
||||||
|
|
||||||
isDef (SDef _ _ _) = True
|
isDef SDef {} = True
|
||||||
isDef _ = False
|
isDef _ = False
|
||||||
|
|
||||||
buildDepGraph :: [TricuAST] -> Map.Map String (Set.Set String)
|
buildDepGraph :: [TricuAST] -> Map.Map String (Set.Set String)
|
||||||
buildDepGraph topDefs
|
buildDepGraph topDefs
|
||||||
@ -192,3 +278,27 @@ mainResult :: Env -> T
|
|||||||
mainResult r = case Map.lookup "main" r of
|
mainResult r = case Map.lookup "main" r of
|
||||||
Just a -> a
|
Just a -> a
|
||||||
Nothing -> errorWithoutStackTrace "No valid definition for `main` found."
|
Nothing -> errorWithoutStackTrace "No valid definition for `main` found."
|
||||||
|
|
||||||
|
evalWithEnv :: Env -> Maybe Connection -> Map.Map String T.Text -> TricuAST -> IO T
|
||||||
|
evalWithEnv env mconn selectedVersions ast = do
|
||||||
|
let varNames = findVarNames ast
|
||||||
|
resolvedEnv <- case mconn of
|
||||||
|
Just conn -> foldM (\e name ->
|
||||||
|
if Map.member name e
|
||||||
|
then return e
|
||||||
|
else do
|
||||||
|
mterm <- resolveTermFromStore conn selectedVersions name Nothing
|
||||||
|
case mterm of
|
||||||
|
Just term -> return $ Map.insert name term e
|
||||||
|
Nothing -> return e
|
||||||
|
) env varNames
|
||||||
|
Nothing -> return env
|
||||||
|
return $ evalASTSync resolvedEnv ast
|
||||||
|
|
||||||
|
findVarNames :: TricuAST -> [String]
|
||||||
|
findVarNames ast = case ast of
|
||||||
|
SVar name _ -> [name]
|
||||||
|
SApp a b -> findVarNames a ++ findVarNames b
|
||||||
|
SLambda args body -> findVarNames body \\ args
|
||||||
|
SDef name args body -> name : (findVarNames body \\ args)
|
||||||
|
_ -> []
|
||||||
|
@ -109,9 +109,9 @@ nsDefinition moduleName other =
|
|||||||
nsBody moduleName other
|
nsBody moduleName other
|
||||||
|
|
||||||
nsBody :: String -> TricuAST -> TricuAST
|
nsBody :: String -> TricuAST -> TricuAST
|
||||||
nsBody moduleName (SVar name)
|
nsBody moduleName (SVar name mhash)
|
||||||
| isPrefixed name = SVar name
|
| isPrefixed name = SVar name mhash
|
||||||
| otherwise = SVar (nsVariable moduleName name)
|
| otherwise = SVar (nsVariable moduleName name) mhash
|
||||||
nsBody moduleName (SApp func arg) =
|
nsBody moduleName (SApp func arg) =
|
||||||
SApp (nsBody moduleName func) (nsBody moduleName arg)
|
SApp (nsBody moduleName func) (nsBody moduleName arg)
|
||||||
nsBody moduleName (SLambda args body) =
|
nsBody moduleName (SLambda args body) =
|
||||||
@ -122,18 +122,16 @@ nsBody moduleName (TFork left right) =
|
|||||||
TFork (nsBody moduleName left) (nsBody moduleName right)
|
TFork (nsBody moduleName left) (nsBody moduleName right)
|
||||||
nsBody moduleName (TStem subtree) =
|
nsBody moduleName (TStem subtree) =
|
||||||
TStem (nsBody moduleName subtree)
|
TStem (nsBody moduleName subtree)
|
||||||
nsBody moduleName (SDef name args body)
|
nsBody moduleName (SDef name args body) =
|
||||||
| isPrefixed name = SDef name args (nsBody moduleName body)
|
SDef (nsVariable moduleName name) args (nsBodyScoped moduleName args body)
|
||||||
| otherwise = SDef (nsVariable moduleName name)
|
|
||||||
args (nsBody moduleName body)
|
|
||||||
nsBody _ other = other
|
nsBody _ other = other
|
||||||
|
|
||||||
nsBodyScoped :: String -> [String] -> TricuAST -> TricuAST
|
nsBodyScoped :: String -> [String] -> TricuAST -> TricuAST
|
||||||
nsBodyScoped moduleName args body = case body of
|
nsBodyScoped moduleName args body = case body of
|
||||||
SVar name ->
|
SVar name mhash ->
|
||||||
if name `elem` args
|
if name `elem` args
|
||||||
then SVar name
|
then SVar name mhash
|
||||||
else nsBody moduleName (SVar name)
|
else nsBody moduleName (SVar name mhash)
|
||||||
SApp func arg ->
|
SApp func arg ->
|
||||||
SApp (nsBodyScoped moduleName args func) (nsBodyScoped moduleName args arg)
|
SApp (nsBodyScoped moduleName args func) (nsBodyScoped moduleName args arg)
|
||||||
SLambda innerArgs innerBody ->
|
SLambda innerArgs innerBody ->
|
||||||
@ -141,13 +139,11 @@ nsBodyScoped moduleName args body = case body of
|
|||||||
SList items ->
|
SList items ->
|
||||||
SList (map (nsBodyScoped moduleName args) items)
|
SList (map (nsBodyScoped moduleName args) items)
|
||||||
TFork left right ->
|
TFork left right ->
|
||||||
TFork (nsBodyScoped moduleName args left)
|
TFork (nsBodyScoped moduleName args left) (nsBodyScoped moduleName args right)
|
||||||
(nsBodyScoped moduleName args right)
|
|
||||||
TStem subtree ->
|
TStem subtree ->
|
||||||
TStem (nsBodyScoped moduleName args subtree)
|
TStem (nsBodyScoped moduleName args subtree)
|
||||||
SDef name innerArgs innerBody ->
|
SDef name innerArgs innerBody ->
|
||||||
SDef (nsVariable moduleName name) innerArgs
|
SDef (nsVariable moduleName name) innerArgs (nsBodyScoped moduleName (args ++ innerArgs) innerBody)
|
||||||
(nsBodyScoped moduleName (args ++ innerArgs) innerBody)
|
|
||||||
other -> other
|
other -> other
|
||||||
|
|
||||||
isPrefixed :: String -> Bool
|
isPrefixed :: String -> Bool
|
||||||
|
68
src/Lexer.hs
68
src/Lexer.hs
@ -3,6 +3,7 @@ module Lexer where
|
|||||||
import Research
|
import Research
|
||||||
|
|
||||||
import Control.Monad (void)
|
import Control.Monad (void)
|
||||||
|
import Data.Functor (($>))
|
||||||
import Data.Void
|
import Data.Void
|
||||||
import Text.Megaparsec
|
import Text.Megaparsec
|
||||||
import Text.Megaparsec.Char hiding (space)
|
import Text.Megaparsec.Char hiding (space)
|
||||||
@ -34,13 +35,13 @@ tricuLexer = do
|
|||||||
[ try lnewline
|
[ try lnewline
|
||||||
, try namespace
|
, try namespace
|
||||||
, try dot
|
, try dot
|
||||||
|
, try identifierWithHash
|
||||||
, try identifier
|
, try identifier
|
||||||
, try keywordT
|
, try keywordT
|
||||||
, try integerLiteral
|
, try integerLiteral
|
||||||
, try stringLiteral
|
, try stringLiteral
|
||||||
, assign
|
, assign
|
||||||
, colon
|
, colon
|
||||||
, backslash
|
|
||||||
, openParen
|
, openParen
|
||||||
, closeParen
|
, closeParen
|
||||||
, openBracket
|
, openBracket
|
||||||
@ -54,16 +55,37 @@ lexTricu input = case runParser tricuLexer "" input of
|
|||||||
|
|
||||||
|
|
||||||
keywordT :: Lexer LToken
|
keywordT :: Lexer LToken
|
||||||
keywordT = string "t" *> notFollowedBy alphaNumChar *> pure LKeywordT
|
keywordT = string "t" *> notFollowedBy alphaNumChar $> LKeywordT
|
||||||
|
|
||||||
|
identifierWithHash :: Lexer LToken
|
||||||
|
identifierWithHash = do
|
||||||
|
first <- lowerChar <|> char '_'
|
||||||
|
rest <- many $ letterChar
|
||||||
|
<|> digitChar <|> char '_' <|> char '-' <|> char '?'
|
||||||
|
<|> char '$' <|> char '@' <|> char '%'
|
||||||
|
_ <- char '#' -- Consume '#'
|
||||||
|
hashString <- some (alphaNumChar <|> char '-') -- Ensures at least one char for hash
|
||||||
|
<?> "hash characters (alphanumeric or hyphen)"
|
||||||
|
|
||||||
|
let name = first : rest
|
||||||
|
let hashLen = length hashString
|
||||||
|
if name == "t" || name == "!result"
|
||||||
|
then fail "Keywords (`t`, `!result`) cannot be used with a hash suffix."
|
||||||
|
else if hashLen < 16 then
|
||||||
|
fail $ "Hash suffix for '" ++ name ++ "' must be at least 16 characters long. Got " ++ show hashLen ++ " ('" ++ hashString ++ "')."
|
||||||
|
else if hashLen > 64 then -- Assuming SHA256, max 64
|
||||||
|
fail $ "Hash suffix for '" ++ name ++ "' cannot be longer than 64 characters (SHA256). Got " ++ show hashLen ++ " ('" ++ hashString ++ "')."
|
||||||
|
else
|
||||||
|
return (LIdentifierWithHash name hashString)
|
||||||
|
|
||||||
identifier :: Lexer LToken
|
identifier :: Lexer LToken
|
||||||
identifier = do
|
identifier = do
|
||||||
first <- lowerChar <|> char '_'
|
first <- lowerChar <|> char '_'
|
||||||
rest <- many $ letterChar
|
rest <- many $ letterChar
|
||||||
<|> digitChar <|> char '_' <|> char '-' <|> char '?'
|
<|> digitChar <|> char '_' <|> char '-' <|> char '?'
|
||||||
<|> char '$' <|> char '#' <|> char '@' <|> char '%'
|
<|> char '$' <|> char '@' <|> char '%'
|
||||||
let name = first : rest
|
let name = first : rest
|
||||||
if (name == "t" || name == "!result")
|
if name == "t" || name == "!result"
|
||||||
then fail "Keywords (`t`, `!result`) cannot be used as an identifier"
|
then fail "Keywords (`t`, `!result`) cannot be used as an identifier"
|
||||||
else return (LIdentifier name)
|
else return (LIdentifier name)
|
||||||
|
|
||||||
@ -76,7 +98,7 @@ namespace = do
|
|||||||
return (LNamespace name)
|
return (LNamespace name)
|
||||||
|
|
||||||
dot :: Lexer LToken
|
dot :: Lexer LToken
|
||||||
dot = char '.' *> pure LDot
|
dot = char '.' $> LDot
|
||||||
|
|
||||||
lImport :: Lexer LToken
|
lImport :: Lexer LToken
|
||||||
lImport = do
|
lImport = do
|
||||||
@ -88,28 +110,25 @@ lImport = do
|
|||||||
return (LImport path name)
|
return (LImport path name)
|
||||||
|
|
||||||
assign :: Lexer LToken
|
assign :: Lexer LToken
|
||||||
assign = char '=' *> pure LAssign
|
assign = char '=' $> LAssign
|
||||||
|
|
||||||
colon :: Lexer LToken
|
colon :: Lexer LToken
|
||||||
colon = char ':' *> pure LColon
|
colon = char ':' $> LColon
|
||||||
|
|
||||||
backslash :: Lexer LToken
|
|
||||||
backslash = char '\\' *> pure LBackslash
|
|
||||||
|
|
||||||
openParen :: Lexer LToken
|
openParen :: Lexer LToken
|
||||||
openParen = char '(' *> pure LOpenParen
|
openParen = char '(' $> LOpenParen
|
||||||
|
|
||||||
closeParen :: Lexer LToken
|
closeParen :: Lexer LToken
|
||||||
closeParen = char ')' *> pure LCloseParen
|
closeParen = char ')' $> LCloseParen
|
||||||
|
|
||||||
openBracket :: Lexer LToken
|
openBracket :: Lexer LToken
|
||||||
openBracket = char '[' *> pure LOpenBracket
|
openBracket = char '[' $> LOpenBracket
|
||||||
|
|
||||||
closeBracket :: Lexer LToken
|
closeBracket :: Lexer LToken
|
||||||
closeBracket = char ']' *> pure LCloseBracket
|
closeBracket = char ']' $> LCloseBracket
|
||||||
|
|
||||||
lnewline :: Lexer LToken
|
lnewline :: Lexer LToken
|
||||||
lnewline = char '\n' *> pure LNewline
|
lnewline = char '\n' $> LNewline
|
||||||
|
|
||||||
sc :: Lexer ()
|
sc :: Lexer ()
|
||||||
sc = space
|
sc = space
|
||||||
@ -125,7 +144,22 @@ integerLiteral = do
|
|||||||
stringLiteral :: Lexer LToken
|
stringLiteral :: Lexer LToken
|
||||||
stringLiteral = do
|
stringLiteral = do
|
||||||
char '"'
|
char '"'
|
||||||
content <- many (noneOf ['"'])
|
content <- manyTill Lexer.charLiteral (char '"')
|
||||||
char '"' --"
|
|
||||||
return (LStringLiteral content)
|
return (LStringLiteral content)
|
||||||
|
|
||||||
|
charLiteral :: Lexer Char
|
||||||
|
charLiteral = escapedChar <|> normalChar
|
||||||
|
where
|
||||||
|
normalChar = noneOf ['"', '\\']
|
||||||
|
escapedChar = do
|
||||||
|
void $ char '\\'
|
||||||
|
c <- oneOf ['n', 't', 'r', 'f', 'b', '\\', '"', '\'']
|
||||||
|
return $ case c of
|
||||||
|
'n' -> '\n'
|
||||||
|
't' -> '\t'
|
||||||
|
'r' -> '\r'
|
||||||
|
'f' -> '\f'
|
||||||
|
'b' -> '\b'
|
||||||
|
'\\' -> '\\'
|
||||||
|
'"' -> '"'
|
||||||
|
'\'' -> '\''
|
||||||
|
53
src/Main.hs
53
src/Main.hs
@ -5,6 +5,7 @@ import FileEval
|
|||||||
import Parser (parseTricu)
|
import Parser (parseTricu)
|
||||||
import REPL
|
import REPL
|
||||||
import Research
|
import Research
|
||||||
|
import ContentStore
|
||||||
|
|
||||||
import Control.Monad (foldM)
|
import Control.Monad (foldM)
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
@ -63,18 +64,16 @@ main = do
|
|||||||
case args of
|
case args of
|
||||||
Repl -> do
|
Repl -> do
|
||||||
putStrLn "Welcome to the tricu REPL"
|
putStrLn "Welcome to the tricu REPL"
|
||||||
putStrLn "You can exit with `CTRL+D` or the `!exit` command.`"
|
putStrLn "You may exit with `CTRL+D` or the `!exit` command."
|
||||||
repl Map.empty
|
repl
|
||||||
Evaluate { file = filePaths, form = form } -> do
|
Evaluate { file = filePaths, form = form } -> do
|
||||||
result <- case filePaths of
|
result <- case filePaths of
|
||||||
[] -> do
|
[] -> runTricuT <$> getContents
|
||||||
t <- getContents
|
|
||||||
pure $ runTricu t
|
|
||||||
(filePath:restFilePaths) -> do
|
(filePath:restFilePaths) -> do
|
||||||
initialEnv <- evaluateFile filePath
|
initialEnv <- evaluateFile filePath
|
||||||
finalEnv <- foldM evaluateFileWithContext initialEnv restFilePaths
|
finalEnv <- foldM evaluateFileWithContext initialEnv restFilePaths
|
||||||
pure $ mainResult finalEnv
|
pure $ mainResult finalEnv
|
||||||
let fRes = formatResult form result
|
let fRes = formatT form result
|
||||||
putStr fRes
|
putStr fRes
|
||||||
TDecode { file = filePaths } -> do
|
TDecode { file = filePaths } -> do
|
||||||
value <- case filePaths of
|
value <- case filePaths of
|
||||||
@ -82,8 +81,46 @@ main = do
|
|||||||
(filePath:_) -> readFile filePath
|
(filePath:_) -> readFile filePath
|
||||||
putStrLn $ decodeResult $ result $ evalTricu Map.empty $ parseTricu value
|
putStrLn $ decodeResult $ result $ evalTricu Map.empty $ parseTricu value
|
||||||
|
|
||||||
runTricu :: String -> T
|
runTricu :: String -> String
|
||||||
runTricu input =
|
runTricu = formatT TreeCalculus . runTricuT
|
||||||
|
|
||||||
|
runTricuT :: String -> T
|
||||||
|
runTricuT input =
|
||||||
let asts = parseTricu input
|
let asts = parseTricu input
|
||||||
finalEnv = evalTricu Map.empty asts
|
finalEnv = evalTricu Map.empty asts
|
||||||
in result finalEnv
|
in result finalEnv
|
||||||
|
|
||||||
|
runTricuEnv :: Env -> String -> String
|
||||||
|
runTricuEnv env = formatT TreeCalculus . runTricuTEnv env
|
||||||
|
|
||||||
|
runTricuTEnv :: Env -> String -> T
|
||||||
|
runTricuTEnv env input =
|
||||||
|
let asts = parseTricu input
|
||||||
|
finalEnv = evalTricu env asts
|
||||||
|
in result finalEnv
|
||||||
|
|
||||||
|
runTricuWithEnvT :: String -> (Env, T)
|
||||||
|
runTricuWithEnvT input =
|
||||||
|
let asts = parseTricu input
|
||||||
|
finalEnv = evalTricu Map.empty asts
|
||||||
|
in (finalEnv, result finalEnv)
|
||||||
|
|
||||||
|
runTricuWithEnv :: String -> (Env, String)
|
||||||
|
runTricuWithEnv input =
|
||||||
|
let asts = parseTricu input
|
||||||
|
finalEnv = evalTricu Map.empty asts
|
||||||
|
res = result finalEnv
|
||||||
|
in (finalEnv, formatT TreeCalculus res)
|
||||||
|
|
||||||
|
runTricuEnvWithEnvT :: Env -> String -> (Env, T)
|
||||||
|
runTricuEnvWithEnvT env input =
|
||||||
|
let asts = parseTricu input
|
||||||
|
finalEnv = evalTricu env asts
|
||||||
|
in (finalEnv, result finalEnv)
|
||||||
|
|
||||||
|
runTricuEnvWithEnv :: Env -> String -> (Env, String)
|
||||||
|
runTricuEnvWithEnv env input =
|
||||||
|
let asts = parseTricu input
|
||||||
|
finalEnv = evalTricu env asts
|
||||||
|
res = result finalEnv
|
||||||
|
in (finalEnv, formatT TreeCalculus res)
|
||||||
|
@ -3,12 +3,12 @@ module Parser where
|
|||||||
import Lexer
|
import Lexer
|
||||||
import Research
|
import Research
|
||||||
|
|
||||||
import Control.Monad (void)
|
import Control.Monad (void)
|
||||||
import Control.Monad.State
|
import Control.Monad.State
|
||||||
import Data.List.NonEmpty (toList)
|
import Data.List.NonEmpty (toList)
|
||||||
import Data.Void (Void)
|
import Data.Void (Void)
|
||||||
import Text.Megaparsec
|
import Text.Megaparsec
|
||||||
import Text.Megaparsec.Error (ParseErrorBundle, errorBundlePretty)
|
import Text.Megaparsec.Error (ParseErrorBundle, errorBundlePretty)
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
data PState = PState
|
data PState = PState
|
||||||
@ -130,7 +130,6 @@ parseFunctionM = do
|
|||||||
parseLambdaM :: ParserM TricuAST
|
parseLambdaM :: ParserM TricuAST
|
||||||
parseLambdaM = do
|
parseLambdaM = do
|
||||||
let ident = (\case LIdentifier _ -> True; _ -> False)
|
let ident = (\case LIdentifier _ -> True; _ -> False)
|
||||||
_ <- satisfyM (== LBackslash)
|
|
||||||
params <- some (satisfyM ident)
|
params <- some (satisfyM ident)
|
||||||
_ <- satisfyM (== LColon)
|
_ <- satisfyM (== LColon)
|
||||||
scnParserM
|
scnParserM
|
||||||
@ -145,11 +144,11 @@ parseLambdaExpressionM = choice
|
|||||||
|
|
||||||
parseAtomicLambdaM :: ParserM TricuAST
|
parseAtomicLambdaM :: ParserM TricuAST
|
||||||
parseAtomicLambdaM = choice
|
parseAtomicLambdaM = choice
|
||||||
[ parseVarM
|
[ try parseLambdaM
|
||||||
|
, parseVarM
|
||||||
, parseTreeLeafM
|
, parseTreeLeafM
|
||||||
, parseLiteralM
|
, parseLiteralM
|
||||||
, parseListLiteralM
|
, parseListLiteralM
|
||||||
, try parseLambdaM
|
|
||||||
, between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) parseLambdaExpressionM
|
, between (satisfyM (== LOpenParen)) (satisfyM (== LCloseParen)) parseLambdaExpressionM
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -205,7 +204,8 @@ parseTreeLeafOrParenthesizedM = choice
|
|||||||
|
|
||||||
parseAtomicM :: ParserM TricuAST
|
parseAtomicM :: ParserM TricuAST
|
||||||
parseAtomicM = choice
|
parseAtomicM = choice
|
||||||
[ parseVarM
|
[ try parseLambdaM
|
||||||
|
, parseVarM
|
||||||
, parseTreeLeafM
|
, parseTreeLeafM
|
||||||
, parseListLiteralM
|
, parseListLiteralM
|
||||||
, parseGroupedM
|
, parseGroupedM
|
||||||
@ -249,7 +249,7 @@ parseGroupedItemM = do
|
|||||||
parseSingleItemM :: ParserM TricuAST
|
parseSingleItemM :: ParserM TricuAST
|
||||||
parseSingleItemM = do
|
parseSingleItemM = do
|
||||||
token <- satisfyM (\case LIdentifier _ -> True; LKeywordT -> True; _ -> False)
|
token <- satisfyM (\case LIdentifier _ -> True; LKeywordT -> True; _ -> False)
|
||||||
if | LIdentifier name <- token -> pure (SVar name)
|
if | LIdentifier name <- token -> pure (SVar name Nothing)
|
||||||
| token == LKeywordT -> pure TLeaf
|
| token == LKeywordT -> pure TLeaf
|
||||||
| otherwise -> fail "Unexpected token in list item"
|
| otherwise -> fail "Unexpected token in list item"
|
||||||
|
|
||||||
@ -258,16 +258,25 @@ parseVarM = do
|
|||||||
token <- satisfyM (\case
|
token <- satisfyM (\case
|
||||||
LNamespace _ -> True
|
LNamespace _ -> True
|
||||||
LIdentifier _ -> True
|
LIdentifier _ -> True
|
||||||
|
LIdentifierWithHash _ _ -> True
|
||||||
_ -> False)
|
_ -> False)
|
||||||
|
|
||||||
case token of
|
case token of
|
||||||
LNamespace ns -> do
|
LNamespace ns -> do
|
||||||
_ <- satisfyM (== LDot)
|
_ <- satisfyM (== LDot)
|
||||||
LIdentifier name <- satisfyM (\case LIdentifier _ -> True; _ -> False)
|
LIdentifier name <- satisfyM (\case LIdentifier _ -> True; _ -> False)
|
||||||
pure $ SVar (ns ++ "." ++ name)
|
pure $ SVar (ns ++ "." ++ name) Nothing
|
||||||
|
|
||||||
LIdentifier name
|
LIdentifier name
|
||||||
| name == "t" || name == "!result" ->
|
| name == "t" || name == "!result" ->
|
||||||
fail ("Reserved keyword: " ++ name ++ " cannot be assigned.")
|
fail ("Reserved keyword: " ++ name ++ " cannot be assigned.")
|
||||||
| otherwise -> pure (SVar name)
|
| otherwise -> pure (SVar name Nothing)
|
||||||
|
|
||||||
|
LIdentifierWithHash name hash ->
|
||||||
|
if name == "t" || name == "!result"
|
||||||
|
then fail ("Reserved keyword: " ++ name ++ " cannot be assigned.")
|
||||||
|
else pure (SVar name (Just hash))
|
||||||
|
|
||||||
_ -> fail "Unexpected token while parsing variable"
|
_ -> fail "Unexpected token while parsing variable"
|
||||||
|
|
||||||
parseIntLiteralM :: ParserM TricuAST
|
parseIntLiteralM :: ParserM TricuAST
|
||||||
@ -275,7 +284,7 @@ parseIntLiteralM = do
|
|||||||
let intL = (\case LIntegerLiteral _ -> True; _ -> False)
|
let intL = (\case LIntegerLiteral _ -> True; _ -> False)
|
||||||
token <- satisfyM intL
|
token <- satisfyM intL
|
||||||
if | LIntegerLiteral value <- token ->
|
if | LIntegerLiteral value <- token ->
|
||||||
pure (SInt value)
|
pure (SInt (fromIntegral value))
|
||||||
| otherwise ->
|
| otherwise ->
|
||||||
fail "Unexpected token while parsing integer literal"
|
fail "Unexpected token while parsing integer literal"
|
||||||
|
|
||||||
|
632
src/REPL.hs
632
src/REPL.hs
@ -5,27 +5,62 @@ import FileEval
|
|||||||
import Lexer
|
import Lexer
|
||||||
import Parser
|
import Parser
|
||||||
import Research
|
import Research
|
||||||
|
import ContentStore
|
||||||
|
|
||||||
import Control.Exception (SomeException, catch)
|
import Control.Concurrent (forkIO, threadDelay, killThread, ThreadId)
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad (forever, void, when, forM, forM_, foldM, unless)
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Maybe (isNothing, isJust, fromJust, catMaybes)
|
||||||
|
import Database.SQLite.Simple (Connection, Only(..), query, query_, execute, execute_, open)
|
||||||
|
import System.Directory (doesFileExist, createDirectoryIfMissing)
|
||||||
|
import System.FSNotify
|
||||||
|
import System.FilePath (takeDirectory, (</>))
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
|
import Control.Exception (IOException, SomeException, catch
|
||||||
|
, displayException)
|
||||||
|
import Control.Monad (forM_)
|
||||||
import Control.Monad.Catch (handle, MonadCatch)
|
import Control.Monad.Catch (handle, MonadCatch)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
|
import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
|
||||||
import Data.Char (isSpace)
|
import Data.Char (isSpace, isUpper)
|
||||||
import Data.List ( dropWhile
|
import Data.List ((\\), dropWhile, dropWhileEnd, isPrefixOf, nub, sortBy, groupBy, intercalate, find)
|
||||||
, dropWhileEnd
|
import Data.Version (showVersion)
|
||||||
, isPrefixOf)
|
import Paths_tricu (version)
|
||||||
import System.Console.Haskeline
|
import System.Console.Haskeline
|
||||||
|
import System.Console.ANSI (setSGR, SGR(..), ConsoleLayer(..), ColorIntensity(..),
|
||||||
|
Color(..), ConsoleIntensity(..), clearFromCursorToLineEnd)
|
||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
|
|
||||||
repl :: Env -> IO ()
|
import Control.Concurrent (forkIO, threadDelay)
|
||||||
repl env = runInputT settings (withInterrupt (loop env True))
|
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
|
||||||
|
import Data.Time (UTCTime, getCurrentTime, diffUTCTime)
|
||||||
|
import Control.Concurrent.MVar (MVar, newMVar, putMVar, takeMVar)
|
||||||
|
|
||||||
|
import Data.Time.Format (formatTime, defaultTimeLocale)
|
||||||
|
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
|
||||||
|
|
||||||
|
data REPLState = REPLState
|
||||||
|
{ replForm :: EvaluatedForm
|
||||||
|
, replContentStore :: Maybe Connection
|
||||||
|
, replWatchedFile :: Maybe FilePath
|
||||||
|
, replSelectedVersions :: Map.Map String T.Text
|
||||||
|
, replWatcherThread :: Maybe ThreadId
|
||||||
|
}
|
||||||
|
|
||||||
|
repl :: IO ()
|
||||||
|
repl = do
|
||||||
|
conn <- ContentStore.initContentStore
|
||||||
|
runInputT settings (withInterrupt (loop (REPLState Decode (Just conn) Nothing Map.empty Nothing)))
|
||||||
where
|
where
|
||||||
settings :: Settings IO
|
settings :: Settings IO
|
||||||
settings = Settings
|
settings = Settings
|
||||||
{ complete = completeWord Nothing " \t" completeCommands
|
{ complete = completeWord Nothing " \t" completeCommands
|
||||||
, historyFile = Just ".tricu_history"
|
, historyFile = Just "~/.local/state/tricu/history"
|
||||||
, autoAddHistory = True
|
, autoAddHistory = True
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,84 +68,537 @@ repl env = runInputT settings (withInterrupt (loop env True))
|
|||||||
completeCommands str = return $ map simpleCompletion $
|
completeCommands str = return $ map simpleCompletion $
|
||||||
filter (str `isPrefixOf`) commands
|
filter (str `isPrefixOf`) commands
|
||||||
where
|
where
|
||||||
commands = ["!exit", "!decode", "!definitions", "!import"]
|
commands = [ "!exit"
|
||||||
|
, "!output"
|
||||||
|
, "!import"
|
||||||
|
, "!clear"
|
||||||
|
, "!reset"
|
||||||
|
, "!help"
|
||||||
|
, "!definitions"
|
||||||
|
, "!watch"
|
||||||
|
, "!refresh"
|
||||||
|
, "!versions"
|
||||||
|
, "!select"
|
||||||
|
, "!tag"
|
||||||
|
]
|
||||||
|
|
||||||
loop :: Env -> Bool -> InputT IO ()
|
loop :: REPLState -> InputT IO ()
|
||||||
loop env decode = handle (interruptHandler env decode) $ do
|
loop state = handle (\Interrupt -> interruptHandler state Interrupt) $ do
|
||||||
minput <- getInputLine "tricu < "
|
minput <- getInputLine "tricu < "
|
||||||
case minput of
|
case minput of
|
||||||
Nothing -> outputStrLn "Exiting tricu"
|
Nothing -> return ()
|
||||||
Just s
|
Just s
|
||||||
| strip s == "" -> loop env decode
|
| strip s == "" -> loop state
|
||||||
| strip s == "!exit" -> outputStrLn "Exiting tricu"
|
| strip s == "!exit" -> outputStrLn "Exiting tricu"
|
||||||
| strip s == "!decode" -> do
|
| strip s == "!clear" -> do
|
||||||
outputStrLn $ "Decoding " ++ (if decode then "disabled" else "enabled")
|
liftIO $ putStr "\ESC[2J\ESC[H"
|
||||||
loop env (not decode)
|
loop state
|
||||||
| strip s == "!definitions" -> do
|
| strip s == "!reset" -> do
|
||||||
let defs = Map.keys $ Map.delete "!result" env
|
outputStrLn "Selected versions reset"
|
||||||
if null defs
|
loop state { replSelectedVersions = Map.empty }
|
||||||
then outputStrLn "No definitions discovered."
|
| strip s == "!help" -> do
|
||||||
else do
|
outputStrLn $ "tricu version " ++ showVersion version
|
||||||
outputStrLn "Available definitions:"
|
outputStrLn "Available commands:"
|
||||||
mapM_ outputStrLn defs
|
outputStrLn " !exit - Exit the REPL"
|
||||||
loop env decode
|
outputStrLn " !clear - Clear the screen"
|
||||||
| "!import" `isPrefixOf` strip s -> handleImport env decode
|
outputStrLn " !reset - Reset preferences for selected versions"
|
||||||
| take 2 s == "--" -> loop env decode
|
outputStrLn " !help - Show tricu version and available commands"
|
||||||
|
outputStrLn " !output - Change output format (tree|fsl|ast|ternary|ascii|decode)"
|
||||||
|
outputStrLn " !definitions - List all defined terms in the content store"
|
||||||
|
outputStrLn " !import - Import definitions from file to the content store"
|
||||||
|
outputStrLn " !watch - Watch a file for changes, evaluate terms, and store them"
|
||||||
|
outputStrLn " !versions - Show all versions of a term by name"
|
||||||
|
outputStrLn " !select - Select a specific version of a term for subsequent lookups"
|
||||||
|
outputStrLn " !tag - Add or update a tag for a term by hash or name"
|
||||||
|
loop state
|
||||||
|
| strip s == "!output" -> handleOutput state
|
||||||
|
| strip s == "!definitions" -> handleDefinitions state
|
||||||
|
| "!import" `isPrefixOf` strip s -> handleImport state
|
||||||
|
| "!watch" `isPrefixOf` strip s -> handleWatch state
|
||||||
|
| strip s == "!refresh" -> handleRefresh state
|
||||||
|
| "!versions" `isPrefixOf` strip s -> handleVersions state
|
||||||
|
| "!select" `isPrefixOf` strip s -> handleSelect state
|
||||||
|
| "!tag" `isPrefixOf` strip s -> handleTag state
|
||||||
|
| take 2 s == "--" -> loop state
|
||||||
| otherwise -> do
|
| otherwise -> do
|
||||||
newEnv <- liftIO $ processInput env s decode `catch` errorHandler env
|
result <- liftIO $ catch
|
||||||
loop newEnv decode
|
(processInput state s)
|
||||||
|
(errorHandler state)
|
||||||
|
loop result
|
||||||
|
|
||||||
|
handleOutput :: REPLState -> InputT IO ()
|
||||||
|
handleOutput state = do
|
||||||
|
let formats = [Decode, TreeCalculus, FSL, AST, Ternary, Ascii]
|
||||||
|
outputStrLn "Available output formats:"
|
||||||
|
mapM_ (\(i, f) -> outputStrLn $ show i ++ ". " ++ show f)
|
||||||
|
(zip [1..] formats)
|
||||||
|
|
||||||
handleImport :: Env -> Bool -> InputT IO ()
|
|
||||||
handleImport env decode = do
|
|
||||||
result <- runMaybeT $ do
|
result <- runMaybeT $ do
|
||||||
let fileSettings = setComplete completeFilename defaultSettings
|
input <- MaybeT $ getInputLine "Select output format (1-6) < "
|
||||||
path <- MaybeT $ runInputT fileSettings $
|
case reads input of
|
||||||
getInputLineWithInitial "File path to load < " ("", "")
|
[(n, "")] | n >= 1 && n <= 6 ->
|
||||||
|
return $ formats !! (n-1)
|
||||||
|
_ -> MaybeT $ return Nothing
|
||||||
|
|
||||||
contents <- liftIO $ readFile (strip path)
|
case result of
|
||||||
|
Nothing -> do
|
||||||
|
outputStrLn "Invalid selection. Keeping current output format."
|
||||||
|
loop state
|
||||||
|
Just newForm -> do
|
||||||
|
outputStrLn $ "Output format changed to: " ++ show newForm
|
||||||
|
loop state { replForm = newForm }
|
||||||
|
|
||||||
if | Left err <- parseProgram (lexTricu contents) -> do
|
handleDefinitions :: REPLState -> InputT IO ()
|
||||||
lift $ outputStrLn $ "Parse error: " ++ handleParseError err
|
handleDefinitions state = case replContentStore state of
|
||||||
MaybeT $ return Nothing
|
Nothing -> do
|
||||||
| Right ast <- parseProgram (lexTricu contents) -> do
|
liftIO $ printError "Content store not initialized"
|
||||||
ns <- MaybeT $ runInputT defaultSettings $
|
loop state
|
||||||
getInputLineWithInitial "Namespace (or !Local for no namespace) < " ("", "")
|
Just conn -> do
|
||||||
|
terms <- liftIO $ ContentStore.listStoredTerms conn
|
||||||
|
|
||||||
|
if null terms
|
||||||
|
then do
|
||||||
|
liftIO $ printWarning "No terms in content store."
|
||||||
|
loop state
|
||||||
|
else do
|
||||||
|
liftIO $ do
|
||||||
|
printSuccess $ "Content store contains " ++ show (length terms) ++ " terms:"
|
||||||
|
|
||||||
processedAst <- liftIO $ preprocessFile (strip path)
|
let maxNameWidth = maximum $ map (length . T.unpack . termNames) terms
|
||||||
let namespacedAst | strip ns == "!Local" = processedAst
|
|
||||||
| otherwise = nsDefinitions (strip ns) processedAst
|
|
||||||
loadedEnv = evalTricu env namespacedAst
|
|
||||||
return loadedEnv
|
|
||||||
|
|
||||||
if | Nothing <- result -> do
|
forM_ terms $ \term -> do
|
||||||
outputStrLn "Import cancelled."
|
let namesStr = T.unpack (termNames term)
|
||||||
loop env decode
|
hash = termHash term
|
||||||
| Just loadedEnv <- result ->
|
padding = replicate (maxNameWidth - length namesStr) ' '
|
||||||
loop (Map.delete "!result" loadedEnv) decode
|
|
||||||
|
liftIO $ do
|
||||||
|
putStr " "
|
||||||
|
printVariable namesStr
|
||||||
|
putStr padding
|
||||||
|
putStr " [hash: "
|
||||||
|
displayColoredHash hash
|
||||||
|
putStrLn "]"
|
||||||
|
|
||||||
|
tags <- ContentStore.termToTags conn hash
|
||||||
|
unless (null tags) $ displayTags tags
|
||||||
|
|
||||||
interruptHandler :: Env -> Bool -> Interrupt -> InputT IO ()
|
loop state
|
||||||
interruptHandler env decode _ = do
|
|
||||||
outputStrLn "Interrupted with CTRL+C\n\
|
|
||||||
\You can use the !exit command or CTRL+D to exit"
|
|
||||||
loop env decode
|
|
||||||
|
|
||||||
processInput :: Env -> String -> Bool -> IO Env
|
handleImport :: REPLState -> InputT IO ()
|
||||||
processInput env input decode = do
|
handleImport state = do
|
||||||
let asts = parseTricu input
|
let fset = setComplete completeFilename defaultSettings
|
||||||
newEnv = evalTricu env asts
|
filename <- runInputT fset $ getInputLineWithInitial "File to import: " ("", "")
|
||||||
case Map.lookup "!result" newEnv of
|
case filename of
|
||||||
Just r -> do
|
Nothing -> loop state
|
||||||
putStrLn $ "tricu > " ++
|
Just f -> do
|
||||||
if decode
|
let cleanFilename = strip f
|
||||||
then decodeResult r
|
exists <- liftIO $ doesFileExist cleanFilename
|
||||||
else show r
|
if not exists
|
||||||
Nothing -> pure ()
|
then do
|
||||||
return newEnv
|
liftIO $ printError $ "File not found: " ++ cleanFilename
|
||||||
|
loop state
|
||||||
|
else importFile state cleanFilename
|
||||||
|
|
||||||
errorHandler :: Env -> SomeException -> IO (Env)
|
importFile :: REPLState -> String -> InputT IO ()
|
||||||
errorHandler env e = do
|
importFile state cleanFilename = do
|
||||||
putStrLn $ "Error: " ++ show e
|
code <- liftIO $ readFile cleanFilename
|
||||||
return env
|
case replContentStore state of
|
||||||
|
Nothing -> do
|
||||||
|
liftIO $ printError "Content store not initialized"
|
||||||
|
loop state
|
||||||
|
Just conn -> do
|
||||||
|
env <- liftIO $ evaluateFile cleanFilename
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
printSuccess $ "Importing file: " ++ cleanFilename
|
||||||
|
let defs = Map.toList $ Map.delete "!result" env
|
||||||
|
|
||||||
|
importedCount <- foldM (\count (name, term) -> do
|
||||||
|
hash <- ContentStore.storeTerm conn [name] term
|
||||||
|
printSuccess $ "Stored definition: " ++ name ++ " with hash " ++ T.unpack hash
|
||||||
|
return (count + 1)
|
||||||
|
) 0 defs
|
||||||
|
|
||||||
|
printSuccess $ "Imported " ++ show importedCount ++ " definitions successfully"
|
||||||
|
|
||||||
|
loop state
|
||||||
|
|
||||||
|
handleWatch :: REPLState -> InputT IO ()
|
||||||
|
handleWatch state = do
|
||||||
|
dbPath <- liftIO ContentStore.getContentStorePath
|
||||||
|
let filepath = takeDirectory dbPath </> "scratch.tri"
|
||||||
|
let dirPath = takeDirectory filepath
|
||||||
|
|
||||||
|
liftIO $ createDirectoryIfMissing True dirPath
|
||||||
|
|
||||||
|
fileExists <- liftIO $ doesFileExist filepath
|
||||||
|
unless fileExists $ liftIO $ writeFile filepath "-- tricu scratch file\n\n"
|
||||||
|
|
||||||
|
outputStrLn $ "Using scratch file: " ++ filepath
|
||||||
|
|
||||||
|
when (isJust (replWatcherThread state)) $ do
|
||||||
|
outputStrLn "Stopping previous file watch"
|
||||||
|
liftIO $ killThread (fromJust $ replWatcherThread state)
|
||||||
|
|
||||||
|
outputStrLn $ "Starting to watch file: " ++ filepath
|
||||||
|
outputStrLn "Press Ctrl+C to stop watching and return to REPL"
|
||||||
|
|
||||||
|
liftIO $ processWatchedFile filepath (replContentStore state) (replSelectedVersions state) (replForm state)
|
||||||
|
|
||||||
|
lastProcessedRef <- liftIO $ newIORef =<< getCurrentTime
|
||||||
|
|
||||||
|
watcherId <- liftIO $ forkIO $ withManager $ \mgr -> do
|
||||||
|
stopAction <- watchDir mgr dirPath (\event -> eventPath event == filepath) $ \event -> do
|
||||||
|
now <- getCurrentTime
|
||||||
|
lastProcessed <- readIORef lastProcessedRef
|
||||||
|
when (diffUTCTime now lastProcessed > 0.5) $ do
|
||||||
|
putStrLn $ "\nFile changed: " ++ filepath
|
||||||
|
processWatchedFile filepath (replContentStore state) (replSelectedVersions state) (replForm state)
|
||||||
|
writeIORef lastProcessedRef now
|
||||||
|
forever $ threadDelay 1000000
|
||||||
|
|
||||||
|
watchLoop state { replWatchedFile = Just filepath, replWatcherThread = Just watcherId }
|
||||||
|
|
||||||
|
handleUnwatch :: REPLState -> InputT IO ()
|
||||||
|
handleUnwatch state = case replWatchedFile state of
|
||||||
|
Nothing -> do
|
||||||
|
outputStrLn "No file is currently being watched"
|
||||||
|
loop state
|
||||||
|
Just path -> do
|
||||||
|
outputStrLn $ "Stopped watching " ++ path
|
||||||
|
when (isJust (replWatcherThread state)) $ do
|
||||||
|
liftIO $ killThread (fromJust $ replWatcherThread state)
|
||||||
|
loop state { replWatchedFile = Nothing, replWatcherThread = Nothing }
|
||||||
|
|
||||||
|
handleRefresh :: REPLState -> InputT IO ()
|
||||||
|
handleRefresh state = case replContentStore state of
|
||||||
|
Nothing -> do
|
||||||
|
outputStrLn "Content store not initialized"
|
||||||
|
loop state
|
||||||
|
Just conn -> do
|
||||||
|
outputStrLn "Environment refreshed from content store (definitions are live)"
|
||||||
|
loop state
|
||||||
|
|
||||||
|
handleVersions :: REPLState -> InputT IO ()
|
||||||
|
handleVersions state = case replContentStore state of
|
||||||
|
Nothing -> do
|
||||||
|
liftIO $ printError "Content store not initialized"
|
||||||
|
loop state
|
||||||
|
Just conn -> do
|
||||||
|
liftIO $ printPrompt "Term name: "
|
||||||
|
nameInput <- getInputLine ""
|
||||||
|
case nameInput of
|
||||||
|
Nothing -> loop state
|
||||||
|
Just n -> do
|
||||||
|
let termName = strip n
|
||||||
|
versions <- liftIO $ ContentStore.termVersions conn termName
|
||||||
|
if null versions
|
||||||
|
then liftIO $ printError $ "No versions found for term: " ++ termName
|
||||||
|
else do
|
||||||
|
liftIO $ do
|
||||||
|
printKeyword "Versions of "
|
||||||
|
printVariable termName
|
||||||
|
putStrLn ":"
|
||||||
|
|
||||||
|
forM_ (zip [1..] versions) $ \(i, (hash, _, ts)) -> do
|
||||||
|
tags <- ContentStore.termToTags conn hash
|
||||||
|
putStr $ show (i :: Int) ++ ". "
|
||||||
|
displayColoredHash hash
|
||||||
|
putStr $ " (" ++ formatTimestamp ts ++ ")"
|
||||||
|
unless (null tags) $ do
|
||||||
|
putStr " ["
|
||||||
|
printKeyword "Tags: "
|
||||||
|
forM_ (zip [0..] tags) $ \(j, tag) -> do
|
||||||
|
printTag (T.unpack tag)
|
||||||
|
when (j < length tags - 1) $ putStr ", "
|
||||||
|
putStr "]"
|
||||||
|
putStrLn ""
|
||||||
|
loop state
|
||||||
|
|
||||||
|
handleSelect :: REPLState -> InputT IO ()
|
||||||
|
handleSelect state = case replContentStore state of
|
||||||
|
Nothing -> do
|
||||||
|
liftIO $ printError "Content store not initialized"
|
||||||
|
loop state
|
||||||
|
Just conn -> do
|
||||||
|
liftIO $ printPrompt "Term name: "
|
||||||
|
nameInput <- getInputLine ""
|
||||||
|
case nameInput of
|
||||||
|
Nothing -> loop state
|
||||||
|
Just n -> do
|
||||||
|
let cleanName = strip n
|
||||||
|
versions <- liftIO $ ContentStore.termVersions conn cleanName
|
||||||
|
if null versions
|
||||||
|
then do
|
||||||
|
liftIO $ printError $ "No versions found for term: " ++ cleanName
|
||||||
|
loop state
|
||||||
|
else do
|
||||||
|
liftIO $ do
|
||||||
|
printKeyword "Versions of "
|
||||||
|
printVariable cleanName
|
||||||
|
putStrLn ":"
|
||||||
|
|
||||||
|
forM_ (zip [1..] versions) $ \(i, (hash, _, ts)) -> do
|
||||||
|
tags <- ContentStore.termToTags conn hash
|
||||||
|
putStr $ show (i :: Int) ++ ". "
|
||||||
|
displayColoredHash hash
|
||||||
|
putStr $ " (" ++ formatTimestamp ts ++ ")"
|
||||||
|
unless (null tags) $ do
|
||||||
|
putStr " ["
|
||||||
|
printKeyword "Tags: "
|
||||||
|
forM_ (zip [0..] tags) $ \(j, tag) -> do
|
||||||
|
printTag (T.unpack tag)
|
||||||
|
when (j < length tags - 1) $ putStr ", "
|
||||||
|
putStr "]"
|
||||||
|
putStrLn ""
|
||||||
|
|
||||||
|
liftIO $ printPrompt "Select version (number or full hash, Enter to cancel): "
|
||||||
|
choiceInput <- getInputLine ""
|
||||||
|
let choice = strip <$> choiceInput
|
||||||
|
|
||||||
|
selectedHash <- case choice of
|
||||||
|
Just selectedStr | not (null selectedStr) -> do
|
||||||
|
case readMaybe selectedStr :: Maybe Int of
|
||||||
|
Just idx | idx > 0 && idx <= length versions -> do
|
||||||
|
let (h, _, _) = versions !! (idx - 1)
|
||||||
|
return $ Just h
|
||||||
|
_ -> do
|
||||||
|
let potentialHash = T.pack selectedStr
|
||||||
|
let foundByHash = find (\(h, _, _) -> T.isPrefixOf potentialHash h) versions
|
||||||
|
case foundByHash of
|
||||||
|
Just (h, _, _) -> return $ Just h
|
||||||
|
Nothing -> do
|
||||||
|
liftIO $ printError "Invalid selection or hash not found in list."
|
||||||
|
return Nothing
|
||||||
|
_ -> return Nothing
|
||||||
|
|
||||||
|
case selectedHash of
|
||||||
|
Just hashToSelect -> do
|
||||||
|
let newState = state { replSelectedVersions =
|
||||||
|
Map.insert cleanName hashToSelect (replSelectedVersions state) }
|
||||||
|
liftIO $ do
|
||||||
|
printSuccess "Selected version "
|
||||||
|
displayColoredHash hashToSelect
|
||||||
|
putStr " for term "
|
||||||
|
printVariable cleanName
|
||||||
|
putStrLn ""
|
||||||
|
loop newState
|
||||||
|
Nothing -> loop state
|
||||||
|
|
||||||
|
handleTag :: REPLState -> InputT IO ()
|
||||||
|
handleTag state = case replContentStore state of
|
||||||
|
Nothing -> do
|
||||||
|
liftIO $ printError "Content store not initialized"
|
||||||
|
loop state
|
||||||
|
Just conn -> do
|
||||||
|
liftIO $ printPrompt "Term hash (full or prefix) or name (most recent version will be used): "
|
||||||
|
identInput <- getInputLine ""
|
||||||
|
case identInput of
|
||||||
|
Nothing -> loop state
|
||||||
|
Just ident -> do
|
||||||
|
let cleanIdent = strip ident
|
||||||
|
|
||||||
|
mFullHash <- liftIO $ resolveIdentifierToHash conn cleanIdent
|
||||||
|
|
||||||
|
case mFullHash of
|
||||||
|
Nothing -> do
|
||||||
|
liftIO $ printError $ "Could not resolve identifier: " ++ cleanIdent
|
||||||
|
loop state
|
||||||
|
Just fullHash -> do
|
||||||
|
liftIO $ do
|
||||||
|
putStr "Tagging term with hash: "
|
||||||
|
displayColoredHash fullHash
|
||||||
|
putStrLn ""
|
||||||
|
tags <- liftIO $ ContentStore.termToTags conn fullHash
|
||||||
|
unless (null tags) $ do
|
||||||
|
liftIO $ do
|
||||||
|
printKeyword "Existing tags:"
|
||||||
|
displayTags tags
|
||||||
|
|
||||||
|
liftIO $ printPrompt "Tag to add/set: "
|
||||||
|
tagValueInput <- getInputLine ""
|
||||||
|
case tagValueInput of
|
||||||
|
Nothing -> loop state
|
||||||
|
Just tv -> do
|
||||||
|
let tagVal = T.pack (strip tv)
|
||||||
|
liftIO $ do
|
||||||
|
ContentStore.setTag conn fullHash tagVal
|
||||||
|
printSuccess $ "Tag '"
|
||||||
|
printTag (T.unpack tagVal)
|
||||||
|
putStr "' set for term with hash "
|
||||||
|
displayColoredHash fullHash
|
||||||
|
putStrLn ""
|
||||||
|
loop state
|
||||||
|
|
||||||
|
resolveIdentifierToHash :: Connection -> String -> IO (Maybe T.Text)
|
||||||
|
resolveIdentifierToHash conn ident
|
||||||
|
| T.pack "#" `T.isInfixOf` T.pack ident = do
|
||||||
|
let hashPrefix = T.pack ident
|
||||||
|
matchingHashes <- liftIO $ query conn "SELECT hash FROM terms WHERE hash LIKE ?" (Only (hashPrefix <> "%")) :: IO [Only T.Text]
|
||||||
|
case matchingHashes of
|
||||||
|
[Only fullHash] -> return $ Just fullHash
|
||||||
|
[] -> do printError $ "No hash found starting with: " ++ T.unpack hashPrefix; return Nothing
|
||||||
|
_ -> do printError $ "Ambiguous hash prefix: " ++ T.unpack hashPrefix; return Nothing
|
||||||
|
| otherwise = do
|
||||||
|
versions <- ContentStore.termVersions conn ident
|
||||||
|
if null versions
|
||||||
|
then do printError $ "No versions found for term name: " ++ ident; return Nothing
|
||||||
|
else return $ Just $ (\(h,_,_) -> h) $ head versions
|
||||||
|
|
||||||
|
interruptHandler :: REPLState -> Interrupt -> InputT IO ()
|
||||||
|
interruptHandler state _ = do
|
||||||
|
liftIO $ do
|
||||||
|
printWarning "Interrupted with CTRL+C"
|
||||||
|
printWarning "You can use the !exit command or CTRL+D to exit"
|
||||||
|
loop state
|
||||||
|
|
||||||
|
errorHandler :: REPLState -> SomeException -> IO REPLState
|
||||||
|
errorHandler state e = do
|
||||||
|
printError $ "Error: " ++ displayException e
|
||||||
|
return state
|
||||||
|
|
||||||
|
processInput :: REPLState -> String -> IO REPLState
|
||||||
|
processInput state input = do
|
||||||
|
let asts = parseTricu input
|
||||||
|
case asts of
|
||||||
|
[] -> return state
|
||||||
|
_ -> case replContentStore state of
|
||||||
|
Nothing -> do
|
||||||
|
printError "Content store not initialized"
|
||||||
|
return state
|
||||||
|
Just conn -> do
|
||||||
|
newState <- foldM (\s astNode -> do
|
||||||
|
let varsInAst = Eval.findVarNames astNode
|
||||||
|
foldM (\currentSelectionState varName ->
|
||||||
|
if Map.member varName (replSelectedVersions currentSelectionState)
|
||||||
|
then return currentSelectionState
|
||||||
|
else do
|
||||||
|
versions <- ContentStore.termVersions conn varName
|
||||||
|
if length versions > 1
|
||||||
|
then do
|
||||||
|
let (latestHash, _, _) = head versions
|
||||||
|
liftIO $ printWarning $ "Multiple versions of '" ++ varName ++ "' found. Using most recent."
|
||||||
|
return currentSelectionState { replSelectedVersions = Map.insert varName latestHash (replSelectedVersions currentSelectionState) }
|
||||||
|
else return currentSelectionState
|
||||||
|
) s varsInAst
|
||||||
|
) state asts
|
||||||
|
|
||||||
|
forM_ asts $ \ast -> do
|
||||||
|
case ast of
|
||||||
|
SDef name [] body -> do
|
||||||
|
result <- evalAST (Just conn) (replSelectedVersions newState) body
|
||||||
|
hash <- ContentStore.storeTerm conn [name] result
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
putStr "tricu > "
|
||||||
|
printSuccess "Stored definition: "
|
||||||
|
printVariable name
|
||||||
|
putStr " with hash "
|
||||||
|
displayColoredHash hash
|
||||||
|
putStrLn ""
|
||||||
|
|
||||||
|
putStr "tricu > "
|
||||||
|
printResult $ formatT (replForm newState) result
|
||||||
|
putStrLn ""
|
||||||
|
|
||||||
|
_ -> do
|
||||||
|
result <- evalAST (Just conn) (replSelectedVersions newState) ast
|
||||||
|
liftIO $ do
|
||||||
|
putStr "tricu > "
|
||||||
|
printResult $ formatT (replForm newState) result
|
||||||
|
putStrLn ""
|
||||||
|
return newState
|
||||||
|
|
||||||
strip :: String -> String
|
strip :: String -> String
|
||||||
strip = dropWhileEnd isSpace . dropWhile isSpace
|
strip = dropWhileEnd isSpace . dropWhile isSpace
|
||||||
|
|
||||||
|
watchLoop :: REPLState -> InputT IO ()
|
||||||
|
watchLoop state = handle (\Interrupt -> do
|
||||||
|
outputStrLn "\nStopped watching file"
|
||||||
|
when (isJust (replWatcherThread state)) $ do
|
||||||
|
liftIO $ killThread (fromJust $ replWatcherThread state)
|
||||||
|
loop state { replWatchedFile = Nothing, replWatcherThread = Nothing }) $ do
|
||||||
|
liftIO $ threadDelay 1000000
|
||||||
|
watchLoop state
|
||||||
|
|
||||||
|
processWatchedFile :: FilePath -> Maybe Connection -> Map.Map String T.Text -> EvaluatedForm -> IO ()
|
||||||
|
processWatchedFile filepath mconn selectedVersions outputForm = do
|
||||||
|
content <- readFile filepath
|
||||||
|
let asts = parseTricu content
|
||||||
|
|
||||||
|
case mconn of
|
||||||
|
Nothing -> putStrLn "Content store not initialized for watched file processing."
|
||||||
|
Just conn -> do
|
||||||
|
forM_ asts $ \ast -> case ast of
|
||||||
|
SDef name [] body -> do
|
||||||
|
result <- evalAST (Just conn) selectedVersions body
|
||||||
|
hash <- ContentStore.storeTerm conn [name] result
|
||||||
|
putStrLn $ "tricu > Stored definition: " ++ name ++ " with hash " ++ T.unpack hash
|
||||||
|
putStrLn $ "tricu > " ++ name ++ " = " ++ formatT outputForm result
|
||||||
|
_ -> do
|
||||||
|
result <- evalAST (Just conn) selectedVersions ast
|
||||||
|
putStrLn $ "tricu > Result: " ++ formatT outputForm result
|
||||||
|
putStrLn $ "tricu > Processed file: " ++ filepath
|
||||||
|
|
||||||
|
formatTimestamp :: Integer -> String
|
||||||
|
formatTimestamp ts = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S" (posixSecondsToUTCTime (fromIntegral ts))
|
||||||
|
|
||||||
|
displayColoredHash :: T.Text -> IO ()
|
||||||
|
displayColoredHash hash = do
|
||||||
|
let (prefix, rest) = T.splitAt 16 hash
|
||||||
|
setSGR [SetColor Foreground Vivid Cyan]
|
||||||
|
putStr $ T.unpack prefix
|
||||||
|
setSGR [SetColor Foreground Dull White]
|
||||||
|
putStr $ T.unpack rest
|
||||||
|
setSGR [Reset]
|
||||||
|
|
||||||
|
coloredHashString :: T.Text -> String
|
||||||
|
coloredHashString hash =
|
||||||
|
"\ESC[1;36m" ++ T.unpack (T.take 16 hash) ++
|
||||||
|
"\ESC[0;37m" ++ T.unpack (T.drop 16 hash) ++
|
||||||
|
"\ESC[0m"
|
||||||
|
|
||||||
|
withColor :: ColorIntensity -> Color -> IO () -> IO ()
|
||||||
|
withColor intensity color action = do
|
||||||
|
setSGR [SetColor Foreground intensity color]
|
||||||
|
action
|
||||||
|
setSGR [Reset]
|
||||||
|
|
||||||
|
printColored :: ColorIntensity -> Color -> String -> IO ()
|
||||||
|
printColored intensity color text = withColor intensity color $ putStr text
|
||||||
|
|
||||||
|
printlnColored :: ColorIntensity -> Color -> String -> IO ()
|
||||||
|
printlnColored intensity color text = withColor intensity color $ putStrLn text
|
||||||
|
|
||||||
|
printSuccess :: String -> IO ()
|
||||||
|
printSuccess = printlnColored Vivid Green
|
||||||
|
|
||||||
|
printError :: String -> IO ()
|
||||||
|
printError = printlnColored Vivid Red
|
||||||
|
|
||||||
|
printWarning :: String -> IO ()
|
||||||
|
printWarning = printlnColored Vivid Yellow
|
||||||
|
|
||||||
|
printPrompt :: String -> IO ()
|
||||||
|
printPrompt = printColored Vivid Blue
|
||||||
|
|
||||||
|
printVariable :: String -> IO ()
|
||||||
|
printVariable = printColored Vivid Magenta
|
||||||
|
|
||||||
|
printTag :: String -> IO ()
|
||||||
|
printTag = printColored Vivid Yellow
|
||||||
|
|
||||||
|
printKeyword :: String -> IO ()
|
||||||
|
printKeyword = printColored Vivid Blue
|
||||||
|
|
||||||
|
printResult :: String -> IO ()
|
||||||
|
printResult = printColored Dull White
|
||||||
|
|
||||||
|
displayTags :: [T.Text] -> IO ()
|
||||||
|
displayTags [] = return ()
|
||||||
|
displayTags tags = do
|
||||||
|
putStr " Tags: "
|
||||||
|
forM_ (zip [0..] tags) $ \(i, tag) -> do
|
||||||
|
printTag (T.unpack tag)
|
||||||
|
when (i < length tags - 1) $ putStr ", "
|
||||||
|
putStrLn ""
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
module Research where
|
module Research where
|
||||||
|
|
||||||
import Control.Monad.State
|
|
||||||
import Data.List (intercalate)
|
import Data.List (intercalate)
|
||||||
import Data.Map (Map)
|
import Data.Map (Map)
|
||||||
import Data.Text (Text, replace)
|
import Data.Text (Text, replace)
|
||||||
@ -15,8 +14,8 @@ data T = Leaf | Stem T | Fork T T
|
|||||||
|
|
||||||
-- Abstract Syntax Tree for tricu
|
-- Abstract Syntax Tree for tricu
|
||||||
data TricuAST
|
data TricuAST
|
||||||
= SVar String
|
= SVar String (Maybe String) -- Variable name and optional hash prefix
|
||||||
| SInt Int
|
| SInt Integer
|
||||||
| SStr String
|
| SStr String
|
||||||
| SList [TricuAST]
|
| SList [TricuAST]
|
||||||
| SDef String [String] TricuAST
|
| SDef String [String] TricuAST
|
||||||
@ -31,22 +30,22 @@ data TricuAST
|
|||||||
|
|
||||||
-- Lexer Tokens
|
-- Lexer Tokens
|
||||||
data LToken
|
data LToken
|
||||||
= LKeywordT
|
= LIdentifier String
|
||||||
| LIdentifier String
|
| LIdentifierWithHash String String
|
||||||
|
| LKeywordT
|
||||||
| LNamespace String
|
| LNamespace String
|
||||||
| LIntegerLiteral Int
|
| LImport String String
|
||||||
| LStringLiteral String
|
|
||||||
| LAssign
|
| LAssign
|
||||||
| LColon
|
| LColon
|
||||||
| LDot
|
| LDot
|
||||||
| LBackslash
|
|
||||||
| LOpenParen
|
| LOpenParen
|
||||||
| LCloseParen
|
| LCloseParen
|
||||||
| LOpenBracket
|
| LOpenBracket
|
||||||
| LCloseBracket
|
| LCloseBracket
|
||||||
|
| LStringLiteral String
|
||||||
|
| LIntegerLiteral Int
|
||||||
| LNewline
|
| LNewline
|
||||||
| LImport String String
|
deriving (Eq, Show, Ord)
|
||||||
deriving (Show, Eq, Ord)
|
|
||||||
|
|
||||||
-- Output formats
|
-- Output formats
|
||||||
data EvaluatedForm = TreeCalculus | FSL | AST | Ternary | Ascii | Decode
|
data EvaluatedForm = TreeCalculus | FSL | AST | Ternary | Ascii | Decode
|
||||||
@ -55,15 +54,24 @@ data EvaluatedForm = TreeCalculus | FSL | AST | Ternary | Ascii | Decode
|
|||||||
-- Environment containing previously evaluated TC terms
|
-- Environment containing previously evaluated TC terms
|
||||||
type Env = Map.Map String T
|
type Env = Map.Map String T
|
||||||
|
|
||||||
-- Tree Calculus Reduction
|
-- Tree Calculus Reduction Rules
|
||||||
|
{-
|
||||||
|
The t operator is left associative.
|
||||||
|
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
|
||||||
|
-}
|
||||||
apply :: T -> T -> T
|
apply :: T -> T -> T
|
||||||
apply Leaf b = Stem b
|
apply (Fork Leaf a) _ = a
|
||||||
apply (Stem a) b = Fork a b
|
apply (Fork (Stem a) b) c = apply (apply a c) (apply b c)
|
||||||
apply (Fork Leaf a) _ = a
|
apply (Fork (Fork a b) c) Leaf = a
|
||||||
apply (Fork (Stem a1) a2) b = apply (apply a1 b) (apply a2 b)
|
apply (Fork (Fork a b) c) (Stem u) = apply b u
|
||||||
apply (Fork (Fork a1 a2) a3) Leaf = a1
|
apply (Fork (Fork a b) c) (Fork u v) = apply (apply c u) v
|
||||||
apply (Fork (Fork a1 a2) a3) (Stem u) = apply a2 u
|
-- Left associative `t`
|
||||||
apply (Fork (Fork a1 a2) a3) (Fork u v) = apply (apply a3 u) v
|
apply Leaf b = Stem b
|
||||||
|
apply (Stem a) b = Fork a b
|
||||||
|
|
||||||
-- Booleans
|
-- Booleans
|
||||||
_false :: T
|
_false :: T
|
||||||
@ -77,9 +85,9 @@ _not = Fork (Fork _true (Fork Leaf _false)) Leaf
|
|||||||
|
|
||||||
-- Marshalling
|
-- Marshalling
|
||||||
ofString :: String -> T
|
ofString :: String -> T
|
||||||
ofString str = ofList (map ofNumber (map fromEnum str))
|
ofString str = ofList $ map (ofNumber . toInteger . fromEnum) str
|
||||||
|
|
||||||
ofNumber :: Int -> T
|
ofNumber :: Integer -> T
|
||||||
ofNumber 0 = Leaf
|
ofNumber 0 = Leaf
|
||||||
ofNumber n =
|
ofNumber n =
|
||||||
Fork
|
Fork
|
||||||
@ -87,10 +95,9 @@ ofNumber n =
|
|||||||
(ofNumber (n `div` 2))
|
(ofNumber (n `div` 2))
|
||||||
|
|
||||||
ofList :: [T] -> T
|
ofList :: [T] -> T
|
||||||
ofList [] = Leaf
|
ofList = foldr Fork Leaf
|
||||||
ofList (x:xs) = Fork x (ofList xs)
|
|
||||||
|
|
||||||
toNumber :: T -> Either String Int
|
toNumber :: T -> Either String Integer
|
||||||
toNumber Leaf = Right 0
|
toNumber Leaf = Right 0
|
||||||
toNumber (Fork Leaf rest) = case toNumber rest of
|
toNumber (Fork Leaf rest) = case toNumber rest of
|
||||||
Right n -> Right (2 * n)
|
Right n -> Right (2 * n)
|
||||||
@ -102,7 +109,7 @@ toNumber _ = Left "Invalid Tree Calculus number"
|
|||||||
|
|
||||||
toString :: T -> Either String String
|
toString :: T -> Either String String
|
||||||
toString tc = case toList tc of
|
toString tc = case toList tc of
|
||||||
Right list -> traverse (fmap toEnum . toNumber) list
|
Right list -> traverse (fmap (toEnum . fromInteger) . toNumber) list
|
||||||
Left err -> Left "Invalid Tree Calculus string"
|
Left err -> Left "Invalid Tree Calculus string"
|
||||||
|
|
||||||
toList :: T -> Either String [T]
|
toList :: T -> Either String [T]
|
||||||
@ -113,20 +120,20 @@ toList (Fork x rest) = case toList rest of
|
|||||||
toList _ = Left "Invalid Tree Calculus list"
|
toList _ = Left "Invalid Tree Calculus list"
|
||||||
|
|
||||||
-- Outputs
|
-- Outputs
|
||||||
formatResult :: EvaluatedForm -> T -> String
|
formatT :: EvaluatedForm -> T -> String
|
||||||
formatResult TreeCalculus = toSimpleT . show
|
formatT TreeCalculus = toSimpleT . show
|
||||||
formatResult FSL = show
|
formatT FSL = show
|
||||||
formatResult AST = show . toAST
|
formatT AST = show . toAST
|
||||||
formatResult Ternary = toTernaryString
|
formatT Ternary = toTernaryString
|
||||||
formatResult Ascii = toAscii
|
formatT Ascii = toAscii
|
||||||
formatResult Decode = decodeResult
|
formatT Decode = decodeResult
|
||||||
|
|
||||||
toSimpleT :: String -> String
|
toSimpleT :: String -> String
|
||||||
toSimpleT s = T.unpack
|
toSimpleT s = T.unpack
|
||||||
$ replace "Fork" "t"
|
$ replace "Fork" "t"
|
||||||
$ replace "Stem" "t"
|
$ replace "Stem" "t"
|
||||||
$ replace "Leaf" "t"
|
$ replace "Leaf" "t"
|
||||||
$ (T.pack s)
|
$ T.pack s
|
||||||
|
|
||||||
toTernaryString :: T -> String
|
toTernaryString :: T -> String
|
||||||
toTernaryString Leaf = "0"
|
toTernaryString Leaf = "0"
|
||||||
@ -153,8 +160,18 @@ toAscii tree = go tree "" True
|
|||||||
++ go right (prefix ++ (if isLast then " " else "| ")) True
|
++ go right (prefix ++ (if isLast then " " else "| ")) True
|
||||||
|
|
||||||
decodeResult :: T -> String
|
decodeResult :: T -> String
|
||||||
decodeResult tc
|
decodeResult Leaf = "t"
|
||||||
| Right num <- toNumber tc = show num
|
decodeResult tc =
|
||||||
| Right str <- toString tc = "\"" ++ str ++ "\""
|
case (toString tc, toList tc, toNumber tc) of
|
||||||
| Right list <- toList tc = "[" ++ intercalate ", " (map decodeResult list) ++ "]"
|
(Right s, _, _) | all isCommonChar s -> "\"" ++ s ++ "\""
|
||||||
| otherwise = formatResult TreeCalculus tc
|
(_, _, Right n) -> show n
|
||||||
|
(_, Right xs@(_:_), _) -> "[" ++ intercalate ", " (map decodeResult xs) ++ "]"
|
||||||
|
(_, Right [], _) -> "[]"
|
||||||
|
_ -> formatT TreeCalculus tc
|
||||||
|
where
|
||||||
|
isCommonChar c =
|
||||||
|
let n = fromEnum c
|
||||||
|
in (n >= 32 && n <= 126)
|
||||||
|
|| n == 9
|
||||||
|
|| n == 10
|
||||||
|
|| n == 13
|
||||||
|
171
test/Spec.hs
171
test/Spec.hs
@ -12,7 +12,6 @@ import Control.Monad.IO.Class (liftIO)
|
|||||||
import Data.List (isInfixOf)
|
import Data.List (isInfixOf)
|
||||||
import Test.Tasty
|
import Test.Tasty
|
||||||
import Test.Tasty.HUnit
|
import Test.Tasty.HUnit
|
||||||
import Test.Tasty.QuickCheck
|
|
||||||
import Text.Megaparsec (runParser)
|
import Text.Megaparsec (runParser)
|
||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
@ -21,8 +20,8 @@ import qualified Data.Set as Set
|
|||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = defaultMain tests
|
main = defaultMain tests
|
||||||
|
|
||||||
runTricu :: String -> String
|
tricuTestString :: String -> String
|
||||||
runTricu s = show $ result (evalTricu Map.empty $ parseTricu s)
|
tricuTestString s = show $ result (evalTricu Map.empty $ parseTricu s)
|
||||||
|
|
||||||
tests :: TestTree
|
tests :: TestTree
|
||||||
tests = testGroup "Tricu Tests"
|
tests = testGroup "Tricu Tests"
|
||||||
@ -33,7 +32,8 @@ tests = testGroup "Tricu Tests"
|
|||||||
, providedLibraries
|
, providedLibraries
|
||||||
, fileEval
|
, fileEval
|
||||||
, modules
|
, modules
|
||||||
, demos
|
-- , demos
|
||||||
|
, decoding
|
||||||
]
|
]
|
||||||
|
|
||||||
lexer :: TestTree
|
lexer :: TestTree
|
||||||
@ -50,7 +50,22 @@ lexer = testGroup "Lexer Tests"
|
|||||||
|
|
||||||
, testCase "Lex escaped characters in strings" $ do
|
, testCase "Lex escaped characters in strings" $ do
|
||||||
let input = "\"hello\\nworld\""
|
let input = "\"hello\\nworld\""
|
||||||
expect = Right [LStringLiteral "hello\\nworld"]
|
expect = Right [LStringLiteral "hello\nworld"]
|
||||||
|
runParser tricuLexer "" input @?= expect
|
||||||
|
|
||||||
|
, testCase "Lex multiple escaped characters in strings" $ do
|
||||||
|
let input = "\"tab:\\t newline:\\n quote:\\\" backslash:\\\\\""
|
||||||
|
expect = Right [LStringLiteral "tab:\t newline:\n quote:\" backslash:\\"]
|
||||||
|
runParser tricuLexer "" input @?= expect
|
||||||
|
|
||||||
|
, testCase "Lex escaped characters in string literals" $ do
|
||||||
|
let input = "x = \"line1\\nline2\\tindented\""
|
||||||
|
expect = Right [LIdentifier "x", LAssign, LStringLiteral "line1\nline2\tindented"]
|
||||||
|
runParser tricuLexer "" input @?= expect
|
||||||
|
|
||||||
|
, testCase "Lex empty string with escape sequence" $ do
|
||||||
|
let input = "\"\\\"\""
|
||||||
|
expect = Right [LStringLiteral "\""]
|
||||||
runParser tricuLexer "" input @?= expect
|
runParser tricuLexer "" input @?= expect
|
||||||
|
|
||||||
, testCase "Lex mixed literals" $ do
|
, testCase "Lex mixed literals" $ do
|
||||||
@ -86,8 +101,8 @@ parser = testGroup "Parser Tests"
|
|||||||
Right _ -> assertFailure "Expected failure when trying to assign the value of T"
|
Right _ -> assertFailure "Expected failure when trying to assign the value of T"
|
||||||
|
|
||||||
, testCase "Parse function definitions" $ do
|
, testCase "Parse function definitions" $ do
|
||||||
let input = "x = (\\a b c : a)"
|
let input = "x = (a b c : a)"
|
||||||
expect = SDef "x" [] (SLambda ["a"] (SLambda ["b"] (SLambda ["c"] (SVar "a"))))
|
expect = SDef "x" [] (SLambda ["a"] (SLambda ["b"] (SLambda ["c"] (SVar "a" Nothing))))
|
||||||
parseSingle input @?= expect
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Parse nested Tree Calculus terms" $ do
|
, testCase "Parse nested Tree Calculus terms" $ do
|
||||||
@ -106,8 +121,8 @@ parser = testGroup "Parser Tests"
|
|||||||
parseSingle input @?= expect
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Parse function with applications" $ do
|
, testCase "Parse function with applications" $ do
|
||||||
let input = "f = (\\x : t x)"
|
let input = "f = (x : t x)"
|
||||||
expect = SDef "f" [] (SLambda ["x"] (SApp TLeaf (SVar "x")))
|
expect = SDef "f" [] (SLambda ["x"] (SApp TLeaf (SVar "x" Nothing)))
|
||||||
parseSingle input @?= expect
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Parse nested lists" $ do
|
, testCase "Parse nested lists" $ do
|
||||||
@ -148,23 +163,23 @@ parser = testGroup "Parser Tests"
|
|||||||
parseSingle input @?= expect
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Parse nested parentheses in function body" $ do
|
, testCase "Parse nested parentheses in function body" $ do
|
||||||
let input = "f = (\\x : t (t (t t)))"
|
let input = "f = (x : t (t (t t)))"
|
||||||
expect = SDef "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
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Parse lambda abstractions" $ do
|
, testCase "Parse lambda abstractions" $ do
|
||||||
let input = "(\\a : a)"
|
let input = "(a : a)"
|
||||||
expect = (SLambda ["a"] (SVar "a"))
|
expect = (SLambda ["a"] (SVar "a" Nothing))
|
||||||
parseSingle input @?= expect
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Parse multiple arguments to lambda abstractions" $ do
|
, testCase "Parse multiple arguments to lambda abstractions" $ do
|
||||||
let input = "x = (\\a b : a)"
|
let input = "x = (a b : a)"
|
||||||
expect = SDef "x" [] (SLambda ["a"] (SLambda ["b"] (SVar "a")))
|
expect = SDef "x" [] (SLambda ["a"] (SLambda ["b"] (SVar "a" Nothing)))
|
||||||
parseSingle input @?= expect
|
parseSingle input @?= expect
|
||||||
|
|
||||||
, testCase "Grouping T terms with parentheses in function application" $ do
|
, testCase "Grouping T terms with parentheses in function application" $ do
|
||||||
let input = "x = (\\a : a)\nx (t)"
|
let input = "x = (a : a)\nx (t)"
|
||||||
expect = [SDef "x" [] (SLambda ["a"] (SVar "a")),SApp (SVar "x") TLeaf]
|
expect = [SDef "x" [] (SLambda ["a"] (SVar "a" Nothing)),SApp (SVar "x" Nothing) TLeaf]
|
||||||
parseTricu input @?= expect
|
parseTricu input @?= expect
|
||||||
|
|
||||||
, testCase "Comments 1" $ do
|
, testCase "Comments 1" $ do
|
||||||
@ -250,7 +265,7 @@ simpleEvaluation = testGroup "Evaluation Tests"
|
|||||||
, testCase "Immutable definitions" $ do
|
, testCase "Immutable definitions" $ do
|
||||||
let input = "x = t t\nx = t\nx"
|
let input = "x = t t\nx = t\nx"
|
||||||
env = evalTricu Map.empty (parseTricu input)
|
env = evalTricu Map.empty (parseTricu input)
|
||||||
result <- try (evaluate (runTricu input)) :: IO (Either SomeException String)
|
result <- try (evaluate (tricuTestString input)) :: IO (Either SomeException String)
|
||||||
case result of
|
case result of
|
||||||
Left _ -> return ()
|
Left _ -> return ()
|
||||||
Right _ -> assertFailure "Expected evaluation error"
|
Right _ -> assertFailure "Expected evaluation error"
|
||||||
@ -258,7 +273,7 @@ simpleEvaluation = testGroup "Evaluation Tests"
|
|||||||
|
|
||||||
, testCase "Apply identity to Boolean Not" $ do
|
, testCase "Apply identity to Boolean Not" $ do
|
||||||
let not = "(t (t (t t) (t t t)) t)"
|
let not = "(t (t (t t) (t t t)) t)"
|
||||||
let input = "x = (\\a : a)\nx " ++ not
|
let input = "x = (a : a)\nx " ++ not
|
||||||
env = evalTricu Map.empty (parseTricu input)
|
env = evalTricu Map.empty (parseTricu input)
|
||||||
result env @?= Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf
|
result env @?= Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf
|
||||||
]
|
]
|
||||||
@ -266,81 +281,85 @@ simpleEvaluation = testGroup "Evaluation Tests"
|
|||||||
lambdas :: TestTree
|
lambdas :: TestTree
|
||||||
lambdas = testGroup "Lambda Evaluation Tests"
|
lambdas = testGroup "Lambda Evaluation Tests"
|
||||||
[ testCase "Lambda Identity Function" $ do
|
[ testCase "Lambda Identity Function" $ do
|
||||||
let input = "id = (\\x : x)\nid t"
|
let input = "id = (x : x)\nid t"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Lambda Constant Function (K combinator)" $ do
|
, testCase "Lambda Constant Function (K combinator)" $ do
|
||||||
let input = "k = (\\x y : x)\nk t (t t)"
|
let input = "k = (x y : x)\nk t (t t)"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Lambda Application with Variable" $ do
|
, testCase "Lambda Application with Variable" $ do
|
||||||
let input = "id = (\\x : x)\nval = t t\nid val"
|
let input = "id = (x : x)\nval = t t\nid val"
|
||||||
runTricu input @?= "Stem Leaf"
|
tricuTestString input @?= "Stem Leaf"
|
||||||
|
|
||||||
, testCase "Lambda Application with Multiple Arguments" $ do
|
, 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)"
|
let input = "apply = (f x y : f x y)\nk = (a b : a)\napply k t (t t)"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Nested Lambda Application" $ do
|
, testCase "Nested Lambda Application" $ do
|
||||||
let input = "apply = (\\f x y : f x y)\nid = (\\x : x)\napply (\\f x : f x) id t"
|
let input = "apply = (f x y : f x y)\nid = (x : x)\napply (f x : f x) id t"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Lambda with a complex body" $ do
|
, testCase "Lambda with a complex body" $ do
|
||||||
let input = "f = (\\x : t (t x))\nf t"
|
let input = "f = (x : t (t x))\nf t"
|
||||||
runTricu input @?= "Stem (Stem Leaf)"
|
tricuTestString input @?= "Stem (Stem Leaf)"
|
||||||
|
|
||||||
, testCase "Lambda returning a function" $ do
|
, testCase "Lambda returning a function" $ do
|
||||||
let input = "f = (\\x : (\\y : x))\ng = f t\ng (t t)"
|
let input = "f = (x : (y : x))\ng = f t\ng (t t)"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Lambda with Shadowing" $ do
|
, testCase "Lambda with Shadowing" $ do
|
||||||
let input = "f = (\\x : (\\x : x))\nf t (t t)"
|
let input = "f = (x : (x : x))\nf t (t t)"
|
||||||
runTricu input @?= "Stem Leaf"
|
tricuTestString input @?= "Stem Leaf"
|
||||||
|
|
||||||
, testCase "Lambda returning another lambda" $ do
|
, testCase "Lambda returning another lambda" $ do
|
||||||
let input = "k = (\\x : (\\y : x))\nk_app = k t\nk_app (t t)"
|
let input = "k = (x : (y : x))\nk_app = k t\nk_app (t t)"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Lambda with free variables" $ do
|
, testCase "Lambda with free variables" $ do
|
||||||
let input = "y = t t\nf = (\\x : y)\nf t"
|
let input = "y = t t\nf = (x : y)\nf t"
|
||||||
runTricu input @?= "Stem Leaf"
|
tricuTestString input @?= "Stem Leaf"
|
||||||
|
|
||||||
, testCase "SKI Composition" $ do
|
, 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)"
|
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)"
|
||||||
runTricu input @?= "Stem (Stem Leaf)"
|
tricuTestString input @?= "Stem (Stem Leaf)"
|
||||||
|
|
||||||
, testCase "Lambda with multiple parameters and application" $ do
|
, 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)"
|
let input = "f = (a b c : t a b c)\nf t (t t) (t t t)"
|
||||||
runTricu input @?= "Stem Leaf"
|
tricuTestString input @?= "Stem Leaf"
|
||||||
|
|
||||||
, testCase "Lambda with nested application in the body" $ do
|
, testCase "Lambda with nested application in the body" $ do
|
||||||
let input = "f = (\\x : t (t (t x)))\nf t"
|
let input = "f = (x : t (t (t x)))\nf t"
|
||||||
runTricu input @?= "Stem (Stem (Stem Leaf))"
|
tricuTestString input @?= "Stem (Stem (Stem Leaf))"
|
||||||
|
|
||||||
, testCase "Lambda returning a function and applying it" $ do
|
, testCase "Lambda returning a function and applying it" $ do
|
||||||
let input = "f = (\\x : (\\y : t x y))\ng = f t\ng (t t)"
|
let input = "f = (x : (y : t x y))\ng = f t\ng (t t)"
|
||||||
runTricu input @?= "Fork Leaf (Stem Leaf)"
|
tricuTestString input @?= "Fork Leaf (Stem Leaf)"
|
||||||
|
|
||||||
, testCase "Lambda applying a variable" $ do
|
, testCase "Lambda applying a variable" $ do
|
||||||
let input = "id = (\\x : x)\na = t t\nid a"
|
let input = "id = (x : x)\na = t t\nid a"
|
||||||
runTricu input @?= "Stem Leaf"
|
tricuTestString input @?= "Stem Leaf"
|
||||||
|
|
||||||
, testCase "Nested lambda abstractions in the same expression" $ do
|
, testCase "Nested lambda abstractions in the same expression" $ do
|
||||||
let input = "f = (\\x : (\\y : x y))\ng = (\\z : z)\nf g t"
|
let input = "f = (x : (y : x y))\ng = (z : z)\nf g t"
|
||||||
runTricu input @?= "Leaf"
|
tricuTestString input @?= "Leaf"
|
||||||
|
|
||||||
, testCase "Lambda with a string literal" $ do
|
, testCase "Lambda applied to string literal" $ do
|
||||||
let input = "f = (\\x : x)\nf \"hello\""
|
let input = "f = (x : x)\nf \"hello\""
|
||||||
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))))"
|
tricuTestString 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
|
, testCase "Lambda applied to integer literal" $ do
|
||||||
let input = "f = (\\x : x)\nf 42"
|
let input = "f = (x : x)\nf 42"
|
||||||
runTricu input @?= "Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) Leaf)))))"
|
tricuTestString input @?= "Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) (Fork Leaf (Fork (Stem Leaf) Leaf)))))"
|
||||||
|
|
||||||
, testCase "Lambda with a list literal" $ do
|
, testCase "Lambda applied to list literal" $ do
|
||||||
let input = "f = (\\x : x)\nf [t (t t)]"
|
let input = "f = (x : x)\nf [t (t t)]"
|
||||||
runTricu input @?= "Fork Leaf (Fork (Stem Leaf) Leaf)"
|
tricuTestString input @?= "Fork Leaf (Fork (Stem Leaf) Leaf)"
|
||||||
|
|
||||||
|
, testCase "Lambda containing list literal" $ do
|
||||||
|
let input = "(a : [(a)]) 1"
|
||||||
|
tricuTestString input @?= "Fork (Fork (Stem Leaf) Leaf) Leaf"
|
||||||
]
|
]
|
||||||
|
|
||||||
providedLibraries :: TestTree
|
providedLibraries :: TestTree
|
||||||
@ -414,7 +433,7 @@ providedLibraries = testGroup "Library Tests"
|
|||||||
|
|
||||||
, testCase "List map" $ do
|
, testCase "List map" $ do
|
||||||
library <- evaluateFile "./lib/list.tri"
|
library <- evaluateFile "./lib/list.tri"
|
||||||
let input = "head (tail (map (\\a : (t t t)) [(t) (t) (t)]))"
|
let input = "head (tail (map (a : (t t t)) [(t) (t) (t)]))"
|
||||||
env = evalTricu library (parseTricu input)
|
env = evalTricu library (parseTricu input)
|
||||||
result env @?= Fork Leaf Leaf
|
result env @?= Fork Leaf Leaf
|
||||||
|
|
||||||
@ -518,3 +537,35 @@ demos = testGroup "Test provided demo functionality"
|
|||||||
res <- liftIO $ evaluateFileResult "./demos/levelOrderTraversal.tri"
|
res <- liftIO $ evaluateFileResult "./demos/levelOrderTraversal.tri"
|
||||||
decodeResult res @?= "\"\n1 \n2 3 \n4 5 6 7 \n8 11 10 9 12 \""
|
decodeResult res @?= "\"\n1 \n2 3 \n4 5 6 7 \n8 11 10 9 12 \""
|
||||||
]
|
]
|
||||||
|
|
||||||
|
decoding :: TestTree
|
||||||
|
decoding = testGroup "Decoding Tests"
|
||||||
|
[ testCase "Decode Leaf" $ do
|
||||||
|
decodeResult Leaf @?= "t"
|
||||||
|
|
||||||
|
, testCase "Decode list of non-ASCII numbers" $ do
|
||||||
|
let input = ofList [ofNumber 1, ofNumber 14, ofNumber 6]
|
||||||
|
decodeResult input @?= "[1, 14, 6]"
|
||||||
|
|
||||||
|
, testCase "Decode list of ASCII numbers as a string" $ do
|
||||||
|
let input = ofList [ofNumber 97, ofNumber 98, ofNumber 99]
|
||||||
|
decodeResult input @?= "\"abc\""
|
||||||
|
|
||||||
|
, testCase "Decode small number" $ do
|
||||||
|
decodeResult (ofNumber 42) @?= "42"
|
||||||
|
|
||||||
|
, testCase "Decode large number" $ do
|
||||||
|
decodeResult (ofNumber 9999) @?= "9999"
|
||||||
|
|
||||||
|
, testCase "Decode string in list" $ do
|
||||||
|
let input = ofList [ofString "hello", ofString "world"]
|
||||||
|
decodeResult input @?= "[\"hello\", \"world\"]"
|
||||||
|
|
||||||
|
, testCase "Decode mixed list with strings" $ do
|
||||||
|
let input = ofList [ofString "hello", ofNumber 42, ofString "world"]
|
||||||
|
decodeResult input @?= "[\"hello\", 42, \"world\"]"
|
||||||
|
|
||||||
|
, testCase "Decode nested lists with strings" $ do
|
||||||
|
let input = ofList [ofList [ofString "nested"], ofString "string"]
|
||||||
|
decodeResult input @?= "[[\"nested\"], \"string\"]"
|
||||||
|
]
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
-- This is a tricu comment!
|
-- This is a tricu comment!
|
||||||
-- t (t t) (t (t t t))
|
-- t (t t) (t (t t t))
|
||||||
-- t (t t t) (t t)
|
-- t (t t t) (t t)
|
||||||
-- x = (\a : a)
|
-- x = (a : a)
|
||||||
main = t (t t) t -- Fork (Stem Leaf) Leaf
|
main = t (t t) t -- Fork (Stem Leaf) Leaf
|
||||||
-- t t
|
-- t t
|
||||||
-- x
|
-- x
|
||||||
-- x = (\a : a)
|
-- x = (a : a)
|
||||||
-- t
|
-- t
|
||||||
|
@ -1 +1 @@
|
|||||||
main = (\x : x) t
|
main = (x : x) t
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
x = map (\i : append "Successfully concatenated " i) [("two strings!")]
|
x = map (i : append "Successfully concatenated " i) [("two strings!")]
|
||||||
main = equal? x [("Successfully concatenated two strings!")]
|
main = equal? x [("Successfully concatenated two strings!")]
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
compose = \f g x : f (g x)
|
compose = f g x : f (g x)
|
||||||
|
|
||||||
succ = y (\self :
|
succ = y (self :
|
||||||
triage
|
triage
|
||||||
1
|
1
|
||||||
t
|
t
|
||||||
(triage
|
(triage
|
||||||
(t (t t))
|
(t (t t))
|
||||||
(\_ tail : t t (self tail))
|
(_ tail : t t (self tail))
|
||||||
t))
|
t))
|
||||||
|
|
||||||
size = (\x :
|
size = (x :
|
||||||
(y (\self x :
|
(y (self x :
|
||||||
compose succ
|
compose succ
|
||||||
(triage
|
(triage
|
||||||
(\x : x)
|
(x : x)
|
||||||
self
|
self
|
||||||
(\x y : compose (self x) (self y))
|
(x y : compose (self x) (self y))
|
||||||
x)) x 0))
|
x)) x 0))
|
||||||
|
|
||||||
size size
|
size size
|
||||||
|
@ -1 +1 @@
|
|||||||
head (map (\i : append "String " i) [("test!")])
|
head (map (i : append "String " i) [("test!")])
|
||||||
|
@ -1 +1 @@
|
|||||||
y = \x : x
|
y = x : x
|
||||||
|
31
tricu.cabal
31
tricu.cabal
@ -1,7 +1,7 @@
|
|||||||
cabal-version: 1.12
|
cabal-version: 1.12
|
||||||
|
|
||||||
name: tricu
|
name: tricu
|
||||||
version: 0.15.0
|
version: 1.0.0
|
||||||
description: A micro-language for exploring Tree Calculus
|
description: A micro-language for exploring Tree Calculus
|
||||||
author: James Eversole
|
author: James Eversole
|
||||||
maintainer: james@eversole.co
|
maintainer: james@eversole.co
|
||||||
@ -21,18 +21,32 @@ executable tricu
|
|||||||
LambdaCase
|
LambdaCase
|
||||||
MultiWayIf
|
MultiWayIf
|
||||||
OverloadedStrings
|
OverloadedStrings
|
||||||
|
ScopedTypeVariables
|
||||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N -optl-pthread -fPIC
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N -optl-pthread -fPIC
|
||||||
build-depends:
|
build-depends:
|
||||||
base >=4.7
|
base >=4.7
|
||||||
|
, aeson
|
||||||
|
, ansi-terminal
|
||||||
|
, base64-bytestring
|
||||||
|
, bytestring
|
||||||
|
, cereal
|
||||||
, cmdargs
|
, cmdargs
|
||||||
, containers
|
, containers
|
||||||
|
, cryptonite
|
||||||
|
, directory
|
||||||
, exceptions
|
, exceptions
|
||||||
, filepath
|
, filepath
|
||||||
|
, fsnotify
|
||||||
, haskeline
|
, haskeline
|
||||||
, megaparsec
|
, megaparsec
|
||||||
, mtl
|
, mtl
|
||||||
|
, sqlite-simple
|
||||||
|
, tasty
|
||||||
|
, tasty-hunit
|
||||||
, text
|
, text
|
||||||
|
, time
|
||||||
, transformers
|
, transformers
|
||||||
|
, zlib
|
||||||
other-modules:
|
other-modules:
|
||||||
Eval
|
Eval
|
||||||
FileEval
|
FileEval
|
||||||
@ -51,20 +65,31 @@ test-suite tricu-tests
|
|||||||
LambdaCase
|
LambdaCase
|
||||||
MultiWayIf
|
MultiWayIf
|
||||||
OverloadedStrings
|
OverloadedStrings
|
||||||
|
ScopedTypeVariables
|
||||||
build-depends:
|
build-depends:
|
||||||
base
|
base >=4.7
|
||||||
|
, aeson
|
||||||
|
, ansi-terminal
|
||||||
|
, base64-bytestring
|
||||||
|
, bytestring
|
||||||
|
, cereal
|
||||||
, cmdargs
|
, cmdargs
|
||||||
, containers
|
, containers
|
||||||
|
, cryptonite
|
||||||
|
, directory
|
||||||
, exceptions
|
, exceptions
|
||||||
, filepath
|
, filepath
|
||||||
|
, fsnotify
|
||||||
, haskeline
|
, haskeline
|
||||||
, megaparsec
|
, megaparsec
|
||||||
, mtl
|
, mtl
|
||||||
|
, sqlite-simple
|
||||||
, tasty
|
, tasty
|
||||||
, tasty-hunit
|
, tasty-hunit
|
||||||
, tasty-quickcheck
|
|
||||||
, text
|
, text
|
||||||
|
, time
|
||||||
, transformers
|
, transformers
|
||||||
|
, zlib
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
other-modules:
|
other-modules:
|
||||||
Eval
|
Eval
|
||||||
|
Reference in New Issue
Block a user